refactor(#33)!: replace terrors::OneOf with domain error enums#74
Merged
Conversation
3 tasks
Replace OneOf<(..)> with per-call-site thiserror enums (MatterBuildError, VerificationError), collapse single-element indexer OneOfs to bare types, fix serder ParsingError->ValidationError string-jamming bug, drop terrors dep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… a stringified ValidationError Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…orts Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… unions, no terrors) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…flake check gate The new SerderError::UnparseablePrimitive variant uses the valid English spelling 'unparseable', which the typos default dictionary corrects to 'unparsable'. Allowlist the deliberate spelling in _typos.toml and fix an unrelated 'mis-routed' -> 'misrouted' comment typo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
deep-fuzz's matter_from_qb64 target found that from_qualified_base64 panics with 'range end index N out of range for slice of length 0' on malformed qb64 whose decoded buffer is shorter than the code's declared lead size (crash input: 5BAA). Both lead-byte slices (ps!=0 and ps==0 branches) indexed buf without a bounds check. Replace the panicking index with .get(), returning MatterBuildError::Validation(ValidationError::StructuralIntegrityError) when the buffer is too short. Parsing untrusted bytes must never panic (CLAUDE.md). Adds a regression unit test and commits the crash input as a fuzz corpus seed (guarded by the cesr-fuzz-replay gate). Note: the existing qb64 never-panics proptest did not generate this structural case — fuzzing complements proptests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78f097d to
c44c8db
Compare
Merged
This was referenced Jul 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #33 (P2.3 · Error ergonomics). Removes the
terrors::OneOf<(...)>error-union layer across the crate in favour of purpose-builtthiserrorenums, and drops theterrorsdependency. The unions were only ever 1–2 variants, soOneOfcost consumers a runtime downcast (.take::/.narrow::) for no matchability benefit.Breaking (MINOR under 0.x) — return types change on public APIs; see CHANGELOG.
What changed
MatterBuildError { Parsing(#[from] ParsingError), Validation(#[from] ValidationError) }— matter builder (from_qualified_base64,from_qualified_base2,build) now returns this instead ofOneOf<(ParsingError, ValidationError)>.VerificationError { Signature(#[from] SignatureError), CodeMismatch(#[from] CodeMismatchError) }—crypto::verifyreturn type.OneOf<(E,)>collapsed to the bareIndexerParseError/IndexerValidationError.map_qb64_errorpreviously string-jammed aParsingErrorintoValidationError::UnknownMatterCode(err.to_string())(a parse failure mislabeled as validation, structured error erased). Now routes each domain correctly via a newSerderError::UnparseablePrimitive { field, source: ParsingError }variant. TDD regression test pins both routing arms.stream::parseoff terrors; removed anunreachable!()panic (banned in production) in favour of a totalmatch.terrorsremoved fromCargo.toml,Cargo.lock, andfuzz/Cargo.lock.#[from]keeps?propagation working, so call sites were untouched. No#[non_exhaustive]— pre-1.0, a new variant is an intentional MINOR bump, and exhaustive matching gives tag-pinning consumers a compile error exactly when they upgrade into a new failure mode.Consumer migration
Replace
err.take::<T>()/err.narrow::<T, _>()with a normalmatchon the new enums (or the bare indexer error types).Test plan
nix flake checkgreen (clippy god-level, fmt, taplo, audit, deny, nextest across feature combos, doctest, wasm, no_std, typos, fuzz-replay) — run independently, EXIT=0git grep terrors|OneOfclean across code/config🤖 Generated with Claude Code
Added after review (fuzz-found panic fix)
While fixing the
deep-fuzzCI workflow (#75), the now-working fuzzer immediately caught a pre-existing panic in this exact file:MatterBuilder::from_qualified_base64panicked (range end index N out of range for slice of length 0) on malformed qb64 whose decoded buffer is shorter than the code's declared lead size (input5BAA). Fixed here (same file/error path) by bounds-checking both lead-byte slices via.get(), returningMatterBuildError::Validation(ValidationError::StructuralIntegrityError). Includes a TDD regression test + a committed fuzz corpus seed. This is a panic-on-untrusted-input / DoS fix (CLAUDE.md mandatory rule).