Files
alkstore/tasks/core-errors-and-validation.md

5.6 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
core-errors-and-validation Core error taxonomy and name validation completed
scaffold-workspace
narrow low project implementation
wave-1
core

Description

Implement the core crate's error taxonomy and the name-validation helpers every entry point shares.

Error taxonomy (ADR-008 §5, exact): one top-level Error, thiserror-typed, #[non_exhaustive] (ADR-017 §3 — mechanically enforces catch-all matching). Variants:

  • PayloadTooLarge { limit } — universal variant; produced pg-side only (the occurrence asymmetry is engine work, not core's — core defines the variant and its doc text)
  • ReservedName { name }
  • InvalidName { name }
  • Closed
  • Codec
  • Database — the opaque fallback; engine detail rides the source chain (#[source]), no engine-specific variants ever minted

Name validation (ADR-008 §4): a shared validation helper applied at every name-bearing entry point — non-empty (InvalidName) and reserved-prefix rejection for the __alkstore_ prefix (ReservedName). The helper distinguishes the two name classes:

  • Shared-namespace kinds (channels, streams, queues, outboxes, locks, schedule names): non-empty + reserved-prefix rejected.
  • Consumer-local identifier classes (stream-consumer names, owner on try_lock, worker_id on claims): non-empty only — a leading __alkstore_ is legal (ADR-008 §4 third-round annotation).

Also the reserved-string constant: __alkstore_listener_reconnected__ (the one v1-reserved string, ADR-008 §4) — public in core so the pg engine and the contract suite reference the same constant.

Result<T> alias in the crate's prelude shape. No panics; no unwrap()/expect().

Acceptance Criteria

  • Error with exactly the six v1 variants, #[non_exhaustive], thiserror-derived, Database carrying #[source]
  • Validation helper(s) covering both name classes, unit-tested (empty, whitespace-only, reserved-prefix, reserved string itself, legal names)
  • Reserved-string constant exported
  • cargo test -p alkstore, clippy -D warnings, fmt clean

References

  • docs/architecture/decisions/008-contract-v1-pinning.md §4, §5
  • docs/architecture/decisions/017-contract-versioning.md §3
  • docs/architecture/core-contract.md §Errors, §Naming / reserved namespace

Notes

  • thiserror v2 added to alkstore — the crate's first (and, until wave 2's serde work, only) dependency.
  • Error::Database carries #[source] Box<dyn std::error::Error + Send + Sync> — the opaque engine-detail carrier per ADR-008 §5. The #[error(...)] text is deliberately terse ("database error") so it doesn't shadow the source chain's detail. An Error::database(err) convenience constructor wraps anything Into<Box<dyn Error + Send + Sync>>.
  • Error::PayloadTooLarge { limit: usize } — the limit is usize (a byte count, not a std::io::ErrorKind-style re-export); doc text carries the ADR-020 §4 serialization-quantity pin and the pg-only/never-SQLite asymmetry.
  • Validation helpers split by name class exactly per ADR-008 §4's third-round annotation: validate_shared_name (six shared kinds — non-empty + reserved-prefix rejection) and validate_local_name (stream-consumer names, try_lock owner, worker_id — non-empty only). NameKind::Shared/NameKind::Local + validate_name(kind, name) is the underlying pair; the two named helpers are the ergonomic surface. Whitespace-only names are rejected as InvalidName for both kinds (never a meaningful name on any engine); names are not trimmed (the caller's name is stored as given — engine quoting is the engine's obligation).
  • The reserved-string test asserts its shared-name rejection carries ReservedName (it carries the reserved prefix, so it can never be a legal shared name) while validate_local_name accepts it — the two name classes distinguished in tests, mirroring the ADR text.
  • #[non_exhaustive] was verified on the enum definition, not in this crate's tests: in-crate matching stays exhaustive (the mechanical catch-all enforcement binds downstream crates, per the Rust rules ADR-017 §3 relies on) — a test here asserting a necessary wildcard is unreachable-code, flagged clippy warn(unreachable_patterns).
  • schedule()/run_schedules' InvalidSpec/LeadershipLost variants (core-contract §Errors, ADR-009 §6) are not minted here — the six-variant list is this task's exact scope; the scheduler variants land with the trait-surface task that pins their signatures.

Summary

Core error taxonomy and name validation implemented in alkstore. src/error.rs: Error — exactly the six v1 variants (PayloadTooLarge { limit }, ReservedName { name }, InvalidName { name }, Closed, Codec(String), Database with #[source]), thiserror-derived, #[non_exhaustive], plus the Result<T> alias (the prelude shape downstream crates use). src/validation.rs: RESERVED_PREFIX = "__alkstore_", RESERVED_LISTENER_RECONNECTED = "__alkstore_listener_reconnected__" (public for the pg engine and contract suite), NameKind::{Shared, Local}, and validate_shared_name/validate_local_name/validate_name helpers. All types re-exported from src/lib.rs. Ten unit tests cover empty/whitespace-only/reserved-prefix/reserved-string/legal names for both classes and the error display/source behavior. cargo test -p alkstore (10 pass), cargo clippy --all-targets -- -D warnings, and cargo fmt --check all clean; workspace cargo build compiles all three crates.