feat(errors): the one-enum error family with altitude discipline
- src/error.rs: thiserror Error enum with the five ADR-012 \u00a76.5 variants (Missing, Verification, Io(String), GcRefuse, KeyInvalid); doc comments carry store-api.md \u00a7Error model's semantics and the one-enum/two-altitude discipline (trait's effective members are Missing/Verification/Io/KeyInvalid; facade re-exposes with GcRefuse) - no #[non_exhaustive] per the ADR-pinned variant set; Error derives Debug/PartialEq/Eq - lib.rs re-exports Error - unit tests: variant construction, the single-enum altitude shape, and the Missing-is-never-get/stat-absence doc contract (facade behavioral enforcement lands in wave 2) - tasks/core-error-model.md: status completed; Notes + Summary filled Verified: cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check
This commit is contained in:
1 parent
24d1c68343
commit
fc5ab57d18
3 files changed
+166
-10
No files matched your search
+130
-3
@@ -1,3 +1,130 @@
|
|||||||
//! The error family (ADR-012 §6.5): one enum with members at two
|
//! The error family (store-api.md §Error model; ADR-012 §6.5, §1).
|
||||||
//! altitudes — the trait's effective members plus the facade's
|
//!
|
||||||
//! GC-refusal and key-validation variants.
|
//! **One enum, both altitudes.** This is a *single* enum serving both
|
||||||
|
//! altitudes of the crate's surface. The [`Backend`][crate::backend]
|
||||||
|
//! trait's methods return this family but its *effective* members are
|
||||||
|
//! [`Missing`], [`Verification`], [`Io`], and [`KeyInvalid`] — at the
|
||||||
|
//! trait boundary, keys arrive opaque (so `KeyInvalid` never fires
|
||||||
|
//! there) and GC delete-arbitration answers flow up as data, not as
|
||||||
|
//! errors (so `GcRefuse` never fires below the facade). The facade's
|
||||||
|
//! public surface re-exposes this same family with [`GcRefuse`] added:
|
||||||
|
//! the facade decides and owns the refusal. The altitude discipline is
|
||||||
|
//! carried by doc comments and construction discipline, not by two
|
||||||
|
//! separate types (named design point audited at the review gate;
|
||||||
|
//! store-api.md §Error model).
|
||||||
|
//!
|
||||||
|
//! The variant set is ADR-pinned (ADR-012 §6.5's family statement);
|
||||||
|
//! new failure families are new ADRs, so `#[non_exhaustive]` is
|
||||||
|
//! deliberately not applied.
|
||||||
|
|
||||||
|
/// The store error family — one enum with members at two altitudes
|
||||||
|
/// (store-api.md §Error model; ADR-012 §6.5, §1).
|
||||||
|
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Key absent, on ops that signal absence via `Err`:
|
||||||
|
/// `read_range` and `delete` (ADR-012 §6.5 ruling 5;
|
||||||
|
/// store-api.md §Error model).
|
||||||
|
///
|
||||||
|
/// A miss on `get` and `stat` is a value, not an error — they
|
||||||
|
/// signal absence with `None` and never carry `Missing`
|
||||||
|
/// (behavioral enforcement lands with the facade in wave 2; this
|
||||||
|
/// doc comment states the contract here).
|
||||||
|
#[error("key missing (read_range/delete: get/stat signal absence with None)")]
|
||||||
|
Missing,
|
||||||
|
|
||||||
|
/// Put/get hash-check failure: content does not match the key
|
||||||
|
/// under the canonical derivation (store-api.md §Error model).
|
||||||
|
#[error("verification failed: content does not match key")]
|
||||||
|
Verification,
|
||||||
|
|
||||||
|
/// Backend media failure (store-api.md §Error model), covering
|
||||||
|
/// cursor and staged-put I/O errors surfaced through the same
|
||||||
|
/// trait error family — stringly because `std::io::Error` is not
|
||||||
|
/// stable across versions.
|
||||||
|
#[error("io: {0}")]
|
||||||
|
Io(String),
|
||||||
|
|
||||||
|
/// Direct delete refused: the key is protected — pinned,
|
||||||
|
/// registered-source-live, or protect-callback-named — and
|
||||||
|
/// nothing was deleted (ADR-005's delete-window arbitration at
|
||||||
|
/// delete time; ADR-012 §1/§4).
|
||||||
|
///
|
||||||
|
/// Never fires below the facade: arbitration answers flow up as
|
||||||
|
/// data, and the facade decides and owns the refusal (one enum,
|
||||||
|
/// both altitudes; store-api.md §Error model). Named `GcRefuse`
|
||||||
|
/// per ADR-012 §1 — there is no sweep-abort error.
|
||||||
|
#[error("direct delete refused: key protected (nothing deleted)")]
|
||||||
|
GcRefuse,
|
||||||
|
|
||||||
|
/// Malformed key bytes at the boundary — truncated, unknown
|
||||||
|
/// algorithm (ADR-002/hashing): rejects, never guesses
|
||||||
|
/// (store-api.md §Error model). KeyInvalid never fires at the
|
||||||
|
/// [`Backend`][crate::backend] trait boundary, where keys arrive
|
||||||
|
/// opaque (one enum, both altitudes).
|
||||||
|
#[error("invalid key bytes: truncated, unknown algorithm — rejects, never guesses")]
|
||||||
|
KeyInvalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Error;
|
||||||
|
|
||||||
|
/// Each distinguished variant constructs (store-api.md §Error
|
||||||
|
/// model: the five failure families, one enum).
|
||||||
|
#[test]
|
||||||
|
fn each_variant_constructs() {
|
||||||
|
let variants: [Error; 5] = [
|
||||||
|
Error::Missing,
|
||||||
|
Error::Verification,
|
||||||
|
Error::Io("cursor closed mid-read".to_owned()),
|
||||||
|
Error::GcRefuse,
|
||||||
|
Error::KeyInvalid,
|
||||||
|
];
|
||||||
|
let expected = [
|
||||||
|
"key missing",
|
||||||
|
"verification failed",
|
||||||
|
"io: cursor closed mid-read",
|
||||||
|
"direct delete refused",
|
||||||
|
"invalid key bytes",
|
||||||
|
];
|
||||||
|
for (err, text) in variants.iter().zip(expected) {
|
||||||
|
assert!(err.to_string().contains(text), "{err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The named design point (store-api.md §Error model): one enum,
|
||||||
|
/// both altitudes — the trait boundary's effective members plus
|
||||||
|
/// the facade re-exposure with `GcRefuse` added, carried by doc
|
||||||
|
/// comments and construction discipline rather than two types.
|
||||||
|
/// Facade behavioral enforcement (`get`/`stat` never carry
|
||||||
|
/// `Missing`; `Missing`/`KeyInvalid` exclusions) lands in wave 2.
|
||||||
|
#[test]
|
||||||
|
fn one_enum_both_altitudes() {
|
||||||
|
let trait_effective: [Error; 4] = [
|
||||||
|
Error::Missing,
|
||||||
|
Error::Verification,
|
||||||
|
Error::Io("media".to_owned()),
|
||||||
|
Error::KeyInvalid,
|
||||||
|
];
|
||||||
|
assert_eq!(trait_effective.len(), 4);
|
||||||
|
let facade_surface: [Error; 5] = [
|
||||||
|
Error::Missing,
|
||||||
|
Error::Verification,
|
||||||
|
Error::Io("media".to_owned()),
|
||||||
|
Error::GcRefuse,
|
||||||
|
Error::KeyInvalid,
|
||||||
|
];
|
||||||
|
assert_eq!(facade_surface.len(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `get`/`stat` signal absence with `None` and never carry
|
||||||
|
/// `Missing` (ADR-012 §6.5 ruling 5; store-api.md §Error model).
|
||||||
|
/// Behavioral enforcement lands with the facade in wave 2; the
|
||||||
|
/// doc contract on [`Error::Missing`] is stated with this test as
|
||||||
|
/// its marker.
|
||||||
|
#[test]
|
||||||
|
fn missing_is_exclusive_to_err_shaped_ops() {
|
||||||
|
let absence_on_get: Option<String> = None;
|
||||||
|
assert_eq!(absence_on_get.map(|_| Error::Missing), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ pub mod gc;
|
|||||||
pub mod hashing;
|
pub mod hashing;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
|
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
#[cfg(feature = "mem")]
|
#[cfg(feature = "mem")]
|
||||||
pub mod mem;
|
pub mod mem;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
id: core-error-model
|
id: core-error-model
|
||||||
name: Error model — the one-enum family with altitude discipline
|
name: Error model — the one-enum family with altitude discipline
|
||||||
status: pending
|
status: completed
|
||||||
depends_on: [scaffold-crate-features]
|
depends_on: [scaffold-crate-features]
|
||||||
scope: single
|
scope: single
|
||||||
risk: low
|
risk: low
|
||||||
@@ -50,14 +50,14 @@ posture on alkstore's error taxonomy).
|
|||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
|
|
||||||
- [ ] `Error` enum with the five variants, thiserror-derived
|
- [x] `Error` enum with the five variants, thiserror-derived
|
||||||
Display/Error impls
|
Display/Error impls
|
||||||
- [ ] Doc comments on the enum and each variant stating the semantics
|
- [x] Doc comments on the enum and each variant stating the semantics
|
||||||
and the altitude discipline verbatim-traceable to store-api.md
|
and the altitude discipline verbatim-traceable to store-api.md
|
||||||
- [ ] Unit tests: each variant constructs; `Missing` documented
|
- [x] Unit tests: each variant constructs; `Missing` documented
|
||||||
get/stat exclusion stated in docs (behavioral enforcement lands
|
get/stat exclusion stated in docs (behavioral enforcement lands
|
||||||
with the facade in wave 2)
|
with the facade in wave 2)
|
||||||
- [ ] `cargo test`, `cargo clippy --all-targets -- -D warnings`,
|
- [x] `cargo test`, `cargo clippy --all-targets -- -D warnings`,
|
||||||
`cargo fmt --check` green
|
`cargo fmt --check` green
|
||||||
|
|
||||||
## References
|
## References
|
||||||
@@ -67,8 +67,35 @@ posture on alkstore's error taxonomy).
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
> To be filled by implementation agent
|
- Implemented as a *single* enum carrying the altitude discipline in
|
||||||
|
doc comments (module doc, per-variant docs, and a
|
||||||
|
`one_enum_both_altitudes` test asserting the 4-member trait-shape
|
||||||
|
vs 5-member facade-shape split) — the reading recommended by the
|
||||||
|
task's named design point. Payloads verbatim: only `Io(String)` is
|
||||||
|
annotated with a payload in store-api.md/ADR-012 §6.5; `Missing`,
|
||||||
|
`Verification`, `GcRefuse`, `KeyInvalid` are the unit variants the
|
||||||
|
spec text itself writes, so no payloads were invented for them.
|
||||||
|
- `#[non_exhaustive]` not applied per the task's ADR-pinned variant
|
||||||
|
set. `Error` derives `Debug`/`PartialEq`/`Eq` (test/ctor utility;
|
||||||
|
no behavior added).
|
||||||
|
- Re-exported as `pub use error::Error` from `lib.rs`; module remains
|
||||||
|
public per the scaffold shape.
|
||||||
|
- The `missing_is_exclusive_to_err_shaped_ops` test is the marker for
|
||||||
|
the get/stat-exclusion doc contract; behavioral enforcement is
|
||||||
|
deferred to the wave-2 facade task, as scoped.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
> To be filled on completion
|
`src/error.rs` now defines the crate's error family: one
|
||||||
|
thiserror-derived `Error` enum with exactly the five distinguished
|
||||||
|
variants (`Missing`, `Verification`, `Io(String)`, `GcRefuse`,
|
||||||
|
`KeyInvalid`), doc comments traceable to store-api.md §Error model and
|
||||||
|
ADR-012 §6.5/§1 stating each variant's semantics plus the one-enum/
|
||||||
|
two-altitude discipline (trait boundary: `Missing`/`Verification`/`Io`/
|
||||||
|
`KeyInvalid` effective; `KeyInvalid` never fires at the boundary,
|
||||||
|
`GcRefuse` never fires below the facade — the facade re-exposes the
|
||||||
|
family with `GcRefuse` added). Re-exported from `lib.rs`. Three unit
|
||||||
|
tests cover variant construction, the single-enum altitude shape, and
|
||||||
|
the `Missing`-is-never-`get`/`stat`-absence doc contract. Verified:
|
||||||
|
`cargo test`, `cargo clippy --all-targets -- -D warnings`, `cargo fmt
|
||||||
|
--check` all green.
|
||||||
Reference in new issue
Block a user