Commit graph

3,242 commits

Author SHA1 Message Date
iris
dfd92a7da9 frontend: shared <hive-warn> component for inline warning banners
Consolidates three independently-written instances of the same thing:
.cred-warning (credentials.html, static markup), .tombstone-warn
(core.js, JS-built), .port-conflict (swarm.js, JS-built) -- the first
two differed only by an undeliberate 10% vs 8% tint, the strongest
argument this was drift rather than three genuinely different needs.

New autonomous custom element, frontend/packages/shared/src/hive-warn/,
matching the established one-component-one-dir shadow-DOM pattern
(hive-btn, hive-toast, hive-dialog). Purely presentational -- no
lifecycle beyond attaching its shadow root once, no
attributeChangedCallback needed since severity/pulse are read directly
by :host([...]) CSS selectors rather than mirrored onto an inner
element. Content passes through via a single default <slot>, so every
call site keeps writing its existing <strong>/<code> markup unchanged.

API: <hive-warn> (amber, default) / <hive-warn severity="red"> for an
active incident vs a standing caveat, plus an opt-in pulse boolean
modifier (only the port-collision banner wants it -- a banner that's
always present and always pulsing just trains you to stop seeing it).
Tint is a single canonical 8% for both severities now, replacing the
10%/8% split.

Registered once in dashboard's common.js (same side-effect-import
pattern as <hive-side-panel>) so every dashboard page picks it up
without a per-file import, since all three call sites (core.js,
swarm.js, credentials.js) already transitively import it.

Verified: npm run build clean for both dashboard and agent packages,
grepped source for leftover cred-warning/tombstone-warn/port-conflict
references (none), confirmed hive-warn/HiveWarn/hive-warn-pulse present
in every affected dist bundle, confirmed the still-live
questions-pulse keyframe (.questions) untouched.
2026-08-03 18:34:31 +02:00
atlas
7ef0e8c788 jobq: key transient tombstones per pill, not per agent
Deletes `TransientState`, adds `transient_kind` to `TransientCleared`, and
fixes a crash misreport — three changes over the same functions.

`TransientState` was `RunningTransient` with `agent` dropped and
`takes_container_down` renamed; that rename was the only thing it did, and
its three consumers each read a disjoint subset. `transient_snapshot` now
returns `RunningTransient` directly.

`recent_transient` was keyed by agent alone and overwritten on each clear.
An agent can clear several pills in one grace window, so a `Prebuild`
(`takes_container_down = false`) landing after a `StopForUpdate` (`true`)
left the tombstone reading `false` and the crash watcher reported a
deliberate stop as a container crash. Keyed by `(agent, label)` now, with
`recent_transient_within` folding back per agent by OR — the same question
`crash_watch` asks of the active set.

`TransientCleared` gains the label for the same reason: a client holding
two open pills for one agent could not tell which one a clear referred to.

