The live run-view streamer returns a single snapshot of whatever act_runner
had buffered by poll time. For a job that's still running that's fine (no
persisted log exists yet), but for a job that already finished it silently
truncates wherever the snapshot happened to stop -- the original bug: a
failed nix flake check run returned only the ~90s eval-phase prefix and
dropped the actual build-phase error entirely.
Flip the priority: try the durable persisted-log download first (complete
once it exists), fall back to the streamer only when nothing's persisted
yet (run still live). --step still goes straight to the streamer since the
persisted log is flat and doesn't honor per-step framing.
mara: "no, do split packages for the separate daemons" — instead of
grouping all daemon/harness/MCP bins into one hyperhive-daemon
derivation, each bin (hive-c0re, hive-priv, hive-agent,
hive-agent-mcp, hive-agent-wake, hive-bash-daemon, hive-bash-mcp,
hive-matrix-daemon, hive-matrix-mcp, hive-metric) is now its own
named package + overlay output, matching the existing hivectl /
hive-forge split. harness-base.nix's ExecStart/command lines now
point at the specific pkgs.hive-* each ExecStart needs, and
environment.systemPackages only carries the two bins actually
PATH-looked-up in-container (hive-agent-wake, hive-metric) instead
of the whole bundle.
Agent containers pulled in `pkgs.hyperhive` (packages.default: daemonBins +
hivectlPkg + hiveForgePkg) via environment.systemPackages and every
harness ExecStart. That shipped hivectl (a host-admin CLI dialing the
host admin socket, unreachable from inside a container, wrapped with
wireguard-tools for `hivectl wg`) plus a redundant second hive-forge
copy (already provided separately via hive-forge-tools.nix) into every
single agent's closure.
Expose the existing daemonBins split as packages.<system>.daemonBins
(mara: 'there should be split nix outputs already, use those' — no new
grouping derivation) + a hyperhive-daemon overlay entry, and repoint
harness-base.nix's systemPackages entry and all agent-run ExecStarts
(hive-agent, hive-agent-mcp, hive-bash-daemon, hive-matrix-daemon) plus
the bash/matrix MCP bridge commands at it instead of the full bundle.
nix flake check: daemonBins + agent-base-toplevel + ruth-toplevel all
evaluate cleanly (docs derivation failure is a preexisting dirty-tree
issue, unrelated).
Argus flagged the doc comment overclaiming no risk of a stale
suppression leaking onto a reused task id — narrow the wording to
match the acknowledged race in run_task's completion handler.
A `status` or `run` call whose inline `wait_seconds` poll observes a
terminal task hands the caller the full result in that same tool
response. The completion wake fired unconditionally regardless,
producing a redundant `bash-task-<id>` inbox message for information
the agent already has.
Add a one-shot, in-memory wake-suppression registry in hive-bash-mcp's
runner: `wait_for_task` (shared by both BashRun's and BashStatus's
inline-wait paths) marks a task's wake suppressed the moment it
observes a terminal state; `run_task`'s completion handler consumes
that flag before calling `send_wake` and skips the wake if set.
In-memory only (daemon restart wipes it) — fine, since a task still
running across a restart is separately marked `interrupted` on boot
and gets its own fresh wake. Narrow best-effort race window between
the terminal write and the wake send; acceptable given this daemon's
existing best-effort delivery tolerance elsewhere.
docs/tools/bash.md updated to describe the new suppression behavior.
- docs/network.md: LOCAL_ADDRESS is now empty (not deterministic-IP);
containers use dhcpcd + dnsmasq DHCP pool, not hash-derived static IPs
- nix/modules/hive-ci.nix: 'service containers' → 'all containers'
in the dnsmasq DHCP pool comment (agents also use the same pool)
All agent containers now receive their bridge IP dynamically via DHCP
from the dnsmasq pool instead of a hash-derived static address:
- nix/templates/harness-base.nix: networking.useDHCP = true
- nix/modules/hive-gateway.nix: expand DHCP pool to full usable range
(.2 to .254 on /24) — was last-14-IPs-only
- hive-sh4re/src/priv_proto.rs: remove agent_ip from NetworkIsolation
- hive-c0re/src/lifecycle/mod.rs: drop agent_network_ip + DHCP_POOL_SIZE
- hive-c0re/src/lifecycle/host_config.rs: remove agent_network_ip call
- hive-priv/src/main.rs: LOCAL_ADDRESS= empty (DHCP assigns IP);
HOST_ADDRESS still set so nixos-container installs default route
before the DHCP lease arrives
- nix/dhcp-pool-size: deleted (no longer needed)
The nix/dhcp-pool-size single-source-of-truth file and all associated
Rust/Nix dual-constant plumbing are gone — there is no static map.
bridge_gateway_ip() is retained (still needed for HOST_ADDRESS).
Closes#2363
The DHCP pool exclusion is now by construction (agent_slots = usable -
dhcpPoolSize), not a secondary-hash remap. Update two comment blocks
that still referenced the old approach.
No need to preserve agent IPs across this deploy — nothing outside a
container depends on a specific agent IP. Simpler approach: subtract
DHCP_POOL_SIZE from the usable count before hashing so agents only ever
land in [2, usable - DHCP_POOL_SIZE + 1], never in the DHCP pool.
Removes the secondary-hash remap block (~10 lines). Returns None for
subnets too small to hold both agent slots and the pool (edge case;
practical subnets are /24).
Move the DHCP pool size constant out of the two separate definitions
(Nix literal + Rust const) into a shared data file: nix/dhcp-pool-size.
- nix/dhcp-pool-size: new file, contains '14'
- hive-gateway.nix: reads via builtins.readFile + toIntBase10
- lifecycle/mod.rs: parses via include_bytes! const block at compile time
Cargo automatically tracks include_bytes! as a file dependency so a
change to nix/dhcp-pool-size triggers recompilation without build.rs.
Add a DHCP pool to the gateway's dnsmasq so bridge-attached service
containers (hive-ci and future equivalents) get their addresses from
a proper DHCP server instead of a brittle static derivation.
gateway (hive-gateway.nix):
- Add IPv4 arithmetic helpers (ipToInt, intToIp, pow2) to compute the
DHCP pool range at nix eval time from bridgeIp + bridgePrefixLength.
- Reserve the last dhcpPoolSize (14) usable host addresses as the DHCP
pool (e.g. .241-.254 on a /24 with 10.42.0.0 network).
- Add dhcp-range and dhcp-leasefile to the dnsmasq settings block.
The pool is active whenever services.hyperhive.network.enable is true.
hive-ci (hive-ci.nix):
- Remove the ciBridgeIp / ciBridgeOctets static derivation and the
brittle top-of-/24 comment block.
- Switch networking.interfaces.eth0 to useDHCP = true so hive-ci gets
its address from the gateway DHCP pool.
lifecycle (mod.rs, tests.rs):
- Add DHCP_POOL_SIZE = 14 constant (must stay in sync with
dhcpPoolSize in hive-gateway.nix).
- Remap agents whose FNV-1a hash falls in the DHCP pool into the
agent-only window [2, dhcp_start - 1]. Only the rare agent whose
name hashes into the pool is affected; all others keep their IPs.
- Update and extend tests: agent range is now .2-.240 on /24;
add agent_network_ip_never_in_dhcp_pool covering 18 agent names.
New POST /api/infra-container/{name}/{action} dashboard route (start/
stop/restart on hive-ci/hive-forge/hive-gateway/hive-matrix), reusing
the existing priv_client::control_infra_container helper the
infra_admin agent path already uses, plus an audit_log entry per
attempt. Adds infra_containers to the /api/state StateSnapshot (name +
live running status via systemctl is-active). New 1NFR4 sub-tab on the
C0R3 dashboard page: one row per infra container with a running/
stopped badge and start/stop/restart buttons, polled every 5s while
the sub-tab is open.
Two issues flagged by argus in PR #2388 review:
1. Empty-key fallback: when load_or_generate() failed, webhook_secret was
String::new(). An attacker knowing this could forge deliveries with a
valid HMAC of the empty key. Fix: change to Option<String>; on None,
skip hook registration entirely and return 503 from /webhook/* handlers
(rather than 401 with a misleadingly-verifiable empty-key HMAC).
2. Stale hook cleanup: on upgrade from old code, old loopback hooks
(http://127.0.0.1:.../webhook/knowledge, .../webhook/config-pr) were
left alongside the new domain-URL hook. Fix: during ensure_webhook /
ensure_config_pr_webhook, after listing hooks, delete any that end with
our path suffix but point at a different base URL.
clippy + nix fmt clean.
Both webhook registrations (knowledge push + config-PR pull_request) now
use the public hive domain instead of loopback:
https://<HYPERHIVE_HIVE_DOMAIN>/webhook/{knowledge,config-pr}
This routes deliveries through the gateway, bypassing the Forgejo SSRF
guard that blocked loopback delivery and silently broke the config-PR
merge flow since launch.
Changes:
- webhook_secret: new module — auto-generate + persist a 32-byte HMAC
secret to STATE_ROOT/webhook-secret on first startup; verify
X-Hub-Signature-256 on every incoming webhook POST (HMAC-SHA256).
- forge/mod.rs: ensure_config_pr_webhook now takes hive_domain +
webhook_secret; sets secret in Forgejo hook config.
- workers/knowledge.rs: ensure_webhook same update.
- dashboard/webhook.rs: both handlers read raw Bytes first, verify HMAC,
then parse JSON. Returns 401 on signature mismatch.
- dashboard/mod.rs: AppState carries webhook_secret; serve() takes it.
- main.rs: load/generate secret at startup; pass to registration tasks
+ dashboard; add 5-minute config-PR polling fallback task.
- forge/config_pr_poll.rs: new — scan agent-configs/* for open PRs with
no pending MergeConfigPr approval; queue them. Idempotent.
- stores/approvals.rs: has_pending_merge_config_pr() for poll dedup.
- nix/modules/hive-gateway.nix: remove dashboardAuth from /webhook/
location (HMAC replaces basic auth for webhook endpoints; Forgejo
cannot send HTTP Basic credentials with webhook deliveries).
thinkingTokensRow/statusRow/pluginInstallRow were three copy-pasted
module-level row/text pairs implementing the same 'collapse repeated
ticks into one updating row' pattern. Extracted a small makeCoalescer(cls,
icon) factory returning an update(api, text) closure; each call site is
now a single line instead of the duplicated guard-and-update-or-create
block against api.mutableRow.
If the remote is ahead of our local mirror (non-fast-forward), the old
code used --force which silently destroyed remote history. Fix:
- Drop --force from the git push invocation.
- On non-ff exit, detect the condition and return Ok(()) instead of
bailing (intentional no-op; leaving remote history intact is correct).
- Raise a persistent dashboard warning banner via crate::warnings so the
operator sees it in the UI rather than having to grep the journal.
- Clear the banner on the next successful push.
Closes#2380.
Part 2 of hyperhive#1970 (backend/CLI landed in PR #2378). Renames
/matrix-accounts.html -> /credentials.html and restructures it with a
sub-tab strip (MATRIX / GITHUB), reusing the shared @hive/shared/tabs.js
tab strip already used by /logs.html.
MATRIX tab carries over the existing account list + login form
unchanged. GITHUB tab adds a single-PAT provisioning form: status line
(present/absent, read from GET /api/github-account), a security-warning
banner (dedicated bot account + minimally-scoped token), a link to
generate a PAT at github.com/settings/tokens, and a paste-token form
posting to POST /api/github-account. Both tabs share one agent picker.
Updated docs/web-ui.md + docs/web-ui/dashboard.md to describe the new
page shape, and the H0M3 hub tile (index.html) to point at the renamed
page.
dispatch was 101 lines (1 over limit) due to the AgentStatus arm.
Extract it to a dedicated handle_agent_status helper to bring dispatch
under the 100-line lint limit without the allow attribute.
Per mara review comment on PR #2379.
- Backtick-quote `pull_request` in doc comments (4x doc_markdown)
- Add #[allow(clippy::too_many_lines)] to server::dispatch (101/100;
+1 line from submit_kind fetched_sha param in 5dd0a36f)
org_list_hooks response type is Vec<Hook> (no pagination headers),
so .all() (which is impl'd for (H, Vec<T>) paginated responses) does
not compile. Switch to .send() — the non-paginated call path.
repo_list_hooks (used in workers/knowledge.rs) returns (H, Vec<T>)
and correctly uses .all(); the org variant is different.