M0-M12 are shipped and the HA cluster runs in production on k3s, so the pre-release disclaimer no longer describes the project. Removes it from the canonical doc set and corrects the readiness text that had gone stale. - README.md: replace the "Pre-release / not yet recommended for production" banner with a production-ready statement; drop "(experimental)" from the cluster status bullet; state the post-1.0 versioning posture (additive in minor releases, breaking changes get a documented migration path). - CLAUDE.md / QUICKSTART.md / docs/guides/server-deployment.md / docs/runbooks/cluster.md: same withdrawal; reframe the cluster opt-in as a guard against standing up a multi-node fabric by accident rather than a readiness warning. - CHANGELOG.md: record the stability posture under [Unreleased], superseding the historical 0.1.0 "no stability guarantees" note (left intact as history). - k8s/statefulset.yaml: the "NOT production HA, tracked as m8p10" comment was stale (m8p10 shipped); point at k8s/cluster/ for the HA deployment instead. Also corrects text that was factually wrong since m11p3/m11p4: the multi-process cluster gate, its CLI help, and the served OpenAPI description all still claimed quorum-ack writes and automatic failure detection did not exist. They do. Historical records (docs/reviews/, docs/profiling/, past CHANGELOG entries, the kubernetes.md rc7 fix note) are left unchanged. Verified against a running binary, not just the build: the opt-in gate's refusal message, the startup WARN, /health 200, and the served /openapi.json description all carry the new text. cargo fmt clean; clippy -D warnings clean on tidaldb and the tidal-server lib; 1943 engine + 155 server lib tests pass; scripts/check-docs.sh OK. Claude-Session: https://claude.ai/code/session_01QdqSDw1tUhK1JT9Pb1vryP
104 lines
4.4 KiB
Rust
104 lines
4.4 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use thiserror::Error;
|
|
|
|
pub type Result<T, E = ServerError> = std::result::Result<T, E>;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ServerError {
|
|
/// A filesystem read that knows WHICH path failed — config / topology /
|
|
/// schema file loads carry the path so the operator sees the offending file.
|
|
/// Constructed via [`ServerError::io`]; never `?`-converted (that path lacks
|
|
/// a path to attach).
|
|
#[error("failed to read {path}: {source}")]
|
|
Io {
|
|
path: PathBuf,
|
|
source: std::io::Error,
|
|
},
|
|
#[error("invalid schema config: {0}")]
|
|
SchemaConfig(String),
|
|
#[error("schema build failed: {0}")]
|
|
SchemaBuild(#[from] tidaldb::schema::SchemaError),
|
|
#[error("tidalDB error: {0}")]
|
|
Tidal(#[from] tidaldb::TidalError),
|
|
/// A pathless `std::io::Error` from socket setup / serving (TCP bind,
|
|
/// `local_addr`, `axum::serve`). This is the `?`-conversion target for
|
|
/// `std::io::Error`; the path-tagged file-read case uses [`ServerError::Io`]
|
|
/// instead. Named for its source (the network/serve stack) rather than the
|
|
/// old misleading "Http", which read as an HTTP-protocol error.
|
|
#[error("network/serve I/O error: {0}")]
|
|
Network(#[from] std::io::Error),
|
|
#[error("bad request: {0}")]
|
|
BadRequest(String),
|
|
#[error("cluster mode requires an explicit opt-in: {0}")]
|
|
ExperimentalDisabled(String),
|
|
#[error("cluster error: {0}")]
|
|
Cluster(String),
|
|
/// The server is shutting down and the cluster fabric has been taken; map to
|
|
/// 503 so a post-shutdown access degrades cleanly instead of panicking.
|
|
#[error("service unavailable: {0}")]
|
|
Unavailable(String),
|
|
/// A write reached a non-leader region node in multi-process cluster mode.
|
|
/// Maps to 503 with a JSON body naming the leader (and its HTTP address when
|
|
/// known) so the node is honest standalone; task 03 upgrades this to
|
|
/// transparent leader forwarding.
|
|
#[error("not the leader; current leader is '{leader}' (term {term})")]
|
|
NotLeader {
|
|
leader: String,
|
|
http_addr: Option<String>,
|
|
/// The responder's election term (m11p4): lets a forwarder tell a
|
|
/// stale answer from a fresh one during election churn.
|
|
term: u64,
|
|
/// The shard group this not-leader answer is for (m11p6). `None` for the
|
|
/// legacy single group (S=1, byte-for-byte). Names the group in the body
|
|
/// so a client retrying a per-shard write learns which group rejected it
|
|
/// and which `?shard=` to re-target.
|
|
shard: Option<u16>,
|
|
},
|
|
/// A region-pinned read named a region this process does not own (multi-
|
|
/// process cluster mode). Maps to 400 naming the region; task 03 upgrades
|
|
/// this to forwarding to the owning process.
|
|
#[error("region '{region}' is not served by this node")]
|
|
NotLocal { region: String },
|
|
/// The current leader could not be reached while forwarding a write
|
|
/// (multi-process cluster mode). Maps to 503 with a body naming the leader,
|
|
/// its HTTP address, and the transport cause, so the client can retry or
|
|
/// re-target the leader directly.
|
|
#[error("leader '{leader}' unreachable while forwarding: {cause}")]
|
|
LeaderUnreachable {
|
|
leader: String,
|
|
http_addr: String,
|
|
cause: String,
|
|
},
|
|
/// A sibling region could not be reached (region-pinned read forward,
|
|
/// sharded write to its owner, or the reconcile snapshot exchange). Maps to
|
|
/// 503 naming the region and the transport cause.
|
|
#[error("region '{region}' unreachable: {cause}")]
|
|
RegionUnreachable { region: String, cause: String },
|
|
/// An `ack=quorum` write was durable on the leader but a majority of the
|
|
/// replica set did not confirm durability within the quorum budget
|
|
/// (m11p3). Maps to a retryable 503 naming the laggards. The write is in
|
|
/// the leader's log and MAY still commit — retries are at-least-once
|
|
/// (see runbook §8 for the dedup guidance).
|
|
#[error(
|
|
"quorum not reached for seqno {seq} within budget: {confirmed} of {needed} required \
|
|
follower confirmations (commit index {committed}); laggards: {laggards:?}"
|
|
)]
|
|
QuorumTimeout {
|
|
seq: u64,
|
|
needed: usize,
|
|
confirmed: usize,
|
|
committed: u64,
|
|
laggards: Vec<String>,
|
|
},
|
|
}
|
|
|
|
impl ServerError {
|
|
pub fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
|
|
Self::Io {
|
|
path: path.into(),
|
|
source,
|
|
}
|
|
}
|
|
}
|