jobq: say the graph is runtime-only, because it is

The crate described itself as a persistent scheduler and NodeId promised
stability across restarts. Neither is true: hive-c0re constructs an empty
Graph on every boot and re-derives desired state with its reconcile sweep,
and nothing in the workspace writes or loads a graph. hive-c0re's own
job_queue module doc has said "runtime-only (no persistence)" all along —
only the extracted library's prose drifted.

Seven claims corrected across the module doc, NodeId, the id-counter error
and the Graph type, plus the repo map. The module doc now states the fact
positively rather than just dropping the word: serde exists so the graph can
be projected onto a wire and so a store could be added later, ids and
timestamps are stable within a run.

NodeId spells out the consequence, since that is the part that could mislead
someone: an id stored outside the process is a historical record, not a
handle that will resolve after a restart.
This commit is contained in:
atlas 2026-08-03 15:46:09 +02:00 committed by mara
commit 6e18bbc839
2 changed files with 29 additions and 13 deletions

View file

@ -53,9 +53,12 @@ hand-maintained per-file tree drifts out of sync with the code.
streamable-http listener, `hive-mcp-http` systemd unit) + its claude streamable-http listener, `hive-mcp-http` systemd unit) + its claude
launch-config layer (tool-group/capability → `--allowedTools`, launch-config layer (tool-group/capability → `--allowedTools`,
`--mcp-config` render). `--mcp-config` render).
- **`hive-jobq/`** — persistent job-DAG scheduler, extracted from - **`hive-jobq/`** — job-DAG scheduler, extracted from hive-c0re's
hive-c0re's in-tree `job_queue` as a domain-agnostic library. One in-tree `job_queue` as a domain-agnostic library. **Runtime-only —
persistent graph for the whole system (not a DAG per job); enqueuing nothing writes the graph to disk**; hive-c0re starts empty each boot
and re-derives desired state via the reconcile sweep, so node ids and
timestamps are stable within a run, not across restarts. One
shared graph for the whole system (not a DAG per job); enqueuing
inserts a self-contained sub-DAG and returns the ids of the nodes the job inserts a self-contained sub-DAG and returns the ids of the nodes the job
asked for, in the order it named them. Generic over asked for, in the order it named them. Generic over
the node payload `N` and the resource name `R`; resource deps are named the node payload `N` and the resource name `R`; resource deps are named

View file

@ -1,9 +1,12 @@
//! `hive-jobq` — a persistent job-DAG scheduler, extracted from hive-c0re's //! `hive-jobq` — a job-DAG scheduler, extracted from hive-c0re's in-tree
//! in-tree `job_queue` as a domain-agnostic library. //! `job_queue` as a domain-agnostic library.
//!
//! **Runtime-only: nothing writes this graph to disk** — ids and timestamps are
//! stable within a run, not across restarts. See [`Graph`] and [`NodeId`].
//! //!
//! # Model (v2) //! # Model (v2)
//! //!
//! One **persistent graph** for the whole system, not a DAG per job. Enqueuing //! One **shared graph** for the whole system, not a DAG per job. Enqueuing
//! inserts a self-contained sub-DAG of nodes and returns their ids; the //! inserts a self-contained sub-DAG of nodes and returns their ids; the
//! scheduler runs a continuous loop, starting every node whose [`Dep`]s are //! scheduler runs a continuous loop, starting every node whose [`Dep`]s are
//! satisfied: //! satisfied:
@ -18,7 +21,7 @@
//! A node carries two independent axes: its [`Dep`]s (ordering + resource //! A node carries two independent axes: its [`Dep`]s (ordering + resource
//! needs) and its [`Node::parent`] (structural grouping) — the parent chain, //! needs) and its [`Node::parent`] (structural grouping) — the parent chain,
//! not the [`Dep::Node`] edges, is what the [`scheduler`] consults for resource //! not the [`Dep::Node`] edges, is what the [`scheduler`] consults for resource
//! re-entrancy. A [`NodeId`] is opaque, stable, and monotonic (persisted). The //! re-entrancy. A [`NodeId`] is opaque, stable and monotonic within a run. The
//! payload `N` is generic so the library stays container-agnostic. //! payload `N` is generic so the library stays container-agnostic.
//! //!
//! A resource unit is held for the acquiring node + its whole [`Node::parent`] //! A resource unit is held for the acquiring node + its whole [`Node::parent`]
@ -36,12 +39,15 @@ use chrono::{DateTime, Utc};
/// Opaque, stable, monotonic node identifier. /// Opaque, stable, monotonic node identifier.
/// ///
/// Assigned by the [`Graph`] on insert and persisted, so it is stable across /// Assigned by the [`Graph`] on insert, so it is stable for the lifetime of
/// restarts. /// that graph. **Not stable across restarts** — nothing persists the graph, so
/// a fresh process mints ids from zero again (see the module doc). Anything
/// storing an id outside the process is keeping a historical record, not a
/// handle it can resolve later.
/// ///
/// The inner field is crate-private: an id can only originate from the graph's /// The inner field is crate-private: an id can only originate from the graph's
/// monotonic counter (or deserialization of a persisted graph), never be /// monotonic counter (or deserializing a graph that was serialized from one),
/// fabricated by a caller — that is what makes it opaque. /// never be fabricated by a caller — that is what makes it opaque.
#[derive( #[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)] )]
@ -351,14 +357,21 @@ pub enum GraphError {
/// so the next minted id would collide with one already in the graph. /// so the next minted id would collide with one already in the graph.
#[error("next_id {next_id} must exceed the largest existing node id {max_id}")] #[error("next_id {next_id} must exceed the largest existing node id {max_id}")]
NextIdTooSmall { NextIdTooSmall {
/// The persisted counter value. /// The counter value carried by the deserialized graph.
next_id: u64, next_id: u64,
/// The largest id already present. /// The largest id already present.
max_id: u64, max_id: u64,
}, },
} }
/// The single persistent graph of all nodes. /// The single in-memory graph of all nodes.
///
/// **Not persisted.** The `Serialize`/`Deserialize` impls exist so a graph can
/// be projected onto a wire (`hive-jobq-wire`) and so a store *could* be added,
/// but no caller writes or loads one: hive-c0re constructs an empty graph on
/// every boot and re-derives desired state with its reconcile sweep (see
/// `hive-c0re/src/job_queue/mod.rs`). A derive is a capability, not a promise
/// that something uses it.
/// ///
/// New jobs are inserted as sub-DAGs of nodes; the scheduler walks this graph /// New jobs are inserted as sub-DAGs of nodes; the scheduler walks this graph
/// filling open slots. Completed nodes are retained (no pruning in v1). /// filling open slots. Completed nodes are retained (no pruning in v1).