pub enum Error {
Show 13 variants
RunAlreadyTerminal {
status: Status,
},
Io {
path: PathBuf,
source: Error,
},
IoBare(Error),
Json {
path: PathBuf,
source: Error,
},
JsonBare(Error),
CorruptEventLog {
path: PathBuf,
reason: String,
},
InvalidRunId {
run_id: String,
reason: String,
},
CorruptProjection {
kind: &'static str,
path: PathBuf,
expected_id: String,
body_id: String,
},
SymlinkRunDir {
path: PathBuf,
},
SymlinkSubdir {
name: &'static str,
path: PathBuf,
},
SymlinkStateFile {
name: &'static str,
path: PathBuf,
},
UnsupportedSchemaVersion {
path: PathBuf,
found: u32,
supported: Vec<u32>,
},
EmptyIdempotencyKey,
}Expand description
Errors raised while reading, writing, or validating run state on disk.
Variants§
RunAlreadyTerminal
A cancel_run was refused because the run is already in a non-cancelled
terminal state (Done / Failed). Cancelling such a run would claim a
transition the reducer’s terminal-state guard refuses, so the operation
is rejected up front without mutating any state. An already-Cancelled
run is not this error — it converges (see crate::cancel_run).
Io
Filesystem I/O failure with the offending path attached for context.
Fields
IoBare(Error)
I/O failure with no path context (from ? on a bare io::Error).
Json
JSON (de)serialization failure with the offending path attached.
Fields
JsonBare(Error)
JSON failure with no path context (from ? on a bare serde_json::Error).
CorruptEventLog
An events.jsonl line could not be parsed or violated an invariant.
Fields
InvalidRunId
A run_id failed validation when constructing crate::paths::RunPaths.
CorruptProjection
A projection file’s embedded id contradicts where it lives on disk.
Two distinct integrity faults share this variant, told apart by kind:
- Read side (
kind="node"/"discussion"/"spinoff"): the body’s own id newtype is well-formed but does not equal the filename key it was requested under — a validnodes/n-0002.jsonplaced atnodes/n-0001.jsondeserializes fine yet describes a different node. Returning it asn-0002would let a laterwrite_nodeclobber a third file, so the read is rejected instead. - Write side (
kind="node_run_id"/"discussion_run_id"/"spinoff_run_id"/"manifest_run_id"): the object’srun_iddoes not equal thecrate::paths::RunPathsrun it would be written under, so the write is refused before it can stamp a foreign run’s id into this run’s directory.
This is the projection-integrity guard, distinct from
Error::CorruptEventLog (which guards events.jsonl). It is not a
path-traversal vector — the keys are already validated id newtypes that
cannot name a file outside the run directory — but a corruption /
mis-placement detector. kind is a fixed &'static str so a caller can
branch on it; path localizes the offending file; expected_id /
body_id carry the two ids for an operator to diff.
Fields
kind: &'static strWhich check fired: a read-side filename-key mismatch ("node",
"discussion", "spinoff") or a run-id mismatch ("node_run_id",
"discussion_run_id", "spinoff_run_id", "manifest_run_id"),
which fires on both the read and write side — the fault is identical
(the object’s run_id does not equal its directory’s run).
path: PathBufThe offending projection file (read side) or its intended destination (write side), so an operator can go straight to it.
SymlinkRunDir
The run directory itself is a symlink rather than a real directory.
Best-effort symlink containment: crate::paths::RunPaths::new and every
projection read/write reject a symlinked run root before any open follows
it, so a replaced <root>/runs/<id> cannot redirect writes outside the
run tree.
Trust model. The state root is $HOME/.orchestratectl/ — a per-user
0700 directory, not a shared multi-user mount. This guards against an
accidentally- or maliciously-replaced subtree component, not a concurrent
attacker who already holds write access to the state root.
Residual gap. The check is check-then-open: a pure TOCTOU attacker can
swap the path for a symlink in the window between the symlink_metadata
call and the subsequent open. Closing that needs O_NOFOLLOW / openat2
(RESOLVE_BENEATH / RESOLVE_NO_SYMLINKS), which the standard library
does not expose portably; it is out of scope for the MVP threat model.
SymlinkSubdir
A run subdirectory (nodes/, discussions/, spinoffs/) is a symlink.
Same best-effort containment, trust model, and TOCTOU residual gap as
Error::SymlinkRunDir.
Fields
SymlinkStateFile
A run-state file is a symlink rather than a regular file — covers the
manifest, the event log, the lock file, and the per-id projection files
(name discriminates: "manifest", "events", "lock", "node",
"discussion", "spinoff").
Same best-effort containment, trust model, and TOCTOU residual gap as
Error::SymlinkRunDir. These files are created by the run itself
(projection writes go via temp-file + rename, always regular files); a
symlink in their place is a tampered or corrupted run.
Fields
UnsupportedSchemaVersion
A state file declared a schema_version this build does not support.
Fields
supported: Vec<u32>Versions this build can read (see SUPPORTED_STATE_SCHEMAS).
EmptyIdempotencyKey
An idempotency key was empty.
A "" key would collapse every “no real key” append into a single
dedup slot, so append_and_apply_idempotent
rejects it in core rather than trusting each CLI boundary to pre-validate.
The CLI verbs already reject it up front; this is the defense-in-depth
backstop for any future caller.
Implementations§
Trait Implementations§
Source§impl Error for Error
impl Error for Error
Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()