Skip to content

wit: normalize every package to 0.1.0#429

Merged
mfw78 merged 1 commit into
dev/m1from
feat/wit-normalize-0-1-0
Jul 20, 2026
Merged

wit: normalize every package to 0.1.0#429
mfw78 merged 1 commit into
dev/m1from
feat/wit-normalize-0-1-0

Conversation

@mfw78

@mfw78 mfw78 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What

Resets every WIT package version and use ... @<ver> reference to a single @0.1.0 across nexum:host and shepherd:cow (the videre:* packages were already @0.1.0 post-rename). Deletes docs/migration/0.1-to-0.2.md and the "Migration from 0.1" section in docs/08-platform-generalisation.md, plus the now-dead cross-links to it in docs 00, 01, 02, 04, 05, 07, the ADRs, sdk.md, and diagrams.md.

Why

The @0.2.0 strings were pre-release cruft, not a real compatibility boundary, and no external consumer pins them. After this cut each package versions independently, so the shared baseline needs to be a clean @0.1.0.

Testing

cargo check / cargo clippy --workspace --all-features (nix develop).

AI Assistance

Drafted with AI assistance under human review.

Closes #371

@mfw78 mfw78 linked an issue Jul 16, 2026 that may be closed by this pull request

@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.

The @0.2.0 -> @0.1.0 version-string sweep itself is complete and clean — checked every .wit file and Rust string literal referencing a WIT import path repo-wide, no stragglers.

