jobq-wire: move the generic graph projection into its own crate

The wire types were in hive-host-sock, which is the host *socket* crate — so
anything living there is core-shaped by construction, and the projection had
quietly grown two core dependencies to match: it selected roots by matching
NodeKind::Dag, and rendered payloads through free functions in hive-c0re that
nothing obliged a second host to write.

hive-jobq is the wrong home too. That crate is the scheduler — logic — and
folding presentation in means every consumer of it carries a JSON vocabulary
it may never serve.

So: a new hive-jobq-wire. A host implements WireNode for its payload N and
WireResource for its resource name R; GraphWire::wire_snapshot is
blanket-implemented for Graph<N, R> when both hold, and for nothing else. A
payload that has never said how it displays has no way onto the wire.

wire_snapshot takes the roots to serve rather than reading Graph::roots
itself. Nothing is ever removed from a Graph, so retention is a policy only
the host can hold; hive-c0re passes visible_roots(), which is the existing
MAX_HISTORY_DAGS bound selected structurally (a root is a node with no
parent) instead of by node kind.
This commit is contained in:
atlas 2026-08-02 23:52:32 +02:00 committed by mara
commit 7966d5eb66
14 changed files with 564 additions and 297 deletions

View file

@ -661,14 +661,14 @@ fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
one opaque per-node payload. Group roots ride as ordinary nodes \
(`parent: null`) and `Done` nodes are not filtered a consumer \
renders the graph without knowing what any node means. Each element \
is a `hive_host_sock::graph::GraphNode`",
is a `hive_jobq_wire::GraphNode`",
body = serde_json::Value),
),
tag = "state_snapshot"
)]
pub(super) async fn jobq_graph(
State(state): State<AppState>,
) -> axum::Json<Vec<hive_host_sock::graph::GraphNode>> {
) -> axum::Json<Vec<hive_jobq_wire::GraphNode>> {
axum::Json(state.coord.job_queue.graph_snapshot())
}