Skip to main content

StoreError

Enum StoreError 

Source
pub enum StoreError {
Show 18 variants Open { path: PathBuf, source: Error, }, Sqlite(Error), SchemaTooNew { found: u32, supported: u32, }, Migration { version: u32, name: &'static str, source: Error, }, StaleRevision { id: PolicyId, expected: u64, found: u64, }, ActiveCountChanged { id: PolicyId, expected: u16, found: u16, }, RunnerRootChanged { id: HostId, expected: String, found: String, }, UncleanedCountChanged { subject: String, expected: u16, found: u16, }, SlotAlreadyLeased { policy: PolicyId, slot: u16, }, NotFound { what: &'static str, id: String, }, AlreadyExists { what: &'static str, id: String, }, CorruptPolicy { id: PolicyId, source: PolicyError, }, CorruptAttempt { id: AttemptId, source: AttemptError, }, CorruptHost { id: HostId, source: ValidationError, }, CorruptHostWorkspace { id: HostId, source: WorkspaceError, }, CorruptColumn { table: &'static str, column: &'static str, id: String, value: String, expected: &'static str, }, UnrepresentableInteger { what: &'static str, value: u64, }, UnrepresentablePath { attempt: AttemptId, path: PathBuf, },
}
Expand description

Everything that can go wrong between a domain value and a database row.

The variants are split the way a caller has to branch, not the way the implementation happens to fail. In particular StoreError::StaleRevision is its own variant rather than a flavour of StoreError::Sqlite, because a concurrent edit is an ordinary outcome the CLI and the TUI must report as “someone else changed this; re-read and try again”, while an I/O failure is not. StoreError::is_conflict is the predicate for that branch.

Variants§

§

Open

The database file could not be opened. The parent directory belongs to d1; this module never creates it.

Fields

§path: PathBuf
§source: Error
§

Sqlite(Error)

Any other SQLite failure: a real I/O error, a locked database, a malformed file.

§

SchemaTooNew

The database was written by a newer build of this product.

This fails closed on purpose. A newer version may have added a column this build does not write — which this build would then drop on its next write — or changed the meaning of one it does. Guessing is how a downgrade silently corrupts a configuration.

Fields

§found: u32
§supported: u32
§

Migration

A migration did not apply. The transaction around it rolled back, so the database is still at the previous version.

Fields

§version: u32
§name: &'static str
§source: Error
§

StaleRevision

A write lost an optimistic-concurrency race. Nothing was written.

The caller must re-read the policy and re-apply its change; it must not retry the value it holds, because that value was derived from a revision that no longer exists.

Fields

§expected: u64
§found: u64
§

ActiveCountChanged

Active work changed after an operator observed it for a disable. The policy update and this predicate execute under one SQLite write transaction, so no attempt journal write can cross the check.

Fields

§expected: u16
§found: u16
§

RunnerRootChanged

A host runner-root write was built from an override that is no longer the stored one. Nothing was written.

This is the host counterpart of StoreError::StaleRevision, and it exists because hosts carries no revision column: 03-migration-rollout requires the host mutation to compare “the expected old override” and update only that column, so that a capacity or service-mode change made between the operator’s read and this write is not silently rolled back by a whole-record Store::put_host.

Both paths are rendered rather than optional, so a message reads the same way whichever direction the change went: an unset override is “the platform default”.

Fields

§expected: String
§found: String
§

UncleanedCountChanged

Uncleaned attempts changed after an operator observed the count a path mutation was refused or permitted on. Nothing was written.

Distinct from StoreError::ActiveCountChanged, and the difference is the whole point of the variant. Active excludes a terminal attempt; uncleaned includes one, because a finished attempt whose cleanup has not run still owns the directory under the root being moved, and a persistent one still holds its slot lease (04-security-recovery.md: “A host root setting cannot change while any ephemeral attempt is active or unresolved”).

subject names the host or policy the count was taken for, already rendered, because the two callers count different sets and an operator reading the message needs to know which.

Fields

§subject: String
§expected: u16
§found: u16
§

SlotAlreadyLeased

Two uncleaned persistent attempts cannot hold one slot.

Raised when a journal write collides with the partial unique index one_uncleaned_persistent_attempt_per_slot. The allocation lock in c2 coordinates slot selection; this is the durable guard that catches the race the lock cannot see — a second process, or a restart that lost the lock — and 04-security-recovery.md names it as the control for “two attempts use one slot concurrently”.

Nothing was written: the statement is a single INSERT, so SQLite rolls it back whole and the existing lease is untouched.

Fields

§policy: PolicyId
§slot: u16
§

NotFound

The row a write was aimed at is not there.

Fields

§what: &'static str
§

AlreadyExists

An insert collided with an existing primary key.

Fields

§what: &'static str
§

CorruptPolicy

A stored policy is not a legal policy. This is the hand-edited-database case: D19’s shape rules and min <= max are re-run on every load.

Fields

§

CorruptAttempt

A stored attempt is not a legal attempt: its state, outcome and timestamps do not pair the way this crate’s own transitions pair them.

Fields

§

CorruptHost

A stored host does not satisfy a domain constraint — a blank display name, or a refresh interval under the documented floor.

Fields

§

CorruptHostWorkspace

A stored host’s configured runner root is not a shape this product will place a runner under.

Separate from StoreError::CorruptHost because the source error is a different vocabulary: hosts.runner_root_override is re-parsed through LocalAbsolutePath::new, so a hand-edited \\nas\builds, a relative path, a bare drive root, or a Windows path in a database opened on Linux fails closed here with the reason attached (D10). A path is not a credential — see crates/domain/src/path.rs — so the offending text may travel in the message, which is what makes the refusal actionable.

Fields

§

CorruptColumn

One column holds something that is not the kind of value it is declared to hold. The row is named so an operator can find and fix it.

value never repeats the whole payload, and how much it repeats depends on which column this is. The row id is what an operator needs to find the row; the payload only helps them recognise it, and repeating all of it turns this error into a disclosure the moment it reaches a log.

table and column are carried for the operator’s sake and are also what decides the echo: a column whose shape the schema fixes gets a clipped echo of at most ECHO_LIMIT characters, and one that may hold text the agent captured from a failure gets position only, with none of the payload. The rule and the measurement behind it are on FREE_FORM_COLUMNS, beside the decoder that applies it. (Named rather than linked: it is private, and a link from here would not resolve for a reader of the public docs.)

Fields

§table: &'static str
§column: &'static str
§value: String

At most ECHO_LIMIT characters of the offending payload for a constrained column, and none of it for a free-form one.

§expected: &'static str
§

UnrepresentableInteger

An integer that does not fit in a SQLite integer.

SQLite has no unsigned 64-bit type, so a u64 above i64::MAX has no representation. Refused rather than saturated: saturating stores one number and reads a different one back, silently, and the two values the domain carries as u64installation_id and github_runner_id – both come from GitHub, so a caller can reach this without doing anything unusual.

Fields

§what: &'static str
§value: u64
§

UnrepresentablePath

A runtime path that is not valid UTF-8 and therefore cannot be stored as text.

Lossy conversion is deliberately not used: e3 deletes the runtime directory this path names, and a path mangled by U+FFFD substitution either fails to delete or names a different directory.

Fields

§attempt: AttemptId
§path: PathBuf

Implementations§

Source§

impl StoreError

Source

pub const fn is_conflict(&self) -> bool

Whether this is an optimistic-concurrency conflict rather than a failure.

The Definition of Done requires that “a stale-revision write is rejected and the caller can distinguish it from an I/O error”. This is that distinction, exposed so a caller need not match on the variant shape to make it.

The three fences the workspace mutations add are conflicts on exactly the same footing: each means “someone else got there first, re-read and try again”, and none of them means the database is unwell. StoreError::SlotAlreadyLeased is included because that is what an allocator that lost a race to a slot must do — pick another one — rather than surface an I/O failure to an operator.

Trait Implementations§

Source§

impl Debug for StoreError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for StoreError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for StoreError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0:

use the Display impl or to_string()

1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0:

replaced by Error::source, which can support downcasting

Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Error> for StoreError

Source§

fn from(source: Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more