The migration-doc link cleanup is incomplete, though, in the same way #344 was: docs/05-sdk-design.md still has 4 dangling inline links to migration/0.1-to-0.2.md that this PR doesn't touch (the diff only removes the one reference-list bullet around line 304-309):

  • docs/05-sdk-design.md:296[migration guide §7](migration/0.1-to-0.2.md#7-sdk-changes-author)
  • docs/05-sdk-design.md:436[migration guide §2](migration/0.1-to-0.2.md#2-error-model-unification-both)
  • docs/05-sdk-design.md:608[migration guide §2](migration/0.1-to-0.2.md#2-error-model-unification-both)
  • docs/05-sdk-design.md:961[migration guide §10](migration/0.1-to-0.2.md#10-deprecation-policy-going-forward-both)

Since this PR deletes the target file, all four will 404 post-merge. Everything else claimed as cleaned up (docs 00, 01, 02, 04, 07, 08, ADR-0001, sdk.md, diagrams.md) checked out fully clean — structurally sound removals, no orphaned headers or broken markdown. Worth a quick follow-up on just these 4 lines before merging.

@mfw78
mfw78 force-pushed the feat/videre-rename-reshape branch from cdda2ba to 6e8371a Compare July 20, 2026 03:45
Base automatically changed from feat/videre-rename-reshape to dev/m1 July 20, 2026 04:32
Reset nexum:host and shepherd:cow package and use versions to 0.1.0,
the shared baseline every package now semvers from independently.
wasi:* keeps its own 0.2.x. Drop the 0.1-to-0.2 migration guide and
its references.

Closes #371
@mfw78
mfw78 force-pushed the feat/wit-normalize-0-1-0 branch from 2943848 to 0e79f7b Compare July 20, 2026 04:34
@mfw78

mfw78 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

On the version sweep: confirmed clean, thanks.

On the migration-doc links: resolved at HEAD, not a live issue on this branch. The four inline links you cited are develop-state, where the target file also still exists, so they are not broken there. On dev/m1 (this PR's base, well ahead of develop) those four inline links were already removed by #245 (558e627, the doc-05 rewrite). This PR (2943848) removes the sole remaining reference (the doc-index bullet at line 307) and deletes docs/migration/0.1-to-0.2.md itself.

Verified end to end: docs/05-sdk-design.md has zero references to migration/0.1-to-0.2.md at this PR's head and through to the last car of the train, so nothing 404s post-merge. No follow-up needed.

@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.

Re-posting as an inline comment (my earlier review body-only comment didn't render in the diff view).

Comment thread docs/05-sdk-design.md
return.
- [Migration guide §7](migration/0.1-to-0.2.md#7-sdk-changes-author) -
what changed in the SDK surface between 0.1 and 0.2.
- [doc 07](07-rpc-namespace-design.md) - the `chain` RPC passthrough

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 @0.2.0 -> @0.1.0 version-string sweep itself is complete and clean — checked every .wit file and Rust string literal referencing a WIT import path repo-wide, no stragglers.

The migration-doc link cleanup is incomplete, though, in the same way #344 was: this file still has 4 dangling inline links to migration/0.1-to-0.2.md that this diff doesn't touch (the diff only removes the one reference-list bullet just above this line):

  • docs/05-sdk-design.md:296[migration guide §7](migration/0.1-to-0.2.md#7-sdk-changes-author)
  • docs/05-sdk-design.md:436[migration guide §2](migration/0.1-to-0.2.md#2-error-model-unification-both)
  • docs/05-sdk-design.md:608[migration guide §2](migration/0.1-to-0.2.md#2-error-model-unification-both)
  • docs/05-sdk-design.md:961[migration guide §10](migration/0.1-to-0.2.md#10-deprecation-policy-going-forward-both)

Since this PR deletes the target file, all four will 404 post-merge. Everything else claimed as cleaned up (docs 00, 01, 02, 04, 07, 08, ADR-0001, sdk.md, diagrams.md) checked out fully clean — structurally sound removals, no orphaned headers or broken markdown. Worth a quick follow-up on just these 4 lines before merging.

mfw78 added a commit that referenced this pull request Jul 27, 2026
* feat(sdk): add keeper conditional-source seam and CoW run loop (#239)

* feat(sdk): add the keeper conditional-source seam, retry ledger, and run

Introduce the world-neutral half of the poll loop in
nexum_sdk::keeper: a ConditionalSource trait (one watch in, one
outcome out, generic over the host, no venue-transport
pre-abstraction), a Tick carrying the dispatch instant, and the
RetryAction (try-next-block / backoff / drop) whose effects the
Retrier runs through the gate and watch-set stores. RetryAction
moves out of shepherd-sdk; the CoW crate re-exports it so no module
rewires.

shepherd-sdk keeps classify_api_error as the CoW errorType table but
returns the keeper action, and encodes two fixes in the one
classification path: classify_poll_error maps an unrecognised revert
selector with a structured payload to DontTryAgain instead of
re-polling every block forever, and DuplicatedOrder (both spellings)
classifies as already-submitted - the run records the
submitted: receipt and keeps the watch rather than dropping every
future tranche. classify_submit_error widens the table to the whole
CowApiError surface and gives Backoff its producer: a rate-limit
fault with server guidance gates the watch on the epoch clock.

cow::run composes the loop: watch-set scan -> gate check ->
source poll -> PollOutcome dispatch, driving submission through the
existing CowApiHost seam with the journal as the idempotency guard
and the retry ledger as the failure dispatch. Acceptance tests run
against the composed shepherd-sdk-test MockHost as integration
tests; no module is rewired yet.

* docs(sdk): disambiguate the run intra-doc link

* fix(cow): build the classify_poll_error test RpcError data as Bytes

RpcError.data is now Bytes; the test helper takes raw Vec<u8> and wraps
it (Vec -> Bytes is O(1)).

* fix(cow): keep the sweep alive when a post-submit journal write faults

journal.record after a successful submit_order was propagating a store
fault out of submit_ready, aborting the whole watch sweep with the
receipt unwritten - the next tick then re-polled and re-posted the same
order. Log the failure and carry on instead; the already-submitted arm
keeps the re-post idempotent.

Both post-submit writes are covered: the Ok arm and the
already-submitted (DuplicatedOrder) arm.

* refactor(twap-monitor): port poll loop onto keeper (#240)

* feat(sdk): log run diagnostics through the tracing facade

The run logged through the LoggingHost seam, which the
guest tracing capture cannot observe - module tests proving
behaviour identity for keeper ports assert on tracing events.
Route the submit-path diagnostics through the tracing macros with
the wording the legacy twap poll loop established, and give
ConditionalSource a defaulted label so shared log lines attribute
the strategy that produced them.

* refactor(twap-monitor): port the poll loop onto the keeper composition

Reduce strategy.rs to decode and evaluate: log decoding persists
watches through the keeper watch set, and the
getTradeableOrderWithSignature evaluation moves behind
ConditionalSource. The hand-rolled gate keys, watch-update
lifecycle, submitted: markers, and submit-retry dispatch are
deleted in favour of cow::run over the keeper stores and
retry ledger, still on the legacy CowApiHost seam. The MockHost
dispatch tests are untouched - the behaviour-identity proof for
the port.

* feat(sdk-test): add MockVenue and namespaced MockLocalStore (#241)

* feat(sdk-test): namespace the mock local store and share its limits

Rework MockLocalStore to mirror the runtime store's shape: namespaced
views over one shared row map, so identical key strings in sibling
namespaces never collide, plus store-wide entry and byte limits that
span namespaces the way one database file does. Fault injection stays
per-view.

* feat(sdk-test): add the programmable MockVenue on the cow-api seam

Script per-call venue behaviour where MockCowApi replays one response:
a strictly-draining queue of submit outcomes with a configurable
steady-state fallback, per-route response sequences whose final entry
sticks (a terminal order status persists across re-polls), and venue
fault injection that overrides both without consuming them. MockHost
grows a defaulted venue type parameter so the composed host carries
either mock on the same CowApiHost seam. Acceptance tests drive the
keeper run across multi-tick retry, backoff, and outage
scenarios, and module-shaped strategy code against status sequences.

* fix(keeper): drop stale watches and idle duplicate rejections (#242)

* fix(sdk): drop the watch when the OrderCreation constructor rejects the order

A constructor rejection (zero from, validTo beyond the client-side
one-year horizon) is deterministic for the polled payload: skipping
with the watch intact re-polled and re-warned on every block forever.
Route it through the retry ledger as a drop, matching the pre-keeper
net effect where the orderbook rejected the shipped body (e.g.
ExcessiveValidTo) and the classifier dropped the watch. The
unknown-enum-marker skip keeps its pinned keep-the-watch behaviour.

Also log the keeper-level DontTryAgain removal so a permanent drop is
observable even for sources that do not log their own outcomes.

* fix(stop-loss): record the submitted receipt on a duplicate rejection

classify_api_error maps DuplicatedOrder to TryNextBlock on the
premise that the caller records the submitted: receipt - true for the
run, but stop-loss consumed the classification without the
compensating write, so a duplicate rejection re-POSTed every block
until validTo. Mirror run::submit_ready: treat
already-submitted as success wearing an error status, write the marker
the dedup guard reads, and idle.

* refactor(sdk): hoist the watch key to a free fn and shed the cross-layer dev cycle

WatchSet::key never used the host parameter, forcing a meaningless
turbofish at every call site (one test existed solely to prove two
instantiations agree). keeper::watch_key is the free canon;
WatchSet::key stays as a thin delegate for discoverability.

The keeper acceptance tests only touch the local-store seam, so they
now run against nexum-sdk-test's MockHost (the same MockLocalStore
type), shrinking nexum-sdk's dev cycle from the cross-layer
shepherd-sdk-test pair - which dragged the whole CoW domain layer into
the world-neutral crate's dev graph - to the within-layer pair its own
mock crate documents.

* feat(twap-monitor): carry the revert cause on the permanent poll-drop path

A revert that classifies to DontTryAgain destroyed the watch with only
an Info outcome label; the selector and node message needed to triage
a wrongly-dropped watch were discarded. Warn with both before
returning the outcome, and pin the log lines in the drop test. The
test-only watch_key wrapper now reuses the keeper free fn.

* docs(sdk): correct stale order.rs rustdoc

OrderCreation::from_signed_order_data was renamed to OrderCreation::new
in cowprotocol 0.2; and only the unknown-marker case of order_uid_hex
returning None also stops the submit path downstream - an unsupported
chain id does not, so say so instead of claiming both do.

* chore(backtest): retire the dead app_data resolution scripting

The epic removed resolve_app_data and the orderbook-mock's
GET /api/v1/app_data route, but the replay harness still programmed
that route against a strategy that never requests it. Drop the dead
programming (and the now-unused shepherd-sdk dependency) and refresh
the module doc to the hash-only submission flow. The RejectedExpected
classification is kept for historical report comparability until the
harness is reworked around the observer-only strategy (follow-up).

* docs: surface the keeper and run in the overview; record the cow-rs patch-channel move

The overview's SDK table predated the epic's headline surface: add the
nexum-sdk keeper row and the shepherd-sdk cow::run row. Amend
ADR-0004 with the move of the [patch.crates-io] channel from
bleu/cow-rs to nullislabs/cow-rs and what the pinned rev carries.

* style: rustfmt the sweep additions

* docs: reword PR-number references out of module comments

Numeric issue/PR references in .rs files go stale across repository
moves; describe the change instead of pointing at the review.

* feat(sdk): scaffold nexum-macros and #[module] attribute (#243)

* feat(sdk): scaffold nexum-macros and ship #[module] attribute

Add the `nexum-macros` proc-macro crate. `#[nexum_sdk::module]`
generates the per-cdylib glue every module hand-wrote: the
`wit_bindgen::generate!` call for the blanket `shepherd:cow/shepherd`
world, the host adapter via `bind_host_via_wit_bindgen!`, the `Guest`
implementation whose `on-event` dispatches to the named handlers
(`init`, `on_block`, `on_chain_logs`, `on_tick`, `on_message`, with
undefined handlers ignored), and `export!`. The WIT directory is
located by walking up from the consuming crate's manifest, so the
emitted paths need no per-module tuning.

Re-export it as `nexum_sdk::module` and port the example, balance-tracker
and price-alert modules onto it; each strategy MockHost suite passes
unchanged.

* refactor(http-probe): port onto #[nexum::module]

Complete the acceptance list: drop the hand-written wit-bindgen glue,
host adapter, and Guest/export block in favour of the attribute, leaving
only the init and on-block handlers. Strategy MockHost suite unchanged.

* docs(sdk): rewrite doc 05 around the two-persona SDK plan (#245)

Doc 05 was stamped future-direction and described the superseded
macro/TypedState/Signer/HostTransport vision. Replace it with the
shipped module-author persona (nexum-sdk + shepherd-sdk + the landed
#[nexum::module] macro) and the planned venue-adapter persona
(nexum-venue-sdk / per-venue-crate), clearly labelled as design intent
tracked by a separate epic. Drop the deferred-features table and
cross-link ADR-0009 and sdk.md.

* fix(sdk): M1 SDK sweep fixes - macro hardening, doc fidelity (#246)

* fix(macros): reject typo'd handlers and malformed impls at expansion

A method named on_blocks (or any other on_-prefixed typo) previously
compiled as an ordinary helper while its event silently no-opped; the
only signal was a dead_code warning that vanishes on pub items.
Reserve the on_ prefix for the recognised handler set and error on
anything else.

Also reject trait impls, generic impls, and impls with no recognised
handlers - all previously slipped past the self-type guard and failed
later with incidental, unhygienic errors (or compiled into a do-nothing
module). Document the wit-bindgen direct-dependency and prelude-
shadowing invariants inherited from the generated glue.

* docs(migration): rewrite SDK section 7 around the shipped surface

Section 7 still described the superseded Signer/Messaging/RemoteStore/
provider() vision - APIs that never shipped - and a #[shepherd::module]
macro that does not exist, while doc 05 endorsed it as the authoritative
0.1 -> 0.2 SDK table. Replace it with the real story: 0.1 had no SDK
crate, 0.2 ships nexum-sdk/shepherd-sdk host traits, typed errors, and
the single #[nexum_sdk::module] attribute. Point doc 05's cross-link
text at what the section now says, and fix doc 05 details flagged by
the sweep: the CowApiHost parenthetical (submit_order, cow_api_request),
the crate trees (proptests.rs in both SDK crates), the testing-story
claim (nexum-runtime's feature-gated test_utils::TestRuntime is an
in-tree component-level harness), and the first #[nexum::module]
mention now names the real nexum_sdk path.

* docs(sdk): document the #[nexum_sdk::module] attribute in sdk.md

sdk.md is doc 05's designated day-to-day reference but still presented
the pre-macro bind_host_via_wit_bindgen! flow as the authoring pattern.
Add an authoring section covering the attribute and its handler set,
mention it in the intro, and include nexum-macros in the rustdoc
command.

* feat(wit): add nexum:value-flow types package v0.1.0 (#247)

feat(wit): author the nexum:value-flow types package

Add the egress-neutral value-flow vocabulary at 0.1.0: the settlement
domain (evm-chain or offchain), the asset variant (native-token, erc20,
erc721, erc1155, service, offchain), the service and offchain
descriptors, and the big-endian asset-amount. The package is
dependency-free so it can outlive any interface built on it.

Every identifier is vetted against WIT keywords, in-flight component-model
proposals, and the nine binding-target reserved-word lists, preferring a
two-word kebab id wherever a single word is a keyword anywhere:
native-token over native (a Java modifier), offchain over external (a
Dart keyword), value-flow over value (value-imports is circling that
word). A wasmtime bindgen smoke, compiled under test in the host crate
through a throwaway world, names every generated type and field by its
plain Rust spelling so a keyword collision surfaces as an escaped
identifier and fails the build rather than a downstream binding.

* feat(wit): author the nexum:intent package with pool interface (#248)

feat(wit): author the nexum:intent package with the pool interface

Add the venue-neutral intent ontology at 0.1.0 over the value-flow
vocabulary: the intent-header (gives/wants/valid-until/settlement/
authorisation), the auth-scheme variant (eip712, eip1271, presign,
offchain-sig, unsigned), the intent-status lifecycle with a structured
fail-reason, the opaque receipt alias, and a self-contained venue-error
(deliberately not embedding the host fault type, so the package's freeze
cadence is not pinned to nexum:host versioning).

The pool interface routes submit/status/cancel by a venue string with
the body as opaque bytes. submit returns a submit-outcome variant from
day one: accepted(receipt), or requires-signing(unsigned-tx) for venues
whose settlement is a host-signed on-chain call. The unsigned-tx record
only describes the call (chain, to, value, input); the host routes it
through the guard's host-signed class and fills gas and fees, so
adapters still structurally cannot move value.

Identifier hygiene follows the value-flow rule (internal-error rather
than internal, a Swift declaration keyword). A wasmtime bindgen smoke,
compiled under test in the host crate through a throwaway world that
imports the pool interface, names every generated type, case, and field
by its plain Rust spelling and pins the three pool function signatures
with a dummy host impl.

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

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

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.

* docs(runtime): keep the venue-adapter world token on one line

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.

* feat(runtime): route nexum:intent/pool to installed venue adapters (#250)

Implement the strategy-facing pool import as a host binding linked into
every module linker, dispatching to a shared router that owns the
installed adapters.

- Bindings: a pool-host bindgen world importing nexum:intent/pool,
  reusing the intent and value-flow types from the venue-adapter
  bindings via `with`, so the outcome and error the router hands back to
  a module are the very types an adapter's submit produced.
- Router: resolve a venue id to its adapter, serialise invocation per
  adapter behind an async mutex (the wasmtime Store is not Sync),
  sequence derive-header, a no-op guard interposition seam, then submit.
  Status and cancel pass through without the header, guard, or quota.
- Quota: a per-caller sliding-window submission budget, with adapter
  decode failures charged to the caller so a module feeding garbage
  bodies exhausts its own budget rather than the adapter's fuel.
- Supervisor: adapters instantiate first and install into the router;
  modules then boot carrying the shared handle, rebuilt identically on
  restart. An init-failed adapter is loaded but not routable.
- Manifest: register nexum:intent/pool as a declarable module capability.
- Config: a [limits.quota] section resolving the per-caller budget.

* feat(venue-sdk): add nexum-venue-sdk crate (#251)

* feat(sdk): ship nexum-venue-sdk over the venue-adapter world

Introduce the venue-author persona crate: the VenueAdapter trait and
export macro over an in-crate bindgen of the adapter world, the borsh
IntentBody derive whose outer version enum fails unknown tags typedly,
the typed intent client core over the byte-level pool seam, and typed
wrappers over the scoped chain, messaging, and wasi:http imports. The
derive expansion lands in nexum-macros and re-exports from the SDK;
borsh enters the workspace dependency table.

* fix(venue-sdk): wrap the wit chain-error data into Bytes

The SDK RpcError.data is Bytes; the wit-bindgen chain-error carries
Vec<u8>, so wrap it (Vec -> Bytes is O(1)) when reprojecting.

* sdk: emit per-component world from declared capabilities (#252)

* feat(sdk): capability-select the wit-bindgen host adapter

Split bind_host_via_wit_bindgen! into a types-only base plus one block
per capability, selected through a caps list; the zero-argument form
keeps emitting the full chain, local_store, logging set for modules
compiled against a blanket world. Narrow read_latest_answer to
ChainHost + LoggingHost, the two capabilities it exercises, so modules
whose worlds omit local-store can still call it.

* feat(sdk): emit a per-module world from declared capabilities

Teach #[nexum_sdk::module] to read the crate's module.toml and
synthesize an inline WIT world whose imports are exactly the
[capabilities].required and optional declarations, replacing the
blanket shepherd:cow/shepherd world every module compiled against. The
built component's imports now equal its declarations by construction,
so the runtime's capability check no longer leans on the toolchain
eliding unused imports; an undeclared capability has no bindings at
all, and an unknown capability name is a compile-time error. The
emitted host adapter is capability-selected to match, and a rebuild
anchor on module.toml recompiles the module when the manifest changes.

Narrow price-alert's strategy bound to ChainHost + LoggingHost: its
world imports only its declared chain and logging capabilities, so the
full Host supertrait (which adds local-store) is unimplementable there
by design.

* test(runtime): pin the example component's imports to its declarations

The per-module world acceptance: compile the built example component
and assert its capability-bearing imports resolve to exactly the
manifest's declared set, with no extension interface leaking in. Skips
gracefully when the wasm fixture is not built, like the other e2e
tests.

* docs: record the retirement of import-elision for macro-built modules

Macro-built components import what they declare by construction, so
capability enforcement is a backstop for them; the elision dependency
ADR-0009 flagged now applies only to hand-rolled modules still
compiled against the supertype world.

* feat(runtime): fan intent-status events to module subscribers (#253)

* feat(runtime): add the intent-status case to the host event variant

The host event variant gains intent-status(intent-status-update): a
venue id, the receipt, and the nexum:intent status vocabulary, so a
subscriber sees where an intent is in its life without knowing how the
host learnt it. nexum:host now depends on nexum:intent, so every
bindgen over the host package carries the intent and value-flow
packages on its resolve path, in dependency order; the runtime
generates the intent types once and the adapter and pool bindgens
remap onto them with 'with'. The module macro recognizes an
on_intent_status handler and the example module ships one.

* feat(runtime): poll venue statuses and fan intent-status events to subscribers

The pool router puts every accepted receipt under watch and, on a
configurable cadence ([limits.status_poll] interval_ms), polls each
adapter's status export, reporting only transitions: the first poll
reports the current status, repeats are deduplicated, a terminal
status prunes the watch, and a receipt the venue disowns is dropped.
A dedicated event-loop stream fans each transition to modules
declaring [[subscription]] kind = "intent-status", optionally
filtered by venue; polling only runs when there is at least one
subscriber and one installed adapter.

* feat(sdk): add the #[nexum::venue] adapter macro (#296)

Extend nexum-macros with the venue-adapter counterpart to #[module]:
read the crate's module.toml, synthesize a per-component venue-adapter
world exporting init plus nexum:intent/adapter and importing exactly the
declared scoped transport, then emit the per-cdylib wit-bindgen call, the
Guest export glue wiring the adapter's associated functions, and export!.

An adapter built this way imports what its manifest declares and nothing
else, retiring the toolchain-elision dependency on the venue side rather
than as follow-on work. A capability outside the venue-permitted set
(chain, messaging, http) is rejected at expansion, so an adapter
structurally cannot reach host key material or persistent state.

Ship echo-venue as the reference adapter and pin its built component's
capability-bearing imports to its single declared capability.

* feat(sdk-test): add the nexum-venue-test conformance kit (#297)

Venue adapters need a way to prove their wire behaviour against the
venue's published contract, in a form a non-Rust author can consume:
codec vectors and header goldens ship as JSON files (bytes as
lowercase hex) rather than Rust-only test code.

CodecVectors publishes IntentBody wire bytes and holds a codec to
them: round-trip vectors must decode and re-encode byte-exactly, and
the failure vectors pin the typed empty, unknown-version, and
malformed error contract. HeaderGoldens pairs published bodies with
the header a conforming derive-header projects, spelled in serde
mirror types whose case names match the WIT. MockTransport composes
chain, messaging, and outbound HTTP mocks behind the SDK seams,
including the messaging_topics scoping refusal.

A reference venue (schema, derivation, and checked-in fixture files
pinned by regeneration tests) documents both formats and proves the
borsh primitive encodings for authors porting the codec.

* feat(echo-venue): settle instantly, add echo-client round trip (#256)

feat(examples): pair echo-venue with an echo-client and prove the round-trip

Add echo-client, the strategy half of the echo pair: it submits an
opaque body through nexum:intent/pool on every block and logs the
intent-status transitions the router fans back. Wire both real
components through an integration test that proves the intent core end
to end, module -> host router -> echo adapter submit and status back,
which is the first-train acceptance path.

Settle the echo venue instantly (status now reports settled) so the
round-trip reaches a terminal transition, and hold its header
derivation to the nexum-venue-test kit so echo-venue doubles as the
conformance target alongside its tutorial role.

Register echo-client in the workspace, the build recipes, and CI.

* docs(wit): freeze WIT clarifications, fix macro nits (#257)

* docs(wit): pin canonical amount encoding and reserve native-token(offchain)

Mandate minimal-length big-endian for asset-amount and require decoders
to compare by value, so the same amount has one canonical wire form
across the binding targets. Document native-token(offchain) as
representable-but-invalid: an off-chain settlement domain has no gas
token, so consumers must reject the pairing rather than interpret it.

* fix(macros): correct venue manifest error attribution and accumulate venue packages

The shared missing-[capabilities] error named #[nexum_sdk::module] even
on the venue path; make it macro-neutral so a venue crate is not told to
use the wrong attribute. Mirror synthesize's package-accumulation loop in
synthesize_venue so a future venue capability carrying an extra WIT
package reaches the resolve path (all venue capabilities are packageless
today, so the base set is unchanged).

* docs: normalise doc comments to British -ise spelling

Align the authorise/authorisation prose in the intent WIT and the
conformance kit with the British spelling used elsewhere and with the
authorisation identifier itself. Doc comments only; no identifier, wire
format, or API name changes.

* docs(runtime): document the pool submit charge asymmetry

State the deliberate rule that a forwarded submission is charged once the
guard admits it regardless of the venue outcome, while a derive-stage
non-decode venue error is left uncharged so the caller may retry.

* feat(cow-venue): scaffold venue-neutral CoW intent body crate (#258)

* feat(cow-venue): scaffold default body slice with borsh IntentBody codec

Stage the CoW venue as a crate of feature slices. The default `body`
slice carries the venue-neutral order and composable intent body types
and the borsh IntentBody codec over the outer per-venue version enum, so
a future adapter component or a strategy module can link the bodies and
codec without the host-side CoW machinery.

The slice links only the venue SDK (for the IntentBody derive) and borsh;
`--no-default-features` drops it to an empty crate. shepherd-sdk gains a
re-export shim under `cow` so the module ports can move off the legacy
path without a breaking rename.

* style(cow-venue): use -ize spelling and drop test reach into borsh __private

* feat(cow-venue): drive CoW retry classification from a data table (#259)

* feat(cow-venue): drive cow retry classification from a shipped data table

Ship the CoW orderbook errorType retry policy as data in
crates/cow-venue/data/classification.toml, readable and editable by
non-Rust authors, and add a `client` feature slice that embeds it,
exposes a typed CowClient bound to the CoW venue, and a table-driven
classification API returning the keeper RetryAction.

Replace the hand-coded classify_api_error/is_retriable in the cow error
surface with the table lookup, preserving the already-submitted and
drop-on-unrecognized behaviour. A throttle errorType now maps to
Backoff, so every RetryAction arm is reachable from the table alone,
guarded by a data-vs-code parity test and an untyped-parse test that
proves any TOML reader consumes the same file.

* perf(cow-venue): generate the classification table at build time

shepherd-sdk unconditionally enables the cow-venue client slice, and the
wasm modules (twap-monitor, ethflow-watcher) reach classify_api_error
through shepherd_sdk::cow::run, so the runtime LazyLock TOML
parse linked the full toml/serde/winnow stack into every guest: the
twap-monitor wasm grew ~237 KiB (350 KiB -> 586 KiB).

Move the parse to build.rs. It reads data/classification.toml, validates
the table invariants, and emits a static lookup table; the runtime slice
carries only generated rows and no TOML parser. serde/toml/thiserror
become build- and dev-only. The parse and invariants live in a shared
classification_data module that build.rs and the parity test both link,
so the data-vs-code parity test now re-parses the shipped file and
checks the generated table against it. The guest returns to 351 KiB.

* fix(cow-venue): QA sweep for m1-cow-classification (#260)

* fix(cow): use canonical InvalidEip1271Signature errorType spelling

The permanent_kinds_yield_drop test asserted on "InvalidErc1271Signature",
which is not a real CoW orderbook errorType (the OpenAPI OrderPostError enum
and cowprotocol's OrderbookApiErrorType both spell it InvalidEip1271Signature,
matching data/classification.toml). The phantom spelling passed only via the
unlisted-default Drop, so it never exercised the real table row. Use the
canonical spelling so the test covers the actual entry.

* docs(cow-venue): ratify deliberate divergence from cowprotocol retry_hint

shepherd-sdk already depends on cowprotocol, whose ApiError::retry_hint()
classifies the same orderbook errorTypes and disagrees with this table on
several (e.g. it retries/backs off InvalidEip1271Signature, InsufficientBalance,
InsufficientAllowance and InvalidAppData where this table drops, and backs off
TooManyLimitOrders for an hour rather than 30s). The table is deliberately not
delegated to it: it is shepherd's own, more conservative policy, kept as data
of record so a non-Rust author owns it and the guest client slice stays free
of the upstream error module. Record that this divergence is intentional so a
future maintainer revisits the policy here rather than swapping the source of
truth. (Also fixes the -ise spelling in the same comment.)

* style(cow-venue): adopt British -ise spelling to match house convention

The crate used American -ize (normalizes/normalization, unrecognized) while
the adjacent domain code (shepherd-sdk cow/order.rs normalised, composable.rs
unrecognised, run.rs) and the repo at large use British -ise. Align
the prose so the sibling modules read consistently.

* fix(cow-venue): allow GenAction variants unused by shipped data

GenAction's variants are constructed only by the build-generated table, so a
classification.toml that carries no row of a given action leaves that variant
unconstructed and trips the -D warnings build. Since which actions appear is a
property of the data, not the code, mark the enum allow(dead_code) so a
data-only edit cannot break the workspace lint gate.

* refactor(cow): structured Verdict poll seam + LegacyRevertAdapter (Wave 1, ADR-0013) (#334)

* docs: reconcile keeper prose and Verdict poll-seam references (#335)

Tip-level residual after the chassis to keeper rename was folded down through the train. Carries the doc-comment prose the fold could not move mechanically (the private-keeper caveat, keeper-run wording) and the current-state design docs brought in line with the Verdict poll seam (PollOutcome to Verdict, decode_revert to LegacyRevertAdapter).

* docs(sdk-test): note MockLocalStore fidelity gaps vs redb

No transaction semantics and no concurrent access (deferred to the
MockRuntime refactor, #94). Salvaged from #168 before closing it as
superseded by the shipped namespaced MockLocalStore.

* docs: multi-chain deployment patterns — Mainnet/Gnosis/Arbitrum/Base (#351)

* docs: multi-chain deployment patterns — Mainnet/Gnosis/Arbitrum/Base (#124)

New docs/deployment/multi-chain.md covering the verbatim M5 grant
deliverable: per-chain [chains.<id>] + env-var wiring, CoW orderbook
URL slugs per network, per-chain contract addresses (CREATE2-stable
vs EthFlow per-network caveat), the [[subscription]] duplication
pattern with twap-monitor and ethflow-watcher examples, event topic
reference, and resource sizing guidance.

* docs(multi-chain): correct the EthFlow address claim and scope statements

Review catches:

- The EthFlow section claimed the address is per-network and told
  operators to hunt for a different mainnet address. The current
  production deployment is address-identical on every supported chain
  (cowprotocol's ETH_FLOW_PRODUCTION, from networks.prod.json) - the
  old text would have steered a mainnet port toward the legacy v1.0.0
  deployment and missed every OrderPlacement event. The real caveat is
  narrower and now stated as such: sameness is not a CREATE2 guarantee,
  legacy per-version deployments exist, verify on version bumps.
- "every chain the orderbook supports" was false (the Chain enum also
  carries BNB, Polygon, Avalanche, Linea, Plasma); the table is scoped
  to the chains this repo's modules target.
- Dropped the unsupported "Gnosis volume roughly equal to Mainnet"
  claim; require_ws violations are an ERROR log, not a warning; the
  RPC recommendation names a rate requirement instead of a specific
  provider's free tier.

---------

Co-authored-by: Luiz Gustavo Abou Hatem de Liz <luizgustavoahsc@gmail.com>

* docs: stale-reference sweep — engine rename, Dockerfile, ADR-0007 errata (#352)

* docs: stale-reference sweep — engine rename, Dockerfile, ADR-0007 errata (#123, #108, #62)

- deployment.md: replace the "planned for M5" Dockerfile sketch with a
  pointer to the real Dockerfile + docker.md; update nexum-runtime log
  directive examples
- qa-signoff.md: nexum-engine → nexum-runtime in crate-dependency row
- adr/0007: add errata noting the nexum-engine → nexum-runtime rename
  and the [patch.crates-io] retirement now that cowprotocol 0.1.0 is
  on crates.io

* docs: fix the sweep's own stale claims - cowprotocol override, production.md 3.1

Review catches on the stale-reference sweep itself:

- ADR-0007 errata and sdk.md Versioning both claimed cowprotocol is a
  clean 0.1.0 registry dependency "with no git override". The branch's
  own Cargo.toml pins 0.2.0 WITH an active [patch.crates-io] override
  to nullislabs/cow-rs (pending the hash-only OrderCreationAppData
  constructor release). Both now describe that state and point at the
  patch-block comment as the source of truth.
- production.md 3.1 still shipped the "interim" hand-rolled Dockerfile
  recipe issue #123 flagged; replaced with a pointer to the real root
  Dockerfile and docs/deployment/docker.md.
- 02-modules-events-packaging.md: un-garble the parenthetical the
  earlier rename made self-contradictory ("was module.toml in earlier
  drafts" -> "was nexum.toml").

---------

Co-authored-by: Luiz Gustavo Abou Hatem de Liz <luizgustavoahsc@gmail.com>

* test(event-loop): event ordering and graceful shutdown tests (#353)

* test(event-loop): add ordering and graceful-shutdown tests (#56, #58)

Four new tests covering the two open issues:

Issue #56 — event ordering guarantees:
- `open_block_streams_opens_one_task_per_chain`: structural check that N
  chains → N independent reconnect tasks, documenting the per-chain task
  isolation that prevents stream-A starvation from blocking stream-B events.
- `open_chain_log_streams_opens_one_task_per_subscription`: same structural
  check for chain-log subscriptions.
- `run_delivers_block_and_chain_log_events_without_starvation`: integration
  test over a zero-module supervisor that pre-queues one block and one
  chain-log event, runs the loop, and verifies it drains without hanging —
  the `biased` select delivers both event kinds in the same session.
- `harness_delivers_block_and_chain_log_events_without_starvation`: E2E test
  over the real example module (skipped when wasm not built) that pushes a
  block and a chain-log and waits for both dispatch log lines.

Issue #58 — graceful shutdown completion:
- `run_drains_reconnect_tasks_cleanly_on_shutdown`: verifies `run()` awaits
  `tasks.shutdown()` before returning — reconnect tasks observe a closed
  channel (ReceiverGone) rather than an abort, proving clean teardown.
- `harness_shutdown_after_push_completes_cleanly`: E2E test that calls
  shutdown immediately after a block push; `wait()` returning Ok proves no
  partial dispatch corrupts module state.

* test(event-loop): make the #56/#58 tests falsifiable

Review follow-ups - three of the six tests could not fail for the
property they claimed to verify:

- run() now returns its (blocks, chain_logs) dispatch tally (the same
  numbers the shutdown log line reports). The no-starvation test
  asserts both queued events were drained instead of merely observing
  that run() terminates on the shutdown timer - a broken or reordered
  select arm now leaves a count at 0 and fails.
- New direct test: a reconnect task parked on a dropped receiver exits
  with TaskExit::ReceiverGone on its own, joined on the bare handle.
  The previous test claimed to verify this through TaskSet::shutdown,
  which aborts every handle before joining, so ReceiverGone was never
  exercised; its doc comment now states the abort-then-join contract
  it actually proves.
- harness_shutdown_after_push_completes_cleanly raced shutdown against
  dispatch and accepted either outcome. Replaced with
  harness_shutdown_preserves_completed_dispatch: prove the dispatch
  completed, tear down, then re-read the log record after teardown.
- The harness no-starvation test now queues both event kinds before
  awaiting either, so the biased select genuinely arbitrates two ready
  streams instead of replaying the two pre-existing sequential tests.
- New harness_delivers_blocks_in_push_order: blocks 7,8,9 pushed
  back-to-back must surface in the module's log records in that order -
  issue #56's ordering guarantee, previously untested.
- run()-level tests wrapped in tokio::time::timeout so a shutdown-path
  hang fails in 10 s instead of stalling the suite to the CI job limit.

* test(event-loop): adapt test call sites to the ChainLogSub struct

The train base moved under the branch again: open_chain_log_streams
now takes Vec<ChainLogSub> (resume cursors + max_lookback) instead of
(module, chain, filter) tuples, and the rebase left the two test call
sites building tuples. Construct ChainLogSub with no cursor and no
lookback - these tests exercise stream/task plumbing, not resume.

---------

Co-authored-by: Luiz Gustavo Abou Hatem de Liz <luizgustavoahsc@gmail.com>

* feat(chain): cap JSON-RPC response size before lowering into the guest (#354)

* feat(chain): cap JSON-RPC response size before lowering into the guest (#154)

Adds a configurable `[limits.chain] response_max_bytes` knob (default
1 MiB) that is enforced host-side in `chain::request()` before the
response string is copied into the guest heap. Oversized responses are
rejected with an `InvalidInput` fault, a `WARN` log, and a dedicated
`shepherd_chain_response_capped_total` metric.

* fix(#154): bound batch aggregates, harden config, test the real wiring

Review follow-ups on the chain response cap:

- request_batch: the per-entry cap bounds each body, but the aggregate
  Vec<RpcResult> lowered into guest memory was unbounded - ~64 entries
  just under the cap is ~64 MiB, exactly the guest-heap saturation
  issue #154 exists to prevent, reachable via the block-range chunking
  the issue itself recommends. A running total now converts entries
  past the cap into typed invalid-input results.
- config: renamed [limits.chain].response_max_bytes to
  response_body_max_bytes (u64) for exact symmetry with the
  [limits.http] sibling; a degenerate 0 saturates to 1, matching the
  logs/poison zero handling.
- new harness test drives the cap through the real path - config ->
  ModuleLimits -> HostState -> chain::request - with the price-alert
  module: an over-cap oracle response surfaces to the guest as the
  typed fault and never reaches classify. The existing unit tests only
  covered the free function, so a deleted and_then would have passed
  the suite.
- TOML parse tests for [limits.chain] (absent -> 1 MiB default,
  override, zero saturation), mirroring the HTTP section's tests.
- engine.example.toml documents the new section; warn message dash
  matches house style.

---------

Co-authored-by: Luiz Gustavo Abou Hatem de Liz <luizgustavoahsc@gmail.com>

* wit: carry opaque status bytes so the host stops importing intent (#426)

Drop the nexum:intent use from nexum:host/types: the intent-status
event now carries an opaque versioned status body and an opaque
receipt, so nexum:host is a leaf package. The core bindgen resolves
against wit/nexum-host alone, module worlds pull the intent packages
only with the pool capability, and the intent vocabulary generates in
the adapter bindgen.

The body codec lives in the new nexum-status-body crate: a leading u8
version tag, then the version's borsh payload (v1: lifecycle status,
optional settlement proof, optional fail reason). Unknown tags fail
closed, a body is never empty, and goldens pin the wire form. The pool
router lowers an adapter-reported status through it (settled is
fulfilled plus proof, failed is cancelled plus reason) and keepers
decode via nexum_sdk::status_body.

* wit: rename the intent contract to videre (#428)

wit: rename the intent contract to the pinned videre surface

Rename the pre-release intent packages into the videre namespace and
reshape them to the pinned 0.1.0 surface: nexum:value-flow becomes
videre:value-flow, nexum:intent splits into videre:types and the
two-faced videre:venue (worker client, provider adapter), and
nexum:adapter retires with its venue-adapter world folded into
videre:venue. nexum:host and shepherd:cow are untouched.

The 0.1 shapes are EVM-only and thin: single-asset gives/wants,
auth-scheme {eip1271, eip712}, settlement {chain}, uint amounts
(big-endian, minimal-length), asset {native, erc20{token}}, plain-enum
intent-status, the seven-case venue-error with rate-limit, unsigned-tx
{chain, to, value, data}, and the quotation record. Dropped cases
return as additive 0.2+ variants; quote functions land separately.

Rust follows the wire: PoolRouter is VenueRegistry, AdapterActor is
VenueActor, GuardPolicy/AllowAllGuard collapse into EgressGuard (the
unit guard allows every egress), IntentPool is VenueClient, PoolQuota
is SubmitQuota, and a VenueId newtype keys the registry. Quota
exhaustion now reports rate-limited with the window; adapter traps
project onto unavailable. The module capability is client; goldens
and the conformance mirrors follow the single-asset header.

* wit: normalize every package to 0.1.0 (#429)

wit: normalize every package to a single 0.1.0

Reset nexum:host and shepherd:cow package and use versions to 0.1.0,
the shared baseline every package now semvers from independently.
wasi:* keeps its own 0.2.x. Drop the 0.1-to-0.2 migration guide and
its references.

Closes #371

* wit: add quote to the videre venue faces and the client typestate (#431)

* engine: advisory venue-agnostic acyclicity check (#432)

* ci: add the advisory venue-agnostic check for nexum-runtime

A local command (scripts/check-venue-agnostic.sh, just check-venue-agnostic)
and a non-blocking CI job assert nexum-runtime is venue-agnostic: its crate
graph reaches no videre/intent/venue/cow crate, its sources carry no venue
symbol, and nexum:host resolves as a leaf WIT package. The symbol scan is
red by design until the physical host cut lands; the flip to a blocking
gate is tracked for M2.

* ci: fail the crate-graph check when cargo tree errors

* ci: fail the scan steps when rg errors instead of finding nothing

* ci: scan crate graph with --all-features so feature-gated venue deps cannot slip the guard

* runtime: make the egress-guard checkpoint advisory-only (#433)

A deny verdict at the venue-registry guard seam now logs a would-deny
and lets the submission proceed. The checkpoint does not enforce until
the egress-guard epic installs the real policy pipeline; the seam docs
and the venue-adapter docs say so.

* runtime: charge quota before guard check on submit (#435)

* runtime: charge the caller's quota on a guard-deny

The submission charge moves ahead of the guard verdict at the venue
registry, so a denied egress spends one unit exactly as an accepted
submit does: a module spamming denied egress exhausts its own budget
instead of looping free once the guard turns enforcing. A regression
test pins the rate limit on a repeated-deny loop.

* runtime: tersen the submit charge docs

* venue-test: version fixture files and reject empty vector sets (#437)

* venue-test: version the fixture file format

Fixture files lead with a format version; a reader fails closed on an
unknown tag and refuses an empty vector or golden set, which would
conform vacuously. Published reference fixtures regenerated.

* venue-test: fail conformance checks on an empty fixture set

* style: rustfmt the venue-test version-rejection tests

* runtime: bound the status-watch set with a capped, expiring policy (#438)

The registry's watch list grew without bound and was polled O(N) per
cadence. Each watch now carries an eviction deadline and the set carries
a cap, both operator-configurable via [limits.watch]; expired entries
evict unpolled, at the cap a new watch is refused and logged rather
than a live watch dropped, and every successful non-terminal poll
pushes the deadline a full window out, so only a venue silent for the
whole window expires.

* runtime: grow the extension seam to carry worker and provider roles (#439)

feat: grow the extension seam to carry worker and provider roles

The Extension seam becomes a trait contributing a namespace, a
capability namespace, a linker hook, an optional HostService, and an
optional ProviderKind. HostService is a sync, type-erased service held
on a typed per-namespace HostState.services map built once at boot;
ProviderKind carries a provider linker hook plus the one cold dyn
async_trait install path. The worker boot path is unchanged.

* engine: extract world synthesis into nexum-world (#440)

refactor: extract world synthesis into nexum-world and de-hardcode the known table

The capability table and per-module world synthesis move into a new
plain nexum-world library carrying only the core nexum:host rows.
Per-namespace rows come from the composition root's extensions.toml
registry, parsed and passed in by the macro layer, so no host crate
carries a downstream name; synthesis rejects a row that shadows a core
capability or another registration. WIT packages resolve crate-locally
(wit/deps, then wit/) with an ancestor fallback for the transitional
monorepo layout.

* runtime: extract the supervised host-actor primitive from the venue adapter (#441)

feat: extract the supervised-actor primitive and generic provider boot

* runtime: make the log pipeline pluggable through the components seam (#442)

feat: make the log pipeline pluggable through the components seam

ComponentsBuilder grows a fourth logs slot, defaulting to the new
LogPipelineBuilder (the in-memory pipeline sized from [limits.logs]);
with_logs substitutes a custom builder. The Runtime preset names the
slot as LogsBuilder and the launcher cars carry the parameter through.

* runtime: carry the venue registry through the services map (#443)

runtime: carry the venue registry through the services map and delete the privileged field

The client face resolves the registry from the store's service map
under the videre namespace; no service means every call resolves to
unknown-venue. The boot path seeds the registry into the map until the
videre extension takes it over, provider stores carry an empty map so
the registry never cycles back into a store it owns, and the supervisor
field is gone.

* runtime: let a preset carry extensions and pre-built backends (#444)

feat: let a runtime preset carry extensions and pre-built backends

* runtime: fold venue adapters into the restart and poison sweeps (#445)

A trap in a routed adapter call marks a shared Liveness dead; the
dispatch-time sweeps restart the provider after the module backoff
policy and quarantine a crash-looper per the poison policy. A dead
venue resolves to unavailable, distinct from unknown-venue, and
adapter_alive_count reports live routability. The flaky-venue fixture
drives the trap-to-recovery and poison paths end to end.

* runtime: ride out venue outages against a grace watch deadline (#514)

A status watch refreshes its give-up deadline only when the venue is
reachable: a status poll returned Ok, even if the body then failed to
encode. A resolve failure or an errored poll now rides out against a
grace window rather than burning the deadline, so a venue outage no
longer silently evicts a pending intent's watch mid-recovery.

grace = min(2 * expiry, 24h), overridable via [limits.watch] grace_secs.

Closes #511
Closes #438

* runtime: extract a generic launcher and split the bare and cow binaries (#446)

feat: extract a generic launcher and split the bare and cow binaries

nexum-launch owns the shared CLI, config load, tracing init, and the
preset-driven run; nexum-cli becomes the bare Ext=() engine binary over
CoreRuntime with no cow edge; the cow composition root moves to a new
shepherd binary wiring the cow-api extension as a Runtime preset. The
venue-agnostic guard's crate-graph check now covers the launcher and
the bare binary, and the cow deployment tooling builds shepherd.

* runtime: build the videre-host crate and register the venue platform (#447)

feat: build the videre-host crate and register the venue platform through the seam

Relocate the venue-adapter and client bindgens, the venue registry, the
advisory egress guard, and the venue-adapter provider kind out of
nexum-runtime into a new videre-host extension crate, registered whole
via videre_host::platform(). The runtime grows two generic seams in
exchange: extension-declared subscription kinds with attribute-filtered
extension events through the event loop, and config-derived preset
extensions. nexum-runtime now carries zero venue, intent, or cow
symbols and the venue-agnostic check passes.

* venue: install-time body-version handshake (#448)

feat: refuse mismatched body-schema versions at install

* ci: align the zero-leak check to the charter symbol set (#449)

Scan the runtime sources for the charter set only (nexum:intent,
value-flow, VenueAdapter, synthesize_venue, nexum:adapter, PoolRouter),
dropping the loose word-shape sweep that false-flagged the opaque
extension-map fixtures; keep the crate-graph and WIT-leaf checks, and
state the invariant in the nexum-runtime crate charter. The CI job
stays advisory until the physical repo cut.

* ci: block the zero-leak gate on router-field and wit-namespace scans (#450)

ci: flip the zero-leak gate to blocking with the echo-venue boot oracle

Drop continue-on-error from the venue-agnostic job, add privileged-field
and host-WIT namespace scans to the zero-leak script, and pin the
invariant with two integration tests: the echo venue boots and routes a
worker's submission purely through the generic extension seam, and the
nexum-runtime crate graph names no venue-shaped crate. A missing wasm
fixture hard-fails the boot oracle under CI so the gate cannot skip
itself.

* sdk: make the IntentBody derive no_std (#451)

The derive reaches Vec and ToString through the venue SDK's __private
alloc re-export instead of ::std, so a #![no_std] consumer needs no
extern crate alloc. cow-venue flips to no_std (tests aside) and a
compile-only no-std-probe crate keeps the contract under the workspace
gate.

* sdk: emit bind-macro fault and level conversions as From impls (#452)

sdk: emit the bind-macro fault and level conversions as From impls

Replace the convert_fault / sdk_fault_into_wit / convert_level shim
functions with From impls emitted by the macro (orphan-legal in the
per-cdylib expansion), so module glue converts via ? and Into. No
behaviour change.

* sdk: add an alloy provider seam over the chain host (#453)

* sdk: put an alloy provider seam over the chain host

Add HostTransport, an alloy Transport dispatching JSON-RPC packets
through ChainHost::request, and host.provider(chain) minting a
RootProvider over it, driven by a single-poll block_on. Methods
outside the typed ChainMethod read surface (mirrored guest-side)
fail as -32601 before reaching the host; node errors carry code,
message and decoded revert bytes; host faults surface as typed
transport errors. Chain and ChainId newtypes replace bare u64 ids
at the SDK edge. The hoisted alloy-provider entry drops its native
transport features so the wasm guest build stays transport-free;
the engine and load-gen re-add theirs at the call site.

* sdk: adopt alloy_chains::Chain, drop hand-rolled chain/id.rs

The guest SDK carried Chain/ChainId newtypes duplicating
alloy_chains::Chain, which is already in the guest graph via
alloy-provider. Delete chain/id.rs, re-export alloy_chains::Chain,
and repoint the named-chain test and doc sites. ChainId was unused
outside the module and the WIT edge is a raw u64, so nothing is
lost; Display now renders the chain name where alloy knows it.

* sdk: assert chain block_on resolves synchronously

The host transport is a synchronous WIT import, so provider futures
resolve on the first poll. Replace the unbounded noop-waker loop -
which livelocks and burns the guest fuel budget if a future ever
returns Pending - with a single poll that panics loudly on Pending.
Fail-loud beats a silent spin, and the host-side unit tests (no fuel
backstop) now surface a stuck future instead of hanging.

Addresses #523 (item 1).

* sdk: sort chain module re-exports

alloy_chains sorts before eth_call; keep the pub-use group in
rustfmt order after the ChainId substitution.

* sdk: rename to videre-sdk and add the keeper sweep assembler (#454)

sdk: rename to videre-sdk and add the keeper sweep assembler and typed venue client

Rename nexum-venue-sdk to videre-sdk across the workspace. Add the
generic Keeper::sweep assembler (WatchSet to Gates to poll to Retrier
to Journal) over a shared Sweep outcome resolving the dangling
ConditionalSource::Outcome; the world-neutral primitives stay in
nexum-sdk. Thread a VenueId newtype through the client seam, add the
owned VenueFault mirror (retry-after-ms preserved) behind ClientError,
mark the client-facing enums non-exhaustive, and seal IntentBody to
its derive.

* sdk: guest seams and mocks for identity, messaging and remote-store (#455)

sdk: add guest seams and mocks for identity, messaging and remote-store

* sdk: split macros into module and venue crates (#456)

sdk: split the macro crate into nexum-module-macros and videre-macros

#[module] stays L1 in nexum-module-macros; #[venue] and derive(IntentBody)
move to videre-macros with the venue-world synthesis. nexum-sdk re-exports
module from nexum-module-macros; videre-sdk re-exports venue and IntentBody
from videre-macros. No macro behaviour change.

* local-store: add contains, len and count metadata queries (#457)

Answer existence, value size and key cardinality without transferring
the payload across the wasm boundary. The SDK trait ships default
bodies over get/list-keys so backends opt into a cheaper path; the
redb backend answers contains and len from the entry in place and
count from a bounded range scan.

* sdk: make the venue macro the single blessed authoring path (#458)

#[videre_sdk::venue] now takes the impl VenueAdapter block itself:
it asserts the manifest kind, keeps the manifest-derived world
narrowing, remaps the type interfaces onto the SDK bindings for type
identity, and expands to the demoted internal export codegen.
export_venue_adapter! is no longer public API; echo-venue and
flaky-venue author through the trait, the golden bridges are gone,
and the cow body codec is held to the kit's typed vectors.

* sdk: rename venue conformance kit to videre-test (#459)

sdk: rename the conformance kit to videre-test and align mock-grant fidelity

* sdk: add the keeper macro and typed venue client (#460)

sdk: add the keeper macro and the typed venue client

The keeper author gets the mirror of #[venue]: #[videre_sdk::keeper] on
a handler impl emits the per-cdylib module world (requiring the client
capability), remaps the videre interfaces onto the SDK's shared shims,
completes async handlers on the synchronous guest boundary, and folds
ClientError into the wire fault so ? works in handlers.

VenueClient<V> replaces the stringly byte-level trait: a Venue marker
carries the VenueId and body schema, calls encode through IntentBody,
and the byte seam under it (VenueTransport, native AFIT) dispatches
statically with zero boxing. HostVenues binds the seam to the module's
own videre:venue/client import; the SDK bindgen collapses to one
import-only world so linking it never obliges a module to export the
adapter face. CowClient becomes the VenueClient<CowVenue> alias, and
the echo-keeper example drives echo-venue end to end (quote, submit,
status, cancel), proven by a platform e2e test.

* sdk: land the alloy-grade dx polish cluster (#461)

sdk: land the alloy-grade DX polish cluster

Add an Order typestate builder over the 12-field CoW OrderBody with
SellToken and BuyToken newtypes, so a keeper cannot swap sides or skip
a required field: sell/buy entry fixes the kind, the counter-side limit
and expiry are compile-time required states, and the optionals default.

Seal the extension traits: Host and HostFault (nexum-sdk), RuntimeTypes
and Runtime (nexum-runtime), and VenueTransport (videre-sdk, the pool
seam's successor) each gain a doc-hidden sealing marker an implementor
opts into, so the surfaces can grow without silent downstream breakage.

Apply #[non_exhaustive] uniformly across the public error and label
enums, adding wildcard folds at the cross-crate match sites.

Emit the mirrored vocabularies from single-source consts in
nexum-world: the capability name consts and CORE_IFACES feed both the
world-synthesis table and the runtime registry, and the fault-label
consts feed the runtime's label projection with the SDK's strum labels
pinned to them in test.

Fix the operator logs that debug-formatted venue faults: the SDK
Fault's rate-limited Display now carries the retry-after hint, the
runtime's fault_message keeps it, and the venue registry renders wire
errors through message projections instead of the bindgen's debug
Display, so rate-limited retry-after-ms survives to the log line. The
golden-bridge sweep found no residue.

* runtime: make the module event type extensible via a generic custom channel (#520)

* runtime: extract intent-status behind a generic custom event

The core nexum:host `event` variant dropped `intent-status(intent-status-update)` for a generic `custom(custom-event)` channel, so the venue-agnostic core no longer names venue/receipt at the WIT boundary and a second extension can add its own event kind with no core WIT change.

The intent-status payload moved into videre's own WIT (`videre:types` intent-status-update) and rides the custom channel: videre-host emits `custom` with kind "intent-status" and the borsh envelope as payload, and `videre_sdk::event::intent_status_update` recovers it typed by matching the kind and decoding. nexum-status-body gained the shared kind const and the envelope codec while the status body stays the payload's inner encoding.

The core `#[module]` macro swapped its `on_intent_status` handler for a generic `on_custom`; the `#[keeper]` macro keeps the typed `on_intent_status`, decoding it from the custom event. check-venue-agnostic gained a WIT-vocabulary gate that fails on venue/receipt/intent-status in nexum:host.

Closes #518

* sdk: move the venue status codec into the videre layer

The status-body codec is wholly venue-domain: the IntentStatus lifecycle at the venue, the venue-reported FailReason, the opaque StatusBody bytes, and the {venue, receipt, status} IntentStatusUpdate envelope keyed by INTENT_STATUS_KIND.

Rename the crate nexum-status-body to videre-status-body so it sits in the videre (L2) layer, and stop the generic guest SDK re-exporting it.

nexum-sdk no longer depends on the codec nor re-exports it as status_body, so venue vocabulary no longer leaks into the venue-agnostic guest SDK.

The guest-facing surface now lives on videre-sdk, which depends on videre-status-body and re-exports it as videre_sdk::status_body; the event decode path and the keeper macro reach it there.

Venue-consuming example modules gain a videre-sdk dependency and decode through videre_sdk::status_body.

Extend check-venue-agnostic.sh with a nexum-sdk scan: its crate graph must reach no videre or venue crate, and its sources must name none of the intent-status codec vocabulary.

The wire form and behaviour are unchanged; the intent-status E2E tests pass unaltered.

* sdk: drop the dead intent-status wit record and generalise the example

The videre:types intent-status-update record was declaration-only:
wit-bindgen prunes it (no world function references it, the payload
rides as opaque bytes), so it generated no binding; the wire contract
lives in videre-status-body's Rust type.

The example module decoded intent-status in on_custom despite never
submitting an intent, pulling videre-sdk in gratuitously. It now logs
the raw custom event generically and drops the videre-sdk dependency.
echo-client keeps its decode: it submits through videre:venue/client.

* macros: hoist duplicated helpers into nexum-world (#550)

`is_plain_type`, `manifest_dir` and `resolve_wit_packages` were
byte-identical in `nexum-module-macros` and `videre-macros`. Both
already depend on `nexum-world`, where the substantive package
resolution lives, so the helpers move there: `manifest_dir`, a
`manifest_wit_packages` wrapper returning the strings
`wit_bindgen::generate!` takes, and `is_plain_type` behind a `macros`
feature, so non-macro consumers take `nexum-world` without `syn`.

Also add `-p videre-macros` to the `docs/sdk.md` doc-lint invocation;
it passes with `-D warnings -D missing-docs`.

Closes #542

* world: enums over const strings for the vocabularies (#551)

`caps` becomes `Cap` with a hand-written `const fn as_str`, so the
`CORE` table, `core_iface_count` and `CORE_IFACES` still evaluate in
const context; `Capability::name` is now typed. `fault_labels` becomes
`FaultLabel` with `IntoStaticStr` for the label and `VariantNames` in
place of the hand-maintained `ALL` array. `EnumString` gives both a
fail-closed parse; new tests pin the hand-written accessor to the
derived vocabulary.

Closes #547

* world: single-source the ChainMethod read surface (#552)

ChainMethod was declared byte-identically in nexum-sdk and
nexum-runtime, with #[non_exhaustive] added to each by hand in #461.
Fold it into nexum-world beside Cap and FaultLabel, with the derive
superset, and re-export from both sides so the guest allowlist and the
host dispatch table cannot drift. Consumer paths are unchanged:
nexum_sdk::chain::ChainMethod and
nexum_runtime::host::component::ChainMethod still resolve.

nexum-world becomes a normal dependency of nexum-sdk, so guests link
toml and the world-synthesis code they never call. No feature gate.

Closes #523

* flake: add cargo-nextest to the dev shell (#554)

CI runs the suite with cargo nextest, but the dev shell shipped no
nextest at all, so local gating silently diverged onto cargo test and
workflow tooling had to nest a `nix shell nixpkgs#cargo-nextest` call to
match CI. Pin it here and report its version in the banner.

Unlike sccache, which is deliberately not bundled because a client must
version-match the host's server, nextest is a standalone runner with no
such constraint.

* cow: cleave the cow venue from the composable-cow keeper (#462)

The venue crate is the orderbook alone. ComposableBody and the
structured poll seam (Verdict, LegacyRevertAdapter, IConditionalOrder)
move to the new composable-cow keeper crate, the Composable variant
drops from the venue body, the goldens shed the composable vectors,
and a blocking CI gate keeps composable symbols out of
crates/cow-venue.

* wit: own the cow event ABIs at the bundle layer (#463)

Pin ConditionalOrderCreated and EthFlow OrderPlacement in a new
shepherd:cow/cow-events package of record; keepers resolve topic-0s
from the mirrored shepherd-sdk constants, parity-tested against the
WIT, the sol! decoders and each module.toml. Mark the legacy cow-api
extension surface retiring and gate the generic WIT packages against
any shepherd:cow reference.

* composable-cow: type ComposableBody with alloy primitives (#557)

cow: type ComposableBody with alloy primitives

Retype the `ConditionalOrderParams` tuple with the types that describe
it: `handler` becomes `Address`, `salt` becomes `B256` and
`static_input` becomes `Bytes`, so the body carries EVM address
semantics instead of raw arrays.

No borsh adapter is needed. alloy-primitives 1.6 ships a `borsh`
feature (absent at 1.3, when the issue was raised), enabled here on the
crate's existing non-optional dependency. Its impls write `Address` and
`B256` as bare bytes and `Bytes` length-prefixed, so the wire is
byte-identical to the arrays it replaces; a test pins that layout
alongside the round-trips.

Closes #555

* status-body: version-tag the intent-status envelope (#556)

`IntentStatusUpdate::encode` now leads with its own version tag and
`decode` refuses an unknown one, mirroring the inner `StatusBody`
codec. The envelope tag frames venue/receipt/status, the body tag
frames the status payload, and the two version independently.

`EnvelopeError` becomes the same typed empty/unknown-version/malformed
enum as `Deco…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

wit: normalize every package to a single 0.1.0

2 participants