Review feedback: the metrics pair had nothing writing into it, and it sat
outside the switch that turns on every other swarm-wide service.
The collector now exports to VictoriaMetrics as well as upstream — a
fan-out, not a choice: a local store is for looking at this swarm, an
upstream is for whoever aggregates across swarms.
That makes a local store a complete destination on its own, so
`otel.endpoint` is no longer required when it runs here; a hive with
neither is still refused. The assertion only ever relaxes, so every
config that evaluated before still does.
`enableRequiredServices` now derives both halves, alongside matrix,
authelia and nats. They derive together because a store with no UI is
unreadable and a UI with no store is empty.
New @hive/shared/jobq-rollup.js (JobqRollup.tsx + jobq-rollup.css),
mirroring JobqGraph's shape exactly: JSX use plus an imperative
mountJobqRollup(container, props) for a plain-.js call site. Fetches
Vec<hive_jobq_wire::StateCount> off `endpoint`, sums Running+Finishing
roots as "running" and Pending roots as "queued", renders nothing when
both are zero. Optional `queueHref` adds a "view queue -> " link.
Swapped dashboard's hand-rolled queue-summary banner (swarm.js) over to
this component instead of keeping two parallel implementations - same
"one shared component" pattern JobqGraph already set for the rebuild
queue tree view. Mounted once into a new #jobq-rollup-section, kept as
a sibling of (not inside) #containers-section since that section gets
replaceChildren()-wiped on every container-state render, which would
tear down and remount a Preact tree on every tick. Refreshed via the
mount handle's .refresh() on rebuild_queue_changed, same as builds.js's
JobqGraph handle.
Also mounted in swarm-ui's /jobs page, above JobqGraph, with no
queueHref (a link back to the page you're already on is noise) - the
literal ask on hyperhive#3364.
Verified: npm run build (whole workspace) and swarm-ui typecheck both
clean, comment-block + issue-ref lints run manually, headless-chromium
screenshots of both the dashboard SW4RM tab and swarm-ui's /jobs page
against mocked /api/jobq/rollup payloads - banner renders identically
in both, with and without the queue link as expected.
Review: the public keys are not an exception, the paragraph was
incomplete. It is private key material and access tokens that are
file-based so they stay out of the nix store; public material in a
public store is fine.
Fixes the rule itself, which removes the need for the special case the
previous commit added.
secrets.md is the page that answers "who mints this and where does it
go" per topology, and the callout nkeys had no row. Adds one, plus the
`nk` invocations an operator needs on every topology that is not
all-local.
The note exists because the change provokes a question the page would
otherwise answer wrongly: this page opens with "a secret is a path,
never a value", and the queue takes two public keys as values. A reader
who meets those after reading that line concludes the page is stale.
fmt_args_generic's generic array handling collapsed `ids: [4]` — the
count — since mark_todos_done had no dedicated match arm. Added one,
matching the file's existing per-tool pattern (extracted into its own
helper to stay under the 100-line clippy limit on fmt_hyperhive_tool).
Ports the shadow-DOM <hive-jobq-graph> custom element
(frontend/packages/shared/src/jobq-graph/) to a Preact component
(JobqGraph.js) shared by the dashboard and swarm-ui, per hyperhive#3310.
- JobqGraph.js: written with plain h() calls (no JSX) so the same file
compiles unmodified under both the dashboard's text-loader CSS config
and swarm-ui's JSX config. Exports `JobqGraph` for JSX use and
`mountJobqGraph(container, props)` for the dashboard's non-JSX
imperative mount, returning a `{refresh(), update()}` handle matching
the old custom element's public surface. Same rendering contract as
before: indented state tree, payload.label verbatim, payload.data as
a generic key/value list, "waits on: <label>" text for Node-kind deps,
per-state filter checkboxes, optional cancel button.
- jobq-graph.css: light-DOM adaptation of the old shadow-scoped
stylesheet (:host -> .jg-root, otherwise unchanged).
- dashboard/src/builds.js: local mountJobqGraph() renamed to
mountRebuildQueue() to avoid colliding with the newly-imported shared
mountJobqGraph; cancel handling is now a plain onCancel callback
instead of a DOM CustomEvent listener (no shadow boundary to cross
anymore).
- dashboard + shared package.json: added preact as a dependency (matches
swarm-ui's existing pin, 10.29.8) - the dashboard was a vanilla-JS MPA
with no Preact/JSX pipeline before this.
- Removed the old hive-jobq-graph.js/.css entirely (confirmed via grep
it had exactly one consumer, dashboard/src/builds.js, so this is a
clean swap, not parallel maintenance of two implementations).
- Updated stale doc-comment references to the old element name in
builds.html, tabs.js, swarm.js, docs/web-ui/dashboard.md, and
hive-c0re/src/job_queue/mod.rs.
Verified: npm run build (whole frontend workspace) and npm run
typecheck (swarm-ui) both clean; cargo build/clippy/test -p hive-c0re
all clean (331 tests, 0 failures); headless-chromium screenshot of
/builds.html against a mock GET /api/jobq/graph payload confirms full
visual/behavioral parity with the old custom element (tree, filter
checkboxes, cancel buttons, error text, waits-on line, data list, live
build log panel).
This covers the dashboard-replacement half of hyperhive#3310 only. The
swarm-ui half (rendering the CreateAgent DAG on the agent-creation page)
is downstream of hyperhive#3306/#3124 landing - no swarm-ui page exists
yet to mount it in.
The section carried a "nothing publishes yet" note that is now false, and
said nothing about the one thing an operator has to do.
Written to the reader's question rather than the author's: what to set,
what defaults on an all-in-one host, what has to be carried by hand to a
hive that is not the swarm host, and where to look when a hive goes quiet.
The identity and the publish cadence are stated because they constrain the
staleness threshold an operator picks; the mechanism behind them is not.
Follow-up to the plumbing removal: docs/swarm/README.md gets the
biggest rewrite (drops the whole "Fingerprint format" section, fixes
the runtime-effects list, the WireGuard config example + "what the
mesh does" bullet), docs/gateway.md and hive-gateway/options.nix drop
4 "needs no certFingerprint" mentions, swarm-peers-removed.nix's
migration-warning text no longer tells an upgrading operator to carry
a field over that no longer exists, swarm.nix/swarm-wireguard.nix/
swarm-controller.nix/swarm-controller's main.rs get comment fixes
where they described the now-removed HYPERHIVE_PEERS shape. Also
caught one more stale "peer hives" mention in docs/web-ui/README.md's
SW4RM tab description that the first pass on this issue missed.
Follow-up to the peer-hives removal: docs/web-ui/dashboard.md drops
the whole "P33R H1V3S" section + its stale #peers hash and tab-strip
mentions; docs/swarm/README.md's runtime-effects list and
cross-references point at swarm-ui's roster page instead. Nix comments
in hive-c0re/environment.nix, swarm.nix and swarm-wireguard.nix that
described HYPERHIVE_PEERS/certFingerprint as dashboard-consumed are
updated to say what's actually still true (agent identity only).
Deliberately NOT removed: the `certFingerprint` nix option itself.
It has no remaining Rust consumer, but deleting a documented,
potentially already-set option is a bigger, breaking change than
removing dead display code — flagged in docs/swarm/README.md with a
note instead of silently dropped, so an operator or reviewer can make
that call explicitly rather than have it made for them here.
Per mara on the PR: less implementation detail, more operator focus. The
section argued why the design is shaped as it is - a cache would
disagree with itself, freshness must be derived rather than stored, the
timestamp is the server's - which is reasoning a reader of this page does
not need and cannot act on.
It now answers what an operator actually asks: what each freshness value
means they should do, what to set the threshold to and why that number,
what a 503 tells them, and that nothing publishes yet so an empty board
is expected rather than broken.
The design rationale is not lost, it is just homed where it belongs: the
status module's own doc comment, next to the code it constrains.
The controller connects to the swarm queue as its own client and serves
what each hive last said about itself at GET /api/hives/status.
THE QUEUE IS THE STORE. A hive publishes into the `hive-status` JetStream
KV bucket (history 1) and the controller reads it per request, keeping no
copy. A cache here would be a second answer to the same question, free to
disagree with the first, and the disagreement surfaces as a hive reading
healthy on a dashboard while the bucket says otherwise. Whichever side
arrives first creates the bucket; both want the same shape.
Rows come from the roster rather than from the bucket, so an empty bucket
renders as a swarm nobody has heard from instead of a healthy one, and
`never_reported` stays distinct from `stale` - went quiet is a fault,
never spoke is usually a deployment that has not happened. Freshness is
derived at read time and never stored as a flag, because a stored
`healthy` boolean goes stale silently the moment nothing arrives, which
is the failure this endpoint is designed against. The timestamp is the
NATS server's, applied when the value landed, so a publisher cannot make
itself look fresher than it is.
Authentication is per connection attempt, not per process. Authelia
issues `client_credentials` tokens that expire in 3599s, and auth happens
at CONNECT, so a long-lived connection is fine but a reconnect an hour
later needs a token minted an hour later. `with_auth_callback` is re-run
by async-nats for each attempt, which handles expiry by construction
rather than by a timer - the alternative fails in the way this subsystem
exists to prevent, with the controller still serving while its data
quietly stops updating.
Three failure shapes are deliberate:
- A half-set environment is fatal; an absent one is not. Silently
behaving like an unconfigured host is how every hive ends up reading
`never_reported` with nothing to point at.
- The endpoint answers 503 rather than an empty list when the store
cannot be read. "I cannot reach the store" and "every hive is silent"
are different answers, and rendering the second turns a local fault
into an apparent swarm-wide outage.
- `retry_on_initial_connect` makes the daemon and the queue bootable in
either order, and the status handler refuses when the client is not
Connected rather than issuing a request into it - a request made in
that window does not fail, it waits, so every poll would hang and
learn nothing. `Pending` is the state a never-connected client is in,
which is why the test is `!= Connected` and not `== Disconnected`.
The rendering rules are a pure function over a map, so the semantics are
tested against a table rather than against a running server. The KV read,
the credential rotation and the 503 paths are covered behaviourally
instead: a real NATS server with a rotating token endpoint, asserting
that the controller recovers only when the credential rotates, and
mutation-tested by holding the credential wrong for the same window.
Single 🔗 button in the Shell header (direction 1 of 3 proposed on the
issue) — one fixed element regardless of link count, so it can't
clutter as the list grows, and visible on every route since Shell
wraps all of them. Opens a popover listing whatever GET /api/links
returns (icon + label, opens in a new tab); hidden entirely when the
list is empty, same "don't render a dead affordance" rule the old
dashboard's H0M3 tiles follow for Forge/Matrix.
Docs: docs/swarm/ui.md gets a short section on the feature + the
contribute-your-own-entry idiom.
Verified: npm run build + npm run typecheck clean, headless-chromium
screenshots of both the closed and (temporarily forced open for the
screenshot only) open states, pixel-sampled to confirm the popover is
actually using the dark theme vars and not just looking that way in a
downscaled preview.
Same bug class the facts sweep hunted for across everything else —
hive-c0re.nix is a module directory now
(nix/host-modules/hive-c0re/), environment.nix is where
HYPERHIVE_HIVE_DOMAIN is actually set. Caught doing a final grep
across the merged sweep for the known bad patterns before pushing.
- MAX_AGENT_NAME lives in lifecycle/mod.rs, not bare lifecycle.rs
- agent_server::handle_send -> socket_server::handle_send (no agent_server
module exists)
- AgentMeta response is missing matrix_accounts in the field list
- tool-group table missing list_containers/ack_until (added since written)
and the forge/web_tools groups entirely
- #[tool] fns live on AgentServer in hive-agent-mcp/src/mcp/mod.rs, not a
HiveServer in a nonexistent hive-ag3nt/src/mcp.rs; ToolGroup::tools()
lives in hive-sh4re/src/permissions.rs, not lib.rs
- Capability + as_str also moved to permissions.rs; capability-check
handler list was stale (agent_server.rs/mcp.rs don't exist)
- async-forms listener is bindAsyncForms in frontend/packages/shared,
not assets/tabs.js + assets/app.js
- 'rebuild is the reconcile verb' described a monolithic lifecycle::rebuild
that no longer exists — it's job_queue::templates::rebuild building a DAG,
and flake.nix is no longer regenerated host-side on rebuild at all
- meta::sync_agents signature (now takes &HiveEnv, not 3 loose params)
- lifecycle.rs -> lifecycle/host_config.rs bare-file ref
- nixosConfigurations.manager -> .ruth (no 'manager' name in flake.nix)
- manager socket path/owner: no /run/hyperhive/manager/ or manager_server,
it's socket_server::start_manager on the standard per-agent runtime dir
- Spawn approval also fires ApprovalResolved (finish_approval always does,
for every kind), not push_todo instead of a HelperEvent
- /agents/<n>/applied.git -> /applied/<n>/.git (matches the actual RO bind)
- lifecycle::rebuild / auto_update::rebuild_agent don't exist anymore;
the reconcile verb is job_queue::templates::rebuild
Verified against nix/host-modules/hive-gateway/*.nix + hive-matrix.nix +
hive-forge/default.nix:
- services.hyperhive.gateway.enable was removed (gateway is now
unconditional) — fixes the Dashboard link shape + Firewall posture
sections, which described a gateway-off mode that no longer exists.
- gateway.selfSignedTls is a removed option (fails eval), not a
deprecated-and-ignored one.
- The forge rootUrl auto-derivation now uses https:// behind the
gateway (httpsPort), not http://.
- Vhost map + discovery-flow + local-dev sections used matrix.<hive> /
forge.<hive>; current defaults are chat.<swarm-domain> and
forge.<swarm-domain> (both swarm-domain-rooted, per hive-matrix.nix's
gatewayHost and hive-forge's domain option).
- hive-c0re.nix is now a directory (nix/host-modules/hive-c0re/); pointed
the HIVE_FORGE_URL reference at the actual file, environment.nix.
Verified against nix/host-modules/hive-matrix.nix: gatewayHost's current
default is chat.<swarm-domain>, not matrix.<hive>, so the GUI mount point
and fluffychat client line were stale. Also rewrote the Assertion
rationale section, which named an assertion (hyperhiveDomain/serverName)
that doesn't exist in this module and missed the three SSO assertions
that do.
Verified against nix/host-modules/hive-gateway/dnsmasq.nix + gateway.localNames
contributors (hive-forge, hive-matrix, swarm-authelia): forge and the matrix
gateway vhost are swarm-domain names (forge.<swarm-domain>, chat.<swarm-domain>),
not sub-domains of the hive's own domain as the doc previously claimed.
Verified against hive-agent/src/stream_enrich.rs and frontend/packages/agent/src/app.js:
- per-tool icon/summary/category classification moved server-side to
stream_enrich.rs::enrich() a while ago; fmtToolUse/fmtArgsGeneric/toolIcon no
longer exist in app.js -- the client now just dispatches on the backend-stamped
_icon/_summary/_category/_body/_body_type fields. Rewrote the 'Renderer dispatch'
and 'Salient-arg formatting' (was 'fmtToolUse patterns') sections to match, and
retargeted the per-tool table's 'Everything else' row to fmt_args_generic.
- Write is no longer a rich diff row -- is_rich_tool() doesn't include it, and
fmt_builtin_tool formats it identically to Read (flat, no count). Only Edit
still gets the rich diff.
- TodoWrite and built-in Bash aren't in ALLOWED_BUILTIN_TOOLS (mcp_config.rs) any
more than Task is -- flagged both as dead paths for consistency with the
existing Task annotation.
- fixed stray 'ask* has an inline answer form' table note that contradicted the
doc's own later 'Inline ask-operator answer' section (no such form exists).
- terminal.css path: frontend/packages/shared/src/terminal/terminal.css, not
.../shared/src/terminal.css; marked npm dep is v18.x, not v4.x.
Verified against hive-agent/src/web_ui/state.rs: the struct is AgentLink
(AgentLinkKind for kind), not NavLink -- NavLink doesn't exist anywhere in
the Rust or frontend source.
Verified against frontend/packages/dashboard/build.mjs, hive-c0re/src/dashboard/mod.rs,
and hive-agent/src/web_ui/mod.rs:
- dashboard ships 9 page CSS bundles + colors/theme/common, not 4
- dashboard SSE routes are /api/dashboard/stream + /api/dashboard/history, not
/dashboard/stream + /dashboard/history
- per-agent bind_with_retry has a MAX_BIND_ATTEMPTS=12 cap (gives up), unlike the
dashboard's genuinely uncapped retry loop
- assets/swarm.js, assets/call.js -> swarm.js, call.js (no assets/ dir exists)
- server-warnings banner: was described as disk-pressure-only; it's now
a push-based registry with multiple producers (forge/CI boot
failures, agent-state warnings), and shown on more standalone pages
than listed
- browser tab title: actual default/format is "hyperhive // h1ve-c0re",
not "hive / c0re"
- SW4RM container tree: ContainerView.parent comes from
agent_config/topology.rs, not the ambiguous dashboard/topology.rs
(which only holds set-parent endpoints)
- T00L GR0UPS: tool-group enumeration was missing "forge"; root agent's
default (MANAGER_DEFAULT) is not literally "all groups" -- it excludes
forge and web_tools
- ST4TS moved to its own /stats.html page a while back; section still
called it a dashboard tab
- P33RS is a headline block within the SW4RM tab, not its own tab --
contradicted the doc's own chrome-header description
- active model badge: reads hyperhive-harness.json's active_model
field, not a bare harness/hyperhive-model file
- themed dialogs: modal.js lives in the shared package
(@hive/shared/modal.js), and themedToast takes (message, opts) not
a single options object
- graceful stop: backend uses a Signal/Drain job-queue node pair, not
a "GracefulStop" transient kind; hivectl flag is `stop --graceful`,
not a bare `--graceful`
- container_state_changed event: named a nonexistent
auto_update::rebuild_agent as an emit site; actual sites are
actions::approve and the job queue's own node execution
Verified against hive-c0re/src (dashboard/, stats/, job_queue/,
container_view.rs), hive-sh4re/src/permissions.rs, and the dashboard
frontend package (tabs.js, stats.js, swarm.js, builds.js, modal.js).
No structural/prose changes beyond what the fact corrections required.
Verified against hive-agent/src/paths.rs: harness-internal files
(hyperhive-events.sqlite, hyperhive-turn-stats.sqlite, hyperhive-model)
live in the separate harness dir, specifically kept out of the
agent-visible state dir.
Verified against hive-sh4re/src/manager.rs and docs/approvals.md: only
ApprovalResolved/ContainerCrash/NeedsUpdate/QuestionAsked/QuestionAnswered
are HelperEvents delivered as inbox messages. spawned/rebuilt/killed/
destroyed/needs_login/logged_in/config_ready aren't HelperEvent variants
at all — they deliver via Coordinator::push_todo/push_todo_submitter
(the in-container todo socket), not the broker inbox.
Verified against hive-c0re/src/agent_config/topology.rs: there is no
parent_of function; <parent> sentinel resolution happens in
topology::resolve_recipient.
Verified against nix/agent-modules/claude-settings.nix (claudeMarketplaces
and claudePlugins defaults both gained a second entry — the hive's own
claude-plugins marketplace and base@hyperhive plugin — that the doc never
picked up) and hive-agent/src/web_ui/state.rs (dashboardLinks is read by
the harness's own per-agent web UI, not hive-c0re / the operator
dashboard).
Verified against Cargo.lock (rmcp resolves to 2.2.0, Cargo.toml pins
"2") and hive-core-agent-sock/src/lib.rs (the Wake variant lives on
Request there; hive_sh4re::AgentRequest doesn't exist).
Verified against hive-agent/src/harness_state.rs (context_window_tokens
lives there, only imported into events.rs) and hive-agent/src/paths.rs
(config_dir() defaults to /run/hive-config, deliberately separate from
/run/hive/ which holds the per-agent socket).
Verified against hive-agent/src/main.rs and hive-core-agent-sock/src/lib.rs:
there is no AgentRequest/AgentResponse or ManagerRequest/ManagerResponse
alias anywhere in the tree — hive-agent uses hive_core_agent_sock's
unified Request/Response enum directly.
Verified against hive-c0re/src/lifecycle/host_config.rs (function lives
in the host_config submodule, not bare lifecycle::) and
nix/host-modules/hive-matrix.nix (the working Accept-header SPA
fallback lives there, serving fluffychat at the vhost root /index.html,
not a /matrix/ path prefix in a nonexistent hive-gateway.nix).
hive-c0re/src/dashboard.rs became a module directory
(hive-c0re/src/dashboard/) a while back; three more references to it
as a single file survived the #3281 sweep (conventions.md x2,
web-ui/shape.md's StateSnapshot pointer). Same bug class the sweep
was hunting for, argus caught it in review.
Two follow-ups the per-file sweep workers flagged but couldn't fix
themselves (either not their assignment, or not visible from their
worktree):
- web-ui/dashboard.md's P33RS tab named dashboard.rs, which no longer
exists (hive-c0re/src/dashboard/ is a module dir now); the function
moved to state_snapshot.rs.
- approvals.md and conventions.md used hive_ag3nt:: as a Rust
module-path prefix. hive-agent is a bin-only crate (no lib target),
so that prefix never resolved to anything; also fixed the
hive-ag3nt/prompts/system.md path to the real hive-agent/prompts/.
All three verified directly against the current source tree, not
guessed.