Skip to content

feat(runtime): config-mapped poison policy, retire the test setter#201

Open
mfw78 wants to merge 1 commit into
feat/m0-wasi-clocksfrom
feat/m0-poison-policy-config
Open

feat(runtime): config-mapped poison policy, retire the test setter#201
mfw78 wants to merge 1 commit into
feat/m0-wasi-clocksfrom
feat/m0-poison-policy-config

Conversation

@mfw78

@mfw78 mfw78 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #197; part of the epic #80 train, merges in stack order.
Poison policy thresholds move from hardcoded constants into EngineConfig, and the cfg(test) back-door setter that let tests bypass the resolver is retired.

Changes

ModuleLimits gains a poison: PoisonLimitsSection field (max_failures, window_secs, both optional, #[serde(default)]) alongside a poison() resolver that follows the same saturating pattern as logs(), defaulting to the existing POISON_MAX_FAILURES / POISON_WINDOW constants when the section is absent.
window_secs uses seconds rather than milliseconds since a ten-minute quarantine window has no need for sub-second precision, unlike the http timeout knobs.
Both max_failures and window_secs saturate to a minimum of one, matching the degenerate-zero handling already used for [limits.logs], since a zero threshold or zero window would quarantine on the very first trap.
Both supervisor::boot and supervisor::boot_single now read poison_policy via limits.poison() instead of PoisonPolicy::default().
The #[cfg(test)] with_poison_policy setter is deleted; the two integration tests that relied on it (poison_pill_quarantines_module_after_threshold, multi_chain_poisoned_module_does_not_affect_other_chains) now inject tight thresholds through ModuleLimits / EngineConfig instead.
engine.example.toml gains a commented [limits.poison] block documenting the two knobs.

Test plan

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --all-features (389 passed, 0 failed), including both e2e poison tests executing against real in-tree wasms and the three new engine_config::tests::poison_limits_* cases (default, override, zero-saturation)

Notes for reviewer

The resolver returns the runtime's own PoisonPolicy type rather than a parallel engine_config-local struct, since the poison threshold already has one canonical home in runtime::poison_policy and a second copy would just be duplication; the import is same-crate and introduces no cycle.
Production behaviour is unchanged: with no [limits.poison] section present, poison() resolves to the same constants PoisonPolicy::default() used before, so both boot paths dispatch identically to main.
One pre-existing doc nit (an off-by-one "more than max_failures" wording in the operator-facing example, inherited rather than introduced here) is left as a follow-up rather than folded into this change.

AI Assistance: Claude Code (Opus 4.8 implementation and adversarial review, Sonnet 5 PR authoring) used for the full change.

Map the poison-pill thresholds into EngineConfig under [limits.poison],
resolved via ModuleLimits::poison the same saturating way as the
[limits.http] and [limits.logs] knobs: max_failures and window_secs both
saturate up to 1, omitted values fall back to the production defaults of
5 traps in 600 s. The supervisor boot paths read the resolved policy from
config instead of hard-coding it, and the cfg(test) with_poison_policy
setter is deleted; the integration tests now inject tight thresholds
through their ModuleLimits.
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