Commit graph

2,548 commits

Author SHA1 Message Date
damocles
b4b5dd7fa4 fix(hive-metric): use opentelemetry-otlp SDK instead of hand-rolled OTLP 2026-07-10 21:27:56 +02:00
damocles
39fc088907 feat(#2100): add hive-metric OTLP CLI for agent-emitted custom metrics 2026-07-10 21:27:56 +02:00
damocles
7964d45a27 refactor(#2359): bare Deserialize derive + clarify error to_string scan 2026-07-10 20:43:58 +02:00
damocles
71c8b14633 refactor(#2359): typed stream-json parsing for sentinel detection 2026-07-10 20:43:58 +02:00
damocles
556a213320 refactor(#2285): inline remaining 1:1 path wrappers (meta_dir, marker fns, host_conf_path) 2026-07-10 20:32:03 +02:00
damocles
cfb84b420a refactor(#2285): drop coordinator 1:1 path accessors, callers use paths:: directly 2026-07-10 20:32:03 +02:00
damocles
187c364feb refactor(#2285): repoint all hive-c0re host-path consumers to paths.rs 2026-07-10 20:32:03 +02:00
damocles
4162f557c9 refactor(#2285): repoint knowledge + forge core-token to paths.rs consts 2026-07-10 20:32:03 +02:00
damocles
1cefaf2c16 refactor(#2285): add consolidated path fns/consts to paths.rs (foundation) 2026-07-10 20:32:03 +02:00
damocles
342f6ab55b chore(#2358): disable claude dynamic workflows via managed settings 2026-07-10 20:30:34 +02:00
damocles
9ad94190e6 fix(hive-forge): use u64::from for repo-search limit clamp (clippy cast_lossless) 2026-07-10 20:22:29 +02:00
atlas
327a3bc3bc feat(hive-forge): repo-search verb — keyword/topic search across forge repos 2026-07-10 20:22:29 +02:00
damocles
3e2565d355 fix(#2356): enable built-in bash for choom by dropping it from managed-settings deny 2026-07-10 20:03:41 +02:00
atlas
0fc4600d07 fix(#2332): add After=hive-tls-ca to resign service — prevent first-boot CA race
On a fresh deploy Persistent=true causes the weekly timer to fire
immediately. Without this ordering hive-tls-resign can race
hive-tls-ca before the CA key is written, failing the resign.
CA is always valid when this runs; resign is a no-op if the leaf
is still within its half-life margin.

Suggested by argus (review) + requested by mara.
2026-07-10 19:37:08 +02:00
atlas
61bd0b604d feat(#1933): weekly hive-tls-resign timer — dedicated resign service, no hive-tls-ca bounce
The parked PR (#1934) triggered re-sign by restarting hive-tls-ca
(a RemainAfterExit oneshot), which mara flagged as a hack.  This
replaces that with a dedicated hive-tls-resign.{service,timer}:

- hive-tls-ca unchanged — still handles boot-time CA generation +
  initial leaf signing; RemainAfterExit, runs before the gateway.
- hive-tls-resign.service — runs the leaf re-sign directly (openssl
  in its own PATH); does NOT touch hive-tls-ca.  Re-signs only when
  the leaf is within half its validity of expiry, so the weekly timer
  catches short-lived (30d default) leaves well before they lapse.
  Checks sha256 before/after; if the leaf rotated it propagates into
  the running gateway via systemctl -M hive-gateway (mirroring how
  hive-c0re reloads the gateway after agents.conf writes — the
  documented correct approach; an inotify path unit inside the
  container does not work across the nspawn mount-namespace boundary).
  || true on the propagation calls so a stopped gateway never fails
  the unit (its next boot imports the already-rotated leaf).
- hive-tls-resign.timer — OnCalendar=weekly, Persistent=true (fires a
  missed run on next boot so a host that was off on the scheduled day
  catches up rather than letting the leaf lapse silently).
- leafValidityDays doc updated to reflect automatic renewal.

Closes #1933.
2026-07-10 19:37:08 +02:00
sock
6c95933775 docs(security): ci netns, knowledge .git tmpfs, matrix id/secret split
Security fixes landed without updating the security-model docs; bring them
current as present-tense invariants:

- security.md: matrix identities are public / cross-agent-readable via
  GetAgentMeta while the token stays private; name validation extends to
  GetAgentMeta; the nix-build blast radius has a network dimension (hive-ci
  netns) alongside the 0600 token policy.
- boundary.md: the core dashboard /api has no app-layer auth — protection is
  the gateway plus network isolation; stated as a standing invariant.
- ci.md: hive-ci runs in its own private netns behind the bridge, reaches the
  forge via the gateway, and cannot reach host-loopback (dashboard/forge port).
- knowledge.md: /knowledge excludes .git via tmpfs so the core token in
  .git/config isn't readable by agents.

Refs #2308, #2310, #2313.
2026-07-10 19:17:17 +02:00
iris
82250b8f59 fix(agent-ui): coalesce plugin_install started/completed into one row
claude emits a started tick then a completed tick per plugin install;
previously each got its own row (loading... then done) instead of one
line updating in place. Same coalescing pattern already used for
thinking_tokens and the generic status tick.
2026-07-10 19:15:24 +02:00
damocles
e0d5c98356 refactor(#2282): split dag_progress out of hivectl.rs 2026-07-10 19:09:14 +02:00
damocles
4fd3928506 feat(#2282): animated indicatif DAG progress render for hivectl wait 2026-07-10 19:09:14 +02:00
damocles
ce909a00a2 fix(#2327): handle dismissed reviews like stale (superseded helper) 2026-07-10 19:08:16 +02:00
damocles
cd092a8ae4 feat(#2327): surface forgejo stale/dismissed review flags in pr-status/reviews/merge 2026-07-10 19:08:16 +02:00
sock
078ea74ba9 fix(hive-ci): static bridge IP instead of DHCP (no DHCP server on the bridge)
hive-ci is the one container on the hive bridge that used DHCP
(networking.interfaces.eth0.useDHCP = true, from #2310 / #2336). But the
bridge has no DHCP server: dnsmasq on the bridge is DNS-only and agent
containers get deterministic static IPs (lifecycle::agent_network_ip),
so the DHCP client never gets a lease and the container hangs at boot:

  A start job is running for DHCP Client (5s / 1min 31s)

Assign a static address on eth0 (top host address of the subnet) plus a
default route via the bridge gateway, and drop useDHCP. Preserves the
#2310 netns isolation (no host-loopback reach) while letting the
container boot and reach the forge through the gateway.

Refs #2310, #2336.
2026-07-10 18:38:17 +02:00
damocles
c48d6319f7 fix(#2351): key error-result scan on is_error to match the real event shape 2026-07-10 17:02:06 +02:00
damocles
9aaf8b93fb fix(#2351): scan error result raw so prompt-too-long in result field is detected 2026-07-10 16:38:11 +02:00
atlas
4d76305f2f fix(clippy): collapse nested if-let in build_graph (collapsible_if) 2026-07-10 15:16:22 +02:00
atlas
0af14d8ef8 refactor(#2281): petgraph for topology — replace bounded walks with graph algorithms
The is_descendant_of and apply_set_parent cycle detection both used
hand-rolled 32-hop bounded ancestor walks. Correct in practice (no
real hive exceeds 32 levels) but carried an arbitrary ceiling and were
harder to reason about than proven graph primitives.

Changes:
- Add build_graph(): converts BTreeMap<name, parent|null> → DiGraph
  with parent→child edges + BTreeMap<name, NodeIndex> index
- Add is_descendant_of_in(): pure (no disk I/O), uses
  petgraph::algo::has_path_connecting from ancestor to candidate
- Rewrite is_descendant_of(): delegates to is_descendant_of_in(&read())
- Rewrite apply_set_parent() cycle detection: build_graph() + speculative
  edge + is_cyclic_directed(); no depth limit
- Add tests for is_descendant_of_in (self, direct child, grandchild,
  parent-is-not-child, sibling, unknown)

petgraph was already a workspace dep (used elsewhere). On-disk format
unchanged (flat JSON map). Public API surface unchanged.
2026-07-10 15:16:22 +02:00
atlas
c0a49c95c1 fix(#2336): per-interface DHCP on eth0 instead of global useDHCP
nixos-containers.nix sets networking.useDHCP = false for every
container (to suppress the deprecated global DHCP warning). Setting
networking.useDHCP = true in the hive-ci inner config conflicts with
no priority differential → eval error on hosts running current main.

Fix: drop the global useDHCP = true and configure DHCP on eth0
(the inner veth interface name assigned by systemd-nspawn when
hostBridge is used). This is both conflict-free and the correct
modern NixOS practice for per-interface DHCP.
2026-07-10 14:47:32 +02:00
damocles
28dbb529c0 feat(#2289): debounced SweepHealth banner tracker, wire knowledge pull 2026-07-10 14:46:43 +02:00
iris
8c908651bc fix(agent-ui): collapse repeated generic status ticks into one row
Claude's generic `system/status` subtype carries no detail beyond the
bare label, and previously each tick got its own note row. During a
compaction pass (which emits a burst of these with no other signal
before the completed compact_boundary) this looked like a wall of
identical noise followed by silence, making a routine compaction look
stuck.

Collapse consecutive status ticks into one updating row, same pattern
already used for the thinking_tokens counter.
2026-07-10 14:13:36 +02:00
damocles
cd155f28e4 fix(#2313): validate GetAgentMeta target against path traversal 2026-07-10 14:07:08 +02:00
damocles
bbbc2e28c7 fix(#2319): treat container start as success when the unit reaches active, not on the start exit code 2026-07-10 13:58:27 +02:00
atlas
3f7f24dd0b fix: correct hyperhive.user.gid description — gid works independently of uid 2026-07-10 13:54:03 +02:00
atlas
0d5f7e00b6 feat(#903): expose hyperhive.user.uid + gid options for optional UID pinning 2026-07-10 13:54:03 +02:00
damocles
9f98925c14 feat(#2289): push-based server-warning registry with RAII guard 2026-07-10 13:52:59 +02:00
lexis
d01d6d91e2 regen docs/tools/hivectl-cli.md with updated hivectl help text 2026-07-10 13:49:54 +02:00
lexis
aba3a3a4ea regen docs/tools/hivectl-cli.md per CI check 2026-07-10 13:49:54 +02:00
lexis
b864dd6a7b docs(hivectl): clarify start command restores previously-running agents (follow-up to #1946) 2026-07-10 13:49:54 +02:00
atlas
bd77b94518 fix(#1821): remove nix.settings.fallback from hive-ci once daemon routing confirmed
The container-level nix.settings.fallback = lib.mkForce true (added as an
interim safety net so a down remote builder degrades gracefully rather than
hard-failing CI) is rendered redundant by the host-daemon routing fix: with
NIX_REMOTE=daemon set for all systemd units in the container, every nix
build invocation routes through the host daemon, which already has its own
fallback policy. The container setting would only apply to nix invocations
that bypass the daemon entirely — a case that should not exist post-deploy.

NOTE: draft — gated on the daemon-routing change (PR that adds
NIX_REMOTE=daemon to systemd.globalEnvironment + hive-priv service env)
being deployed and confirmed working in production before merging.
2026-07-10 13:48:27 +02:00
atlas
9451ebfb24 fix(#2308): overlay --tmpfs on /knowledge/.git to hide host git credentials
The knowledge repo is cloned with credentials embedded in .git/config;
those credentials survive on disk and the entire LOCAL_DIR (including
.git/) is bind-mounted read-only into every agent container. Any
agent (or prompt-injected PR build) can read the site-admin token via
cat /knowledge/.git/config.

Defense-in-depth layer: in write_nspawn_flags, append
--tmpfs=/knowledge/.git after the --bind-ro=.../knowledge flag whenever
the /knowledge bind mount is present. systemd-nspawn processes the tmpfs
after the bind, overlaying an empty in-memory directory over .git/ inside
the container. Agents see the working-tree documents but not the repo
metadata or stored credentials.

This is independent of the complementary fix in workers/knowledge.rs
(stop embedding credentials in .git/config in the first place). Both
layers together provide defense-in-depth: even if a future credential
accidentally reaches .git/config, it stays off the container floor.

Closes part of issue 2308 (bind-mount isolation layer).
2026-07-10 13:09:58 +02:00
atlas
056a442a18 fix(#2310): hive-ci private network — bridge-connected, no host-loopback reach
Give hive-ci its own network namespace (privateNetwork=true, hostBridge)
instead of sharing the host netns. This closes the path by which a
prompt-injected PR nix build could reach the unauthenticated dashboard
at 127.0.0.1:7000.

The runner now reaches the forge through the gateway:
- networking.extraHosts maps forgeCfg.domain → bridgeIp
- runner url = http://${forgeCfg.domain} (gateway port 80, addSSL=true,
  no HTTP→HTTPS redirect — HTTP is served alongside HTTPS)
- networking.useDHCP = true for the bridge-attached veth
- networking.nameservers = [bridgeIp] (dnsmasq on bridge, external fwd)

Ports 80 and 443 are already open on the bridge firewall (hive-network.nix).
The bridge→127.0.0.0/8 DROP rule (defence-in-depth) is unchanged — CI
can no longer reach 127.0.0.1:{7000 dashboard, forge httpPort, etc.}.

Requires forge.behindGateway=true (the default when hyperhive is enabled)
— asserted so misconfigured hosts get a clear build error.

Note: the host-side hive-ci-prefetch script still uses
127.0.0.1:${httpPort} directly (it runs on the host, not in the
container) — no change needed there.
2026-07-10 12:54:46 +02:00
iris
476a7a3c9f web_ui: support unix:<path> upstreams in extraWebProxies
reqwest has no UDS transport, so unix: upstreams dial the socket
directly with a raw hyper/1.1 client per request instead. http(s)://
upstreams are unaffected (still go through the existing reqwest path).

Adds hyper (client, http1), hyper-util (tokio IO adapter), and
http-body-util as direct hive-ag3nt dependencies - all three were
already present transitively via reqwest, this just uses them
directly for the new code path.
2026-07-10 12:53:09 +02:00
damocles
cfeb4f0170 fix(#2312): scrub model-authored result field before sentinel scan to stop false auth/prompt-too-long DoS 2026-07-10 12:45:35 +02:00
damocles
17fa4a3ea5 fix(#2311): verify operator branch protection exists on create error instead of failing open 2026-07-10 12:40:03 +02:00
damocles
ee61a3d7e1 fix(#2314): refuse symlink leaf in hive-priv write_agent_state_file (O_NOFOLLOW + fd-based chmod/chown) 2026-07-10 12:27:18 +02:00
damocles
89ce8790ff feat(web_ui): per-path reverse-proxy via HIVE_EXTRA_WEB_PROXIES 2026-07-10 12:14:21 +02:00
atlas
6da7835ad9 fix(#2264): boot.tmp.useTmpfs = false — disk-backed /tmp for agents
Agent containers currently mount /tmp as a 3.2 GB tmpfs (RAM-backed).
Large scratch writes (nix-develop shells, cargo build dirs) eat container
RAM unnecessarily and can exhaust the tmpfs, losing in-flight work.

Set boot.tmp.useTmpfs = false so /tmp is disk-backed instead.
boot.tmp.cleanOnBoot (default true) preserves the ephemeral-per-boot
semantics — /tmp is cleared on container start, same as before.

Closes #2264. Supersedes PR #2299 (TMPDIR redirect + state/tmp).
2026-07-10 12:09:31 +02:00
iris
ccc5e631e2 web-ui: sanitize markdown HTML with DOMPurify to fix XSS
Both mdNode implementations (agent UI app.js, dashboard common.js)
assigned marked.parse() output straight to innerHTML with no
sanitizer. marked v5+ dropped its built-in sanitize option, and there
was no DOMPurify anywhere in frontend/, so markdown containing raw
HTML/script tags rendered live in the browser.

Both sinks receive untrusted input in practice: the agent UI's mdNode
renders recv tool_result bodies, assistant prose, and send/ask/answer
payloads sourced from peer agents and matrix-relayed messages (the
documented prompt-injection adversary); the dashboard's mdNode renders
agent-authored .md files served verbatim by GET /api/state-file
(the endpoint validates path, not content). Since the per-agent UI and
dashboard are same-origin behind the gateway with operator-authority
endpoints (approve/spawn/rebuild/destroy/answer-question), injected
script would run with the operator's session.

Fix: DOMPurify.sanitize() the marked.parse() output at both sinks
before assigning to innerHTML. Added dompurify as a dependency to
both the agent and dashboard npm workspaces, recomputed npmDepsHash
in nix/frontend.nix for the updated lockfile. Also corrected
docs/web-ui/shape.md, which claimed the markdown-rendering path was
XSS-safe by construction the same way the text-node-based linkify
path is — it isn't; it's safe because it's sanitized.

CSP hardening for the dashboard (no unsafe-inline) is a separate,
larger backend change (response headers in hive-c0re) and is left as
a fast-follow rather than folded into this fix.
2026-07-10 11:54:50 +02:00
atlas
9adf074504 feat(#2304): set weston VNC default resolution to 720p (1280x720)
Add [output] section to weston.ini with name=VNC-1 mode=1280x720.
The VNC backend's output is named VNC-1; the mode string sets the
compositor's framebuffer size that clients connect to.
2026-07-10 02:47:39 +02:00
damocles
e58805b1ff feat(broker): add message priority, operator messages surface first 2026-07-10 02:44:35 +02:00
atlas
e9667f9c1a fix(#2287): reconcile team settings on 409 (upsert via org_edit_team)
On 409 (team already exists), list the org teams to find the operators
team id, then unconditionally PATCH to the desired settings via
org_edit_team. This self-heals a team that was created with the wrong
shape by an older code path (missing units, wrong permission) without
touching membership (separate endpoint, operator-managed).

Addresses mara's review: 'shouldnt we get, then change, then update'.
Unconditional PATCH is simpler than GET→diff→conditional PATCH and safe
here since we own units/permission/description fully.
2026-07-10 02:43:37 +02:00