123 lines
5.6 KiB
Markdown
123 lines
5.6 KiB
Markdown
---
|
|
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<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. |