Skip to content

fix(runtime): whole-train hardening for the typed-fault stack#219

Open
mfw78 wants to merge 2 commits into
fix/fault-train-followupsfrom
fix/fault-train-hardening
Open

fix(runtime): whole-train hardening for the typed-fault stack#219
mfw78 wants to merge 2 commits into
fix/fault-train-followupsfrom
fix/fault-train-hardening

Conversation

@mfw78

@mfw78 mfw78 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes out the typed-fault train (epic #206) with a red-team pass over the whole stack, from the unified host-error envelope's replacement by the per-interface fault vocabulary through to the WASI-idiom end state on this tip.
The verdict: the envelope is fully dead (zero host-error/HostError/domain/kind/code dispatch remaining anywhere in crates/, modules/, or wit/), each interface's error type honestly matches what it can fail with, and the chain revert and cow-rejection traces are lossless end to end, except one hole this PR closes.

Changes

  • Restore the orderbook rejection's data payload. The retired host-error envelope round-tripped the whole ApiError (including data, e.g. a minimum-fee quote) but the typed order-rejection record dropped it, so orderbook_error_to_wit silently discarded information the old flow preserved. Adds data: option<string> to the WIT record, populates it host-side from the parsed envelope, and threads it through the SDK mirror and the wit-bindgen conversion. This is a pre-1.0 wire break, sanctioned, and the affected wasms are rebuilt.
  • Drop the stale ApiError prelude re-export in shepherd-sdk/src/prelude.rs. Nothing guest-side decodes ApiError any more since the host parses the envelope once and the classifier dispatches on the typed rejection; the re-export's comment described a flow that no longer exists.
  • Correct four doc WIT-snippet lines that had gone stale describing the pre-fix shape.

Test plan

  • just ci (fmt, clippy -D warnings, rustdoc -D warnings, all 11 module wasms, full workspace tests) exits clean on the fixed tree.
  • Module wasms built in-tree first so the supervisor/cow_boot end-to-end tests exercised the real artefacts, not stubs: e2e_twap_monitor, e2e_stop_loss, e2e_ethflow, e2e_http_probe, and the rest all pass, zero SKIP lines, zero failures.
  • New host-side test asserts the data round-trip through orderbook_error_to_wit.
  • A wiremock test already on the train's tip proves the upstream JSON-string revert data decodes to raw bytes exactly once, host-side, before reaching guest code.

Notes

Broader red-team findings that came back clean, for the record: exhaustive macro conversions in both directions (the only wildcard arm sits on the #[non_exhaustive] SDK side and folds to Internal with Display detail preserved, not a re-creation of the envelope), supervisor telemetry now emits stable fault_label/fault_message snake_case metric labels, and the epic-80 harness (MockChain/MockLocalStore/MockCowApi) is reshaped to the typed errors and still exercises the module tests correctly.

Deferred documentation

  • docs/adr/0011-per-interface-typed-errors.md line 11 describes the cow rejection shape as {errorType, description}; it should read {errorType, description, data} after this fix. Left for the prose pass.
  • The cow rejection flow narrative in docs/07-rpc-namespace-design.md and the migration guide could call out the data payload explicitly; the WIT-snippet lines themselves are already corrected in this PR.

Follow-ups (not implemented here)

  • A shared impl From<&CowApiError> for RetryAction at the SDK level: twap-monitor and stop-loss currently duplicate the identical "Rejected classifies, everything else retries on next block" match.
  • Populate rate-limit.retry-after-ms: the 429 mapping in transport_fault always sends None; capturing the upstream Retry-After header needs alloy transport-layer support.
  • Replace the timeout string-sniff in transport_fault (msg.contains("timed out")) with a host-side request deadline or a typed alloy timeout signal.
  • fault_message renders the fixed phrase "rate limited" for RateLimited without the retry_after_ms detail when present.
  • request-batch's outer chain-error is structurally dead on the reference host; either flatten the WIT to list<rpc-result> or record in ADR-0011 why the escape hatch stays for other hosts.

AI Assistance: Claude Code (Fable 5 red-team and fixes, Sonnet 5 PR authoring) used for the full change.

mfw78 added 2 commits July 6, 2026 04:15
The orderbook rejection envelope has an optional structured data field
(e.g. a minimum-fee quote) that the retired host-error envelope
round-tripped in full but the typed order-rejection dropped. Add
data: option<string> to the record, populate it host-side from the
parsed envelope, and thread it through the SDK mirror and the
wit-bindgen conversion so no rejection information is lost end to end.
Nothing guest-side decodes the orderbook ApiError JSON any more: the
host parses the envelope once into the typed order-rejection and the
classifier dispatches on that. The re-export and its comment described
the retired flow, so remove them from the prelude surface.
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.

1 participant