--- id: core-errors-and-validation name: Core error taxonomy and name validation status: completed depends_on: [scaffold-workspace] scope: narrow risk: low impact: project level: implementation tags: [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` 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` — 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>`. - `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` 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.