The out-of-band suppression guard has no node and so no label; it uses
`NO_NODE_LABEL`, angle-bracketed to stay out of the `NodeKind::as_str`
namespace.
2026-08-03 18:15:58 +02:00
atlas
6a8a729f58 fix(#3020): K3PT ST4T3 says what it shows, and stops reading the job graph
`build_tombstone_views` folded `transient_snapshot`'s keys into its `live`
set, so an agent with in-flight transient work was treated as not-a-tombstone.
Since #3010 that set is derived from the running job graph, which made a
page about on-disk state a function of the scheduler.

Dropping the filter exposes what was always true underneath: nothing records
a destroy. Every definition-side artifact — state subvolume, proposed +
applied repos, `deployed/0`, meta registration, topology entry — is written by
`Provision` before the container exists and survives `lifecycle::destroy`. So
a mid-spawn agent is byte-identical on disk to a tombstone.

Per mara on #3020: remove the filter, warn on the page, keep the issue open
for the swarm-controller / snapshot-storage rework where the problem shape
changes anyway.

- dashboard/tombstones.rs: drop the param + the chain; document the real
  semantics
- core.js: amber caveat banner above the rows; row badge `destroyed` ->
  `offline`, which is what an absent container actually proves
- dashboard.css: `.tombstone-warn`, modelled on `.port-conflict` but amber and
  without the pulse — a permanent banner that pulses trains you to ignore it
- docs/web-ui/dashboard.md: the pane was described as "destroyed-but-state-kept
  agents", now the exact wrong claim
2026-08-03 18:07:04 +02:00
atlas
6ce71556e2 fix(#2971): thread the queue node id into nix_logged
`nix_logged` wrote its `build_logs` row with `node_id = None`, so a deploy's
eval/relock log was reachable from the agent+kind+time listing but not from
the queue node that ran it.

The comment justifying the `None` said both callers are "reached from outside
the queue as well as from inside it". That is no longer true: `verify_commit`
and `prepare_deploy` have exactly one caller each, and both sit under the
`MergeVerify` / `DeployApply` arms of `exec.rs`'s node dispatch, where the
`NodeId` is already in scope.

Threads `Option<u64>` from the dispatch down, mirroring `prebuild_toplevel`'s
existing `Some(id.get())` at exec.rs:241. Kept as `Option` rather than a bare
`u64` because `meta::prepare_deploy` / `meta::verify_commit` are public API and
a future non-queue caller has no node to name; the comment now says that
instead of the stale claim.

No behaviour change beyond the log row gaining its node link.
2026-08-03 17:53:53 +02:00
damocles
772529b7b5 matrix: drop dead unread_count handler 2026-08-03 17:41:24 +02:00
damocles
1192c4e4c7 matrix: wake and unread-guard on own read receipt, not push-rule count 2026-08-03 17:41:24 +02:00
atlas
d9da92de4f jobq: the README repeats the same false persistence claims
Six more, in the crate's standalone README: the summary, the model section,
NodeId, the Graph entry and the Node serde note. Same wrong claim as the
module doc, in the sibling file the first sweep didn't look at.

A crate's README and its //! module doc are the same document in two files,
so a fix that touches one and not the other is the default outcome rather
than an unlucky miss.
2026-08-03 17:28:09 +02:00
atlas
6e18bbc839 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.
2026-08-03 17:28:09 +02:00
iris
e41417d00e docs: add a top-level docs/README.md index, point README.md at it
Fixes hyperhive#3014.

docs/README.md is a genuine hand-written index for the docs/ tree -
task-oriented reading paths grouped by topic, covering every top-level
doc plus the three subdirectories that already have their own landing
page (web-ui/, tools/, turn-loop/). Adapted from CLAUDE.md's existing
"Reading paths" section (already curated and kept current) rather than
written from scratch, reorganized into headed groups since this is a
landing page, not a flat reference list. Explicitly covers
docs/tools/matrix.md and docs/github.md, per the note on hyperhive#3014
about the content that moved out of README.md's deleted section on
PR #3006 not going undiscoverable.

Top-level README.md's reading-path table (enumerating every doc)
replaced with a single prominent docs-site link, per mara's "replace
docs links table in readme md with a prominent link to the docs
(public host and git relative path)" - both forms present (rendered
site URL, git-relative docs/ path).

Verified every link in docs/README.md resolves to a real file (33
files checked). nix fmt clean, tracker-tag grep clean.

Companion to hyperhive/website#47/#48 - this is what that PRs "index.html
half" needs to exist before it can render, per atlas's note on #48.
2026-08-03 17:27:43 +02:00
atlas
55705fd617 jobq: drop the Dag payload's created_at, read it off the node
The container node now carries its own `created_at` like every other node,
so the payload copy recorded the same instant a second time — and only the
payload one was reachable to a viewer that doesn't know what a Dag is.

`dag_view` reads `node.created_at` off the container instead. `DagView`
keeps the field on the wire: hivectl's dag_progress uses it as the elapsed
fallback for a DAG that hasn't started yet. It just has one source now.

Removing the field left chrono entirely unused in model.rs, which is the
compiler confirming the payload had no other use for a timestamp.
2026-08-03 15:38:51 +02:00
atlas
24cd7f6d65 jobq: stamp created_at on every node, beside started_at/finished_at
`Node` carried two of the three lifecycle timestamps; the third lived on
hive-c0re's `NodeKind::Dag` container payload, a core-specific wrapper the
graph knows nothing about. Give it its real home so the container's copy
becomes redundant rather than load-bearing.

Not an `Option` like its neighbours: starting and finishing are events that
may never happen, but a node that exists was created. Modelling it as
optional would encode a state the graph cannot be in.

`hive-jobq-wire::GraphNode` gains the field in the same commit — it already
carries the other two, and without this one the value cannot reach a viewer
when the container's copy is deleted.
2026-08-03 15:33:12 +02:00
atlas
238a99ba08 jobq: restore the coordinator.md cross-reference
The \bJob\b pass rewrote a doc reference too: docs/coordinator.md::Job
queue became ::JobBuilder queue, pointing at a section that does not
exist. The section is still "## Job queue".
2026-08-03 15:26:46 +02:00
atlas
684f6da78e jobq: rename the builder parameters too, not just the alias
Renaming `pub type Job` fixed the definition and left every use site
reading `b` and `job` — including `job: super::JobBuilder`, where the
parameter still asserted it was a job while its type said otherwise.
The propagation is what the issue was about, so the parameters are the
half that matters at a call site.

Two spots deliberately untouched: `auto_update`'s `sort_by(|a, b| …)`
comparator, and the prose that means the job *queue* (main.rs's
"Job-queue scheduler", scheduler.rs's "not this module's job any more",
the "grown job rejected" log).

315 tests pass unchanged.
2026-08-03 15:26:46 +02:00
atlas
379c9bb570 jobq: the core alias is a JobBuilder, not a Job
A JobBuilder holds pending nodes that are not in the graph yet — it is
the thing you declare into. Naming the alias Job claimed it was the work
itself, and the name propagated into every parameter derived from it
(job: super::Job in run_node read as if it carried the DAG).

Prose uses meaning the job *queue* are left alone: main.rs's "Job-queue
scheduler" comment and the docs/coordinator.md reference.

315 tests pass unchanged.
2026-08-03 15:26:46 +02:00
atlas
8ce265fdf0 jobq: a transient is any running node naming the agent
Per mara on #2822: status is the only test. The agent comes off the
node's own payload rather than a declared Resource::Agent edge, so the
lease-exempt kinds (Prebuild, MetaSync) that name an agent without
holding its lease now light a pill — they are work on that agent.

Dropping that test breaks the one-pill-per-agent invariant, since
lease-exemption is exactly what lets one DAG build for an agent while
another holds its lease. Everything keyed by agent alone had to follow:

- reconcile_transients keys (agent, label) via TransientSeen, so a
  second pill cannot evict the first — and cannot lose its
  takes_container_down, which the crash watcher reads at clear time.
- transient_snapshot returns a Vec per agent for the same reason. The
  collapse was silent: a Prebuild could evict a StopForUpdate and its
  deliberate_stop, making an intentional stop report as a crash.
- crash_watch asks whether ANY running node expects the container down.
- the dashboard renders one row per node instead of one per agent.

takes_container_down never reached the frontend; no wire change needed.

315 tests pass unchanged.
2026-08-03 14:43:19 +02:00
iris
6809c782a3 docs: actually remove the Agent configuration section, not trim it
Missed mara'\''s reply on this PR before pushing two more commits on top
- she said remove, my first commit trimmed to two bullets, which is
still going into Matrix/GitHub as named subsystems. Deletes the
section outright; the two docs it pointed at (docs/tools/matrix.md,
docs/github.md) are still reachable from CLAUDE.md's reading path and
from docs/tools/README.md, just not named in the top-level README.
2026-08-03 12:59:28 +02:00
iris
3993f635d5 docs: add tools/ landing page
Part of hyperhive#1898 (b), the other missing docs subdir. Unlike
turn-loop/ this one has no single existing file that already covers
the whole directory - hivectl.md/hivectl-cli.md are genuinely
operator-facing (the operator's own host CLI), while bash.md/forge.md/
lifecycle.md/matrix.md/scheduling.md document the agents' own MCP tool
surface (a different audience: what the agent can do, not what the
operator does). Writes a new README.md rather than moving one,
splitting the link list along that line so the operator-relevant half
leads.
2026-08-03 12:57:00 +02:00
iris
04b274753b docs: give turn-loop/ a README.md landing page
Part of hyperhive#1898 (b): every docs subdir should have a top-level
README.md link, achieved by moving/renaming where an existing file
already fits the role.

docs/turn-loop.md already served as the hub + index for the three
sub-pages under turn-loop/ (claude-invocation.md, config.md, mcp.md),
so it moves wholesale rather than leaving a redundant top-level
pointer stub. Fixes every inbound/relative link across the repo
(top-level README.md, CLAUDE.md, docs/persistence.md,
docs/tools/scheduling.md, the sub-pages own back-link, hive-agent
README + doc comments, hive-agent/Cargo.toml, .prettierignore per-file
exemption entry) - grepped the whole tree for both turn-loop.md and
turn-loop/ to find every reference rather than trusting a partial
list.

nix fmt clean, cargo check -p hive-agent clean.
2026-08-03 12:55:18 +02:00
iris
c6af5a2abf docs: trim impl detail out of the top-level README
Per mara on hyperhive#1898: the top-level README should not go into
details about impl details or specific subsystems. Trims the
Multi-account Matrix support and GitHub account sections down to a
one-line pointer each.

The matrixAccounts option detail that was only ever documented in the
README (checked: docs/matrix.md is entirely about the host hive-matrix
container, not this per-agent option) moves to docs/tools/matrix.md
instead, next to the account parameter every matrix tool already
takes - the natural home, not a link to a page that never covered it.
2026-08-03 12:48:37 +02:00
atlas
05f84191fb jobq: split RebuildOpts into two rebuild entry points
RebuildOpts held one real parameter (relock) and one single-call-site
flag (graceful). The struct justified itself as swap-protection for two
positional bools; with graceful out of the signature there is nothing
left to swap.

graceful stays an internal switch rather than moving to the caller: it
re-parents the stop root (StopForUpdate goes from part_of(prebuild) to
part_of(signal)) rather than prepending nodes, so a caller could only
declare it by being handed the subtree's internals — and that nesting
keeps the agent lease continuous across the whole stop.

run_meta_lock no longer returns options: both fields were a pure
function of the sweep flag its caller had just passed in.

315 tests pass unchanged.
2026-08-03 09:16:09 +02:00
damocles
8db1cbd204 hive-forge: add pr edit, reusing issue edit's shared /issues endpoint 2026-08-03 01:54:28 +02:00
iris
aa149a7a62 builds: mount <hive-jobq-graph> directly, drop the hand-rolled queue renderer
Per mara's explicit steer on hyperhive#2812 ("also replace the build
queue tab with this component" + "graph fetching should live in the
component, not build.js" + "dont replicate the grouping by dag"):
R3BU1LD QU3U3 is now a mounted <hive-jobq-graph endpoint="/api/jobq/graph">
element. builds.js no longer renders the queue itself, does its own
fetch, or hand-rolls a per-root tree/roll-up/cancel-button — all of
buildNodeTree/topoSort/entryFingerprint/renderQueueEntry/
firstFailedNode/rebuildQueueRowCache/QUEUE_STATE_GLYPH/rollupState is
gone.

builds.js's remaining job is listening for the component's
hive-jobq-graph-update event (added to the component in the prior
commit) to keep a flat jobqNodes array in sync, and using that for the
two things the generic view doesn't render: the count-pill and the
live-log panel. On the rebuild_queue_changed SSE tick, calls the
mounted element's .refresh() instead of doing its own fetch — that
event still carries its own queue payload on the wire (tabs.js/SW4RM
still reads it for the badges, untouched), this page just ignores it
now.

Also removed, now genuinely dead: the two elapsed/finished-time
tickers (nothing produces the .rqe-when spans they targeted anymore),
stateSlug and isoToSecs (no callers left), fmtElapsed import (no
callers left).

New @hive/shared/jobq-graph.js export entry in packages/shared's
package.json, alongside the existing hive-tab-strip.js/hive-menu.js/
etc. pattern.

docs/web-ui/dashboard.md's R3BU1LD QU3U3 section rewritten to match:
mounted-component shape, no source/reason/cancel-button/deep-link on
rows (generic component has none), settled entries show their full
step tree (Done nodes aren't filtered off this wire, unlike the old
DagView projection).

Verified against real production data again (this hive's own live
/api/jobq/graph, now settled — no in-flight build at test time) plus
a synthetic running-build case to exercise findLiveBuild's happy path:
correct live-node detection (build_log_id gate), correct in-flight
root count. Confirmed the built dist bundle actually registers
customElements.define("hive-jobq-graph", ...) — the new shared
package export resolves correctly through esbuild.

Branch reused per mara's explicit "dont rework #3000 - continue
working on _this_ pr [#2996], it already has the component that
replaces 90% of build.js" — this ships as part of PR #2996, not a
separate PR.
2026-08-03 01:53:39 +02:00
iris
98957b48e9 hive-jobq-graph: dispatch hive-jobq-graph-update on every render
Fetching lives in the component (mara's steer on hyperhive#2812) — a
host that needs the raw node list for something the generic tree
doesn't show (a count badge, a live-log panel keyed on a specific
node) now listens for this bubbling/composed CustomEvent instead of
doing its own parallel fetch. Fires from both the self-fetch path
(refresh()) and a host-pushed render(nodes) call, so a listener sees
every update regardless of source.

Verified via jsdom: both paths dispatch with the correct nodes in
detail.
2026-08-03 01:53:39 +02:00
iris
123eec80c4 fix tracker-tag lint: reword #2893 reference to prose 2026-08-03 01:53:39 +02:00
iris
57c9ff6d6c shared: add <hive-jobq-graph>, a generic renderer for /api/jobq/graph
Shadow-DOM custom element (attachShadowCss, own <style>, matches the
<hive-dialog>/<hive-toast> pattern) that renders any hive_jobq graph
from the wire shape hive-jobq-wire serves: a tree from parent/child
structure, a state glyph per node, payload.label verbatim, and
payload.data as a generic key/value list. Never branches on what a
label or data key means, per the wire type's own opaque-payload
contract.

Fetch endpoint is a configurable attribute
(<hive-jobq-graph endpoint="/api/jobq/graph">) rather than
hardcoded, and a public render(nodes) method lets a host push
pre-fetched data (e.g. from its own SSE stream) instead. No transport
of its own beyond the initial self-fetch — refresh() is public so the
host decides its own refresh cadence.

Verified against real production data (61-node live rebuild-queue
graph, fetched from this hive's own /api/jobq/graph) via a jsdom
render: correct tree shape, correct state glyphs, correct data-list
presence count, both the self-fetching and render()-pushed paths,
and the empty-graph path.

Not wired into any page yet — the builds.js migration (hyperhive#2812)
follows once the open payload-gap question there is settled.
2026-08-03 01:53:39 +02:00
atlas
39299c6035 jobq: read DAG metadata off the container payload, drop DagMeta
DagMeta was three fields copied out of the NodeKind::Dag payload and read
back out in one place; its own docstring conceded the data's single home
is the payload. dag_view now destructures the payload directly.

The borrow stays immutable alongside the existing descendants() borrow,
so nothing needed cloning beyond the reason String the DagView field
already required.

315 tests pass unchanged.
2026-08-03 01:42:27 +02:00
atlas
6899f574f6 jobq: templates swallow the DagSpec layer
DagSpec described the graph the templates were about to build, one layer
below the templates themselves. Per #2972 the templates should be that
unit, so the spec type is gone and every declarer writes onto the job
builder directly.

- delete DagSpec<F> and its hand-written Debug impl
- submit(source, reason, declare: impl FnOnce(&Job)) replaces the
  pre-built-spec signature; submit_and_emit follows
- all six templates take &Job; the Source is now the caller's to pass,
  which spawn and approval_deploy previously hardcoded while the other
  four did not
- power_dag dissolves into stop_nodes/start_nodes/restart_nodes, which
  borrow their targets instead of owning them

315 tests pass unchanged.
2026-08-03 01:36:04 +02:00
damocles
96ef592fee hive-agent: /cancel matches claude's cmdline, not the kernel comm name 2026-08-03 01:33:09 +02:00
iris
3512e4b019 dashboard: hide forge links instead of guessing <hostname>:3000
Adds services.hyperhive.forge.publicUrl (defaults to the gateway vhost
URL when behindGateway=true, null otherwise). HIVE_FORGE_PUBLIC_URL is
now sourced from it instead of hardcoding https://${forge.domain}
whenever behindGateway is on.

The 4 frontend call sites that built a forge link from
state.forge_public_url now hide the link when that's absent, rather
than guessing http://<browser-hostname>:3000 — a guess that's only
correct by accident once the operator isn't on plain localhost. Fixes
the dashboard H0M3 tile, per-agent-row forge links + agent menu, the
approval-queue PR link, and the per-agent page's own meta-nav forge
link (found during this pass, same defect, not in the original
3-site inventory).

Docs + doc-comments updated to match.
2026-08-03 01:21:11 +02:00
damocles
68560215bd hive-c0re: deliver scheduled prompts via push_todo, not a broker message 2026-08-03 01:08:24 +02:00
damocles
7c6f108716 coordinator: push_todo returns Result so callers can see delivery outcome 2026-08-03 01:08:24 +02:00
atlas
b8627e7e94 jobq-wire: take utoipa's chrono feature from the workspace
The per-crate features = ["chrono"] override was a local deviation with a
comment defending it. The feature belongs in the workspace list, where every
crate sees the same utoipa.
2026-08-03 00:39:14 +02:00
atlas
fe0906c043 jobq-wire: name every outcome, keep the enum spellings, document the schema
Three things, all from review:

Accepted outcomes were built from a hand-listed [Done, Failed, Cancelled,
Skipped] array. Exhaustive today, silently short the day someone adds a
variant — the new outcome would vanish from every edge that accepts it.
BitFlags::ALL asks the type instead.

TerminalState carried rename_all = "snake_case" while its sibling State did
not, so one enum shipped "done" and the other "Done". A rename is a second
spelling of a name that then has to be kept in agreement by hand; both now
serialise their variant names verbatim. Nothing else reads TerminalState off
a wire, so no consumer moves. GraphDep's tag values likewise.

The endpoint documented its body as serde_json::Value, which tells a spec
reader nothing. hive-jobq-wire now derives ToSchema. State and TerminalState
are foreign types here and utoipa stays out of the scheduler crate, so the
schema points at local mirror enums. A mirror that drifts is worse than none:
the conversions are exhaustive (a new upstream variant fails the build) and a
test asserts each documented name equals the serialised one, since an
exhaustive match still compiles when only the spellings diverge.
2026-08-03 00:39:14 +02:00
atlas
7966d5eb66 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.
2026-08-03 00:39:14 +02:00
atlas
f357f98867 c0re: serve the jobq graph generically at /api/jobq/graph
`graph_node` projects a `hive_jobq::Node` onto the wire type from the
parent commit: everything the crate records, with hive-c0re's own fields
(`agent`, `approval_id`, `inputs`, `build_log_id`) collected into the
opaque payload slot instead of standing as named columns. A node with
nothing domain-specific to say serialises no `data` key at all, so the
slot costs nothing when it is unused.

`graph_dep` turns a `DepWhen` into the set of outcomes that satisfy the
edge by asking it about each of the four terminal states, rather than
leaking the bitflags representation onto the wire.

`Resource::wire_name` gives the resource vocabulary a string form —
hive-jobq is generic over the resource type, so a viewer that renders any
graph cannot be handed this enum. The `agent:` prefix keeps per-agent
leases from colliding with a global resource sharing an agent's name.

`graph_snapshot` deliberately reuses `visible_dags` for retention: every
live group plus the newest terminal ones. Serving the raw graph would
grow without bound — evicted groups' nodes linger until bounded pruning
lands. Within a retained group nothing is filtered: group roots ride as
ordinary nodes, and `Done` nodes stay, which is the projection defect
behind the "rebuild shows a single node" report.

The endpoint lands in the same commit rather than after it. Without a
consumer the whole projection is dead code, and a wire type nobody
produces cannot be reviewed for whether it says the right things.

Its OpenAPI body is `serde_json::Value`, matching `api_state`: no type in
`hive-host-sock` derives `ToSchema`, and that crate stays dependency-lean
on purpose.
2026-08-03 00:39:14 +02:00
atlas
792d7cb304 hive-host-sock: a jobq graph wire type that doesn't know what a node is
`jobs::NodeView` can only ever display hive-c0re's queue. Five of its
fields are domain knowledge: `approval_id` is only ever on a
`DeployWindow`, `inputs` only on a `MetaLock`, `build_log_id` only on the
nix-heavy kinds, `agent` is derived from the payload, and `kind` is a
payload tag consumers branch on. A component built against that shape
cannot render a second jobq.

`graph::GraphNode` is `hive_jobq::Node` with both generics erased: the
crate's own field set, with everything domain-specific in one opaque
`payload.data` slot the consumer renders without branching on. That is
the crate boundary made visible — hive-jobq owns structure, its host owns
meaning — and it is the same split #2957 drew inside the code.

Two details that are easy to get wrong and are pinned by tests:

`GraphDep::Node` carries `accepts` as the **set** of terminal outcomes,
not a strong/weak flag. A template emits its tails as a pair edged on the
same upstream node, and the only thing telling them apart is which
outcomes each accepts; collapsing that renders two structurally different
nodes identically.

There is **no roll-up field**. A group root ships as an ordinary node
with `parent: None`, and its own `state` is its subtree's answer —
`Finishing` means "own logic done, children still running", the terminal
states are the rolled-up outcome. A separate field would be a lossier
copy: `DagView::rollup_state` flattens `Running` and `Finishing` into
one, which is exactly the distinction a viewer wants.

`State` and `TerminalState` are re-exported from `hive-jobq` rather than
redeclared, so they cannot drift from the scheduler that produces them.
2026-08-03 00:39:14 +02:00
iris
7fba2d6919 docs(ci): add a For operators section
ci.md mixes a genuinely short operator bootstrap step with deep
auto-registration/container-design internals and a full security
writeup an operator does need to read once, but not first.

Adds a short synthesis at the top: enabling is one nix option, the
unsandboxed-build trust tradeoff (fine for a trusted team, gate it if
you take fork PRs), and the disk-maintenance recipe (CI shares the
host nix store with no auto-GC of its own). Every claim checked
against the existing detail further down in this same file.

Part of hyperhive#1898.
2026-08-03 00:30:57 +02:00
iris
7892304827 docs(persistence): add a For operators section
persistence.md is exhaustive schema/impl reference (sqlite table
columns, systemd unit internals, marker files, btrfs subvolume
mechanics) with the one thing an operator actually needs - what
happens to my stuff when I destroy or purge an agent - scattered
across several sections rather than answered up front.

Adds a short synthesis at the top: destroy keeps everything
(revivable, no re-login), purge wipes it all (no undo), approvals and
questions never age out, message history vacuums acked rows at 30
days, an agent's own /state/ and claude login survive every restart/
rebuild, and the root agent auto-recreates if it's ever destroyed.
Every claim checked against the existing detail further down in this
same file before writing it.

Part of hyperhive#1898.
2026-08-03 00:23:16 +02:00
damocles
601cd295e5 docs: describe the push_todo lifecycle-notice migration 2026-08-03 00:04:24 +02:00
damocles
e3729d148f hive-sh4re: drop the HelperEvent variants push_todo replaced 2026-08-03 00:04:24 +02:00
damocles
c4c35bafe3 hive-c0re: push NeedsLogin/LoggedIn as todos, not broker messages 2026-08-03 00:04:24 +02:00
damocles
548936ab70 hive-c0re: push Destroyed as a todo, not a broker message 2026-08-03 00:04:24 +02:00
damocles
1f448a7818 hive-c0re: push Killed as a todo, not a broker message 2026-08-03 00:04:24 +02:00
damocles
c41d2d201d hive-c0re: push Rebuilt as a todo, not a broker message 2026-08-03 00:04:24 +02:00
damocles
e52ceef2b6 hive-c0re: push ConfigReady as a todo, not a broker message 2026-08-03 00:04:24 +02:00
damocles
836284a22d coordinator: add push_todo mechanism for host-to-agent lifecycle notices 2026-08-03 00:04:24 +02:00
iris
2fb0d08274 docs(approvals): fix deny-notification conditionality
argus review: the submitting agent is always notified when its request
is denied (HelperEvent::ApprovalResolved fires unconditionally); only
the reason text is optional, and only from the dashboard prompt (not
the CLI, which has no reason argument at all). Also note that
cancelling the dashboard prompt aborts the whole deny, not just the
reason.
2026-08-02 23:59:53 +02:00
iris
23ff147953 docs(approvals): add a For operators section
approvals.md (655 lines) is genuinely dense implementation reference
(webhook mechanics, DAG phase names, db column names, git-ref rollback
internals) with nothing written for an operator deciding whether to
click approve. Unlike web-ui/, this is a single top-level file with no
subdirectory to give a separate landing page to, so the fix here is
in-file: a "For operators" section right after the intro, covering
what actually shows up on the dashboard (or hivectl CLI) and what each
approval kind does when you click approve or deny, before the existing
implementation detail.

Every claim in the new section was checked against the rest of this
file plus dashboard.md and hivectl-cli.md rather than assumed - caught
one real error before pushing (hivectl approvals list doesn't exist,
the verb is `pending`) and one incomplete claim (denial reasons are
dashboard-only with an optional prompt; the CLI deny has no reason
argument).
2026-08-02 23:59:53 +02:00
iris
052767c25b docs(github): move impl detail below the operator-facing sections
github.md mixed operator content (enabling, provisioning, security)
with deep implementation detail (the gh wrapper/credential-helper
mechanics, the notification poller's internals) in file order, so an
operator reading top-to-bottom hits internals before finishing the
part they actually need.

Pure reorder, no rewrite: Enabling -> Provisioning -> Security (all
operator-facing) now come first: How the agent uses it and
Notifications (both pure impl detail) move to the end, with a one-line
marker between them. Every word of existing content is unchanged, only
section order moved - lowest-risk shape for a file like this with no
subdirectory to split into (see hyperhive#1898).
2026-08-02 23:54:55 +02:00
iris
53ce83ff56 docs(web-ui): fix Stats/Peers/Settings tab-strip miscategorization
argus review on hyperhive#2986: the README listed Stats/Peers/Settings
alongside Permissions/Schedules as dashboard tabs. Checked the actual
frontend rather than trusting dashboard.md prose (which is internally
inconsistent on this - some section headers say "tab" for things that
turned out not to be):

- stats.html and settings.html are real separate bundles
  (frontend/packages/dashboard/src/{stats,settings}.{html,js}) - same
  shape as builds.html/core.html/logs.html, moved to the "own page"
  list.
- Peers is neither a tab nor a page - swarm.js::renderPeerHives
  confirms it is a card list rendered inside the SW4RM tab
  (#peers-block/#peers-section), gated on state.peer_hives being
  non-empty. Folded into the SW4RM bullet instead of listing it as its
  own item anywhere.

The dashboard tab strip is genuinely just four: SW4RM, Y3R C4LL,
P3RM1SS10NS, SCH3DUL3S.
2026-08-02 23:30:39 +02:00