Skip to content

feat!: harden wire-boundary enums with #[non_exhaustive] + pin tests#66

Merged
sanity merged 1 commit intomainfrom
feat-non-exhaustive-wire-enums
Apr 14, 2026
Merged

feat!: harden wire-boundary enums with #[non_exhaustive] + pin tests#66
sanity merged 1 commit intomainfrom
feat-non-exhaustive-wire-enums

Conversation

@sanity
Copy link
Copy Markdown
Contributor

@sanity sanity commented Apr 14, 2026

Problem

freenet-stdlib repeatedly hits source-level breakage cycles when wire-boundary enums grow new variants (most recently #65 / freenet/freenet-core#3860 for MessageOrigin, the v0.2.11 streaming incident for HostResponse, etc.). Each break forces every downstream consumer that pattern-matches exhaustively to add a wildcard arm — a coordination tax that this PR pays once for five enums at once so future variants are non-events at the source level.

A separate failure mode: a refactor that innocently reorders enum variants would silently rewrite the bincode wire tags and break every deployed consumer without a compile error. There is no regression guard for that today on any enum except MessageOrigin (added in 0.5.0).

Approach

Add #[non_exhaustive] to the five wire-boundary enums that don't already have it:

  • delegate_interface::InboundDelegateMsg (companion to OutboundDelegateMsg)
  • contract_interface::update::UpdateData
  • delegate_interface::DelegateError
  • contract_interface::error::ContractError
  • versioning::APIVersion

Add wire-format pin tests for two of them mirroring the MessageOrigin pattern from 0.5.0:

  • inbound_delegate_msg_wire_format_is_stable — asserts ApplicationMessage(..) stays at variant tag 0 and round-trips
  • update_data_wire_format_is_stable — asserts State(..) at tag 0 and Delta(..) at tag 1 and round-trips both

Compatibility

Source-level breaking, wire-compatible:

  • #[non_exhaustive] is a source attribute only. No effect on bincode discriminants, serde impls, byte layout, or wire format.
  • Deployed contract/delegate WASM compiled against any earlier 0.x stdlib continues to deserialize identically. The wire format is unchanged byte-for-byte (which is what the new pin tests now permanently lock down).
  • Downstream Rust code that pattern-matches these enums exhaustively must add a wildcard arm to compile against 0.6. Per investigation, the only current consumer of stdlib 0.5.0 is freenet-core itself, which is updated in the companion fix: attest delegate-to-delegate caller via MessageOrigin::Delegate freenet-core#3865. River and delta are pinned to 0.3.5 and unaffected until they voluntarily bump.

Bumps 0.5.0 → 0.6.0 (minor-breaking per 0.x semver).

Testing

  • Existing webapp_origin_wire_format_is_stable and delegate_origin_wire_format_is_stable pin tests continue to pass.
  • New: inbound_delegate_msg_wire_format_is_stable, update_data_wire_format_is_stable.
  • cargo test full sweep clean.
  • cargo clippy --all-targets --all-features clean (only the pre-existing __frnt__fill_buffer snake-case warning unrelated to this PR).

Release coordination

This unblocks the second hardening pass on freenet/freenet-core#3865 (which folds the dep bump and any required wildcard arms into the same PR rollout, per the "fold-into-#3865" decision).

[AI-assisted - Claude]

Addresses the "cheap win" from the freenet-core#3860 hardening discussion:
future variants on these five wire-boundary enums should NOT require a
downstream source break, and a refactor that reorders variants should
fail loudly at test time rather than silently rewriting the wire format
for deployed contracts and delegates.

Adds `#[non_exhaustive]` to:

- `delegate_interface::InboundDelegateMsg` — pair of the already-
  `non_exhaustive` `OutboundDelegateMsg`.
- `contract_interface::update::UpdateData` — contract update wire type.
- `delegate_interface::DelegateError` — returned from delegate process().
- `contract_interface::error::ContractError` — contract validation errors.
- `versioning::APIVersion` — historical panic site (fixed in 0.1.8, this
  adds belt-and-braces protection).

All five are source-level breaking for downstream code that matches
exhaustively; they are NOT wire-format breaking. `#[non_exhaustive]` is
a source attribute only. Bincode discriminants, serde impls, and byte
layouts are unchanged, so deployed contract/delegate WASM compiled
against any earlier 0.x stdlib continues to deserialize identically.

Adds wire-format pin tests mirroring the style introduced for
`MessageOrigin` in 0.5.0:

- `inbound_delegate_msg_wire_format_is_stable` — asserts
  `ApplicationMessage(..)` stays at variant tag 0 and round-trips.
- `update_data_wire_format_is_stable` — asserts `State(..)` at tag 0
  and `Delta(..)` at tag 1 and round-trips both.

These pin tests are the regression guard for refactors that innocently
reorder variants: without them, a renamed/reordered variant would
silently remap the wire tags and break every deployed consumer without
a compile error.

Bumps stdlib 0.5.0 → 0.6.0 (minor breaking per 0.x semver).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sanity sanity merged commit 763e377 into main Apr 14, 2026
8 checks passed
@sanity sanity deleted the feat-non-exhaustive-wire-enums branch April 14, 2026 01:20
sanity added a commit to freenet/freenet-core that referenced this pull request Apr 14, 2026
…stive arms

stdlib 0.6.0 (freenet/freenet-stdlib#66) adds `#[non_exhaustive]` to five
wire-boundary enums (`InboundDelegateMsg`, `UpdateData`, `DelegateError`,
`ContractError`, `APIVersion`) and pins their bincode wire formats with
new test coverage. The bump is source-level breaking but wire-compatible:
deployed contracts and delegates compiled against any earlier 0.x stdlib
continue to deserialize identically because bincode discriminants for
existing variants are byte-identical (now permanently locked by the new
pin tests).

This commit fixes every freenet-core / fdev / test-fixture match site
that the compiler flagged after the bump:

- `wasm_runtime/delegate.rs` (2 sites): `inbound_msg_name` lookup gets
  an "Unknown" sentinel; the `for msg in inbound` loop forwards future
  variants through the same generic exec path so a delegate built
  against a newer stdlib can still receive them. Annotated
  `#[allow(clippy::wildcard_enum_match_arm)]` with a comment because
  expanding the wildcard into `pat | _` would defeat the non_exhaustive
  safety net.
- `client_events.rs` (1 site): UPDATE conversion to 'static rejects
  unknown UpdateData variants with an explicit `Error::Node` (forwards
  a "rebuild freenet-core against the stdlib emitting this variant"
  diagnostic to the client) rather than silently dropping the payload.
- `contract.rs` (2 sites): delegate UPDATE path falls into the existing
  "unsupported variant" warn+reject branch; the `ContractHandlerEvent::
  UpdateQuery` path stays `unreachable!()` because the producer at the
  edge is now responsible for rejecting unknowns.
- `node/request_router.rs` (1 site): hash dedup uses sentinel
  discriminant 255 for unknown variants so dedup keys stay distinct.
- `operations/update.rs` (1 site): state-extraction returns `None` for
  unknowns (treated as "no state to extract").
- `contract/executor/mock_wasm_runtime.rs` (1 site): mock-only path
  ignores unknowns for the merge.
- `tests/test-contract-mock-aligned/src/lib.rs` (1 site): test fixture
  ignores unknowns.
- `crates/fdev/src/commands.rs` (2 sites): `extract_update_bytes`
  returns an empty slice for unknown variants; `describe_update_variant`
  returns "unknown".

Test fixture lockfile bump: `tests/test-delegate-messaging/Cargo.toml`
already pinned 0.5.0 (the published version at the time of the prior
commit); now bumped to 0.6.0 to match.

Adds `.claude/rules/git-workflow.md` guidance: stdlib-first release
policy (don't use [patch.crates-io] git overrides for cross-repo
coordination — publish stdlib first, then open the consumer PR) and a
walk-through of the non_exhaustive bump pattern so the next person who
adds a wire-boundary variant doesn't have to rediscover the cheap-win
shape from scratch.

Verified locally: full freenet-core lib test suite passes (2245 tests,
4 more than baseline — the `resolve_message_origin_tests` precedence
suite from the prior commit), `cargo clippy --locked -- -D warnings`
clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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