Commit graph

2,785 commits

Author SHA1 Message Date
atlas
02167caf60 refactor(#2500): skip the empty acquire for fully re-entrant nodes
When every resource dep of a node re-enters an ancestor's lock, `owned_reqs`
is empty; the old code still called `acquire(vec![])` and stored a no-op
empty guard in `owned`. Gate the acquire + guard insertion on
`!owned_reqs.is_empty()` — one fewer `borrow_mut` + `HashMap` entry per
fully-re-entrant node in the settle loop. `node_owns` already treats a
missing `owned` entry as non-owning, so behaviour is unchanged.
2026-07-19 16:10:56 +02:00
damocles
144912f8e0 address review: drop backwards-compat request/response aliases, use canonical names 2026-07-19 15:53:09 +02:00
damocles
d0beec8a40 refactor(#2581): carve per-agent mcp.sock protocol into hive-agent-sock crate 2026-07-19 15:53:09 +02:00
atlas
f64ab47de0 refactor(#2500): encapsulate the jobq lock, drop the dead borrowed-guard layer
Make the resource lock unmisusable from outside the crate: the public
surface is now purely declarative (build a Graph with Dep::Resource edges,
configure capacities, run the Scheduler), and the scheduler owns every
acquire/release — a consumer never holds a guard, so it cannot hold the
lock wrong.

- `guard` module + `ResourceTable::try_acquire_all`/`release_all` +
  `Graph::set_state` are now `pub(crate)`.
- Remove the dead borrowed-guard layer (`ResourceGuard::borrowed`,
  `Acq::Borrowed`, `is_owning`): the scheduler tracks re-entrancy via its
  own single borrow slot per (holder, resource) and never constructs a
  borrowed guard, so re-entrancy lives in exactly one place. `Acq`
  collapses into the owning `ResourceGuard` struct.
- `#[must_use]` on `Scheduler::settle` — ignoring its ids silently drops
  runnable work.
- `SharedResources::with` (test-only table observability) is `#[cfg(test)]`.
- Drop the moot borrowed-guard tests; retained owning tests are black-box,
  and the redundant `set_state` test helper is gone.
2026-07-19 15:24:11 +02:00
atlas
b4bcf8b6e4 refactor(jobq): make the crate generic over the resource type R
Replace the concrete ResourceName(String) with a type parameter
R: Clone + Eq + Hash threaded end-to-end (Dep<R>, Node<N,R>, Graph<N,R>,
ResourceTable<R>, ResourceGuard<R>/SharedResources<R>, Scheduler<N,R>).
The crate no longer hard-codes the resource identity; the consumer picks
the concrete type (a String, or an enum like BuildSlot/Agent(name)) at
the port. Tests use String as the concrete R. Pure type-parameter
thread-through, no logic change. 25 tests green, clippy pedantic clean.
2026-07-19 15:24:11 +02:00
atlas
7ffc13dc86 feat(#2500): hive-jobq scheduler re-entrancy + eager AfterOk cascade 2026-07-19 15:24:11 +02:00
atlas
12e618097a feat(#2500): hive-jobq scheduler settle loop (owned resources + subtree-hold) 2026-07-19 15:24:11 +02:00
atlas
01ff8071f6 feat(#2500): add hive-jobq RAII resource guards with recursive re-entrancy 2026-07-19 15:24:11 +02:00
iris
06bc7e31c0 fix(#2289): wire sync_agent failures to dashboard warning banner
sync_agent() now returns bool (false if any step fails). ensure_all()
collects the names of agents whose sync failed and raises a single
set_boot_warning with the aggregated list:

  forge: per-agent sync failed for: alice, bob (see journal for per-step
  detail)

The static_kind() leak is already used for per-org boot warnings in the
same file — the leak is bounded (one per hive-c0re boot, not per request)
so reusing it here is appropriate.

The rebuild call site in job_queue/exec.rs discards the bool return and
keeps its existing tracing::warn! lines, which is the right separation:
rebuilds are their own retry loop and don't need to post a persistent boot
warning.
2026-07-19 14:57:21 +02:00
iris
66c715842f dashboard: add hive infra containers to the logs UI agent selector
Extends GET /api/journal/{name} to also accept the four hive infra
container names (hive-ci, hive-forge, hive-gateway, hive-matrix —
hive_priv_sock::InfraContainer is the allowlist), reusing the same
journalctl -M / hive-priv delegation path already used for agent
containers. Infra containers don't run the per-agent hive daemons, so
the unit filter is skipped for them — always the full machine journal.

Frontend: the AGENT tab's agent selector now lists infra containers
in a separate optgroup (sourced from /api/state's existing
infra_containers field), and disables the unit-filter select when one
is chosen.
2026-07-19 14:49:17 +02:00
iris
e292d8d16c fix: use crate::warnings re-export (consistent with forge/mod.rs) 2026-07-19 14:17:53 +02:00
iris
b63738e8c2 fix(#2289): wire ci-runner registration failures to dashboard warning banner
Both warn!-and-forget sites in ensure_ci_runner_registered() now also
call set_boot_warning():

- fetch_registration_token failure → crit banner (forge unreachable or
  API error; runner stays with stale/absent creds)
- hive-priv register_ci_runner failure → crit banner (EROFS or priv
  socket error; runner token not written)

Both are only ever invoked from ensure_all() at hive-c0re startup (no
periodic retry), so set_boot_warning() is the right API: the banner
persists until the next c0re restart that re-runs the step, which is
exactly when a config/environment fix (e.g. the ReadWritePaths EROFS
fix from 64075107) would take effect.

Journal warn! lines are kept alongside the banner (belt-and-suspenders).

Remaining in scope for #2289: sync_agent() warn! sites (called from
both startup and rebuild paths — needs set_warning RAII or explicit
return value to enable later success to clear the banner; left for a
follow-up).
2026-07-19 14:17:53 +02:00
damocles
565b1b90fc docs(#2569): address argus review — add Errors/Panics doc sections + fix ClearTodo keyless-clear semantics 2026-07-19 13:28:17 +02:00
damocles
8149dc7633 fix(#2569): wake body points at get_loose_ends (get_todos rename is a later increment); strip tracker tags from source comments per hive-rules 2026-07-19 13:28:17 +02:00
damocles
711e0ece2a feat(#2569): matrix producer — sweep_unread pushes per-room todos instead of direct wakes 2026-07-19 13:28:17 +02:00
damocles
6685b33c9d feat(#2569): DB-backed per-agent todo store + mcp.sock upsert/clear/list/mark-done ops 2026-07-19 13:28:17 +02:00
atlas
6a4382bee8 fix(#2570): recognize 422 team-already-exists so operators-team provisioning stops warning
Forgejo returns team-already-exists as HTTP 422 ValidationFailed, not
409 Conflict, so the 409-only guard in ensure_operators_team missed it
and logged a spurious warning every boot (and skipped the settings
reconcile). Add a lenient discriminator that also treats a 422 whose
message says already-exists as benign.
2026-07-19 01:11:19 +02:00
damocles
a35b67b7c5 fix(#2573): also add /etc/tmpfiles.d to hive-priv ReadWritePaths (same EROFS class) 2026-07-18 16:39:20 +02:00
damocles
6407510744 fix(#2573): add /run/hive-ci to hive-priv ReadWritePaths so the ci-runner token write doesn't EROFS 2026-07-18 16:31:18 +02:00
atlas
b9cef9507b revert(#2502): render agent config input from local applied mirror, not forge 2026-07-17 18:06:28 +02:00
atlas
b670291347 fix(#2565): lenient notifications deserialize in hive-forge read-guard 2026-07-17 16:45:56 +02:00
damocles
742ed51d57 fix(#2557): sweep unread rooms post-sync so a dropped matrix wake self-heals 2026-07-17 16:42:39 +02:00
damocles
089abf89b4 fix(#2565): lenient local notification struct in forge_notify so forgejo schema drift can't drop notifications 2026-07-17 16:28:37 +02:00
damocles
6bf839cfe4 fix(#2560): use from_mins for migration timeouts to satisfy clippy pedantic 2026-07-17 15:40:35 +02:00
damocles
40cf7f29de fix(#2560): kill_on_drop for git + nix children so timeouts don't orphan 2026-07-17 15:04:33 +02:00
damocles
618d774467 fix(#2560): make hive-core git non-interactive + bound stalled fetches 2026-07-17 15:01:47 +02:00
damocles
d00b349102 fix(#2560): bound migration shellouts with timeouts to prevent boot hang 2026-07-17 14:55:37 +02:00
atlas
29ceca4323 feat(#2500): add hive-jobq ResourceTable with atomic all-or-nothing acquire
Named counting-semaphore resources — the Dep::Resource side of the v2
model. A ResourceTable tracks per-name capacity + held counts; unconfigured
names default to capacity 1 (created lazily). try_acquire_all grants every
requested unit or none, leaving the table untouched on failure — so a node
never holds one resource while waiting for another, which is what makes the
scheduler deadlock-free without cycle detection. Duplicate names in a
request are summed; over-capacity requests can never acquire. release_all
saturates rather than underflowing. Runtime scheduler state, not persisted:
held counts are rederived from running nodes on restart. Guard objects
(RAII release, recursive re-entrancy) wrap this in a follow-up.
2026-07-17 14:46:25 +02:00
iris
f378f842ea forge: wire ensure_all() boot-warn sites to the dashboard banner
Adds set_boot_warning() to stats/warnings.rs: a one-shot variant of
set_warning() for steps that run once at hive-c0re boot (no periodic
retry to observe a later success and clear the banner via WarningGuard
drop). It forgets the guard, leaking it for the life of the process —
the banner clears on the next hive-c0re restart, which is also when a
config/environment fix would take effect.

Wires every remaining tracing::warn!()-and-forget site in
forge::ensure_all() to also raise a boot warning: core user/token
provisioning, per-org seeding, the operators-team grant per org, the
core/meta repo, shared docs repo, knowledge repo (+ its local clone),
both avatar uploads, and the final nixos-container list failure.

Split the core_token.is_some() body of ensure_all() into a new
ensure_all_orgs_and_repos() helper to stay under clippy's
function-length limit.

Deliberately out of scope: sync_agent() (shared with rebuild_agent(),
different blast radius) and ci_runner::ensure_ci_runner_registered()
(its own retry logic) — matrix::ensure_all() got the same slice-by-
slice treatment.
2026-07-17 13:15:47 +02:00
damocles
d124dd205a feat(#2554): add hivectl forge reconcile-config to reconcile local applied config against forge main 2026-07-17 12:55:42 +02:00
atlas
11df4a1bf5 feat(#2500): validate NodeId references on insert and deserialize
Per mara's direction — validate ids as they enter the graph so internal
iteration can trust every id the graph holds; the generational route for
removal comes later. Adds GraphError; insert() now rejects a dangling
Dep::Node / parent id (it is fallible); validate() checks all internal id
references resolve and that next_id is past the largest existing id;
deserialization runs validate() via #[serde(try_from = "GraphData<N>")], so
a loaded graph can never carry a dangling reference. 5 new tests; serde_json
added as a dev-dependency for the round-trip cases.
2026-07-17 12:54:19 +02:00
atlas
570188fa1a refactor(#2500): make NodeId genuinely opaque via a crate-private field
argus flagged that `NodeId(pub u64)` contradicted the "opaque" doc — a pub
inner field lets callers fabricate `NodeId(42)`. Make the field `pub(crate)`
so an id can only originate from the graph's monotonic counter or serde
deserialization, never a caller. Tests construct ids in-crate (unaffected);
the derived Serialize/Deserialize round-trips fine. Doc keeps "opaque" — now
accurate — with a line explaining the enforcement.
2026-07-17 12:54:19 +02:00
atlas
581737583e fix(#2500): group_terminal must require the group node itself terminal
group_terminal checked only that every child was terminal, never the group
node's own state — so an empty group whose node is still Running returned
true (empty .all()), making a running node that has yet to append its
subgraph look already-finished. Now it requires the group node itself
terminal AND every child recursively terminal. Deciding when to settle a
group node to terminal once its children are done stays a scheduler concern;
this answers the dependents' question — is the whole group, node included,
finished. Adds group-node-pending-with-child-done + empty-running-group tests.
2026-07-17 12:54:19 +02:00
atlas
8f5ccb2882 feat(#2500): scaffold hive-jobq crate with the core graph data model
First step of extracting the job-DAG queue into a domain-agnostic
`hive-jobq` library, per the operator's v2 design: one persistent
graph, named-counter resources, recursive node groups, opaque stable
node ids, guard-object locks, a slot-filling scheduler.

This commit lands only the data model, so the shape can be reviewed
before the machinery is built on it:

- NodeId: opaque, stable, monotonic; group membership is a parent
  edge, not encoded in the id (the 1/1/2 hierarchy is a derived UI
  label).
- ResourceName, Dep (Node | Resource{name,count}), State.
- Node<N>: caller-defined payload N so the library stays
  container-agnostic.
- Graph<N>: insert (mints stable ids), node lookup, children,
  recursive group-terminal check. Retains completed groups (no
  pruning in v1).

The resource-acquisition machinery (atomic all-or-nothing acquire),
the recursive-lock guards, and the scheduler loop are follow-ups.
Tests cover id minting, group terminality, and state terminality;
clippy + rustdoc clean.
2026-07-17 12:54:19 +02:00
damocles
fc00e38490 fix(#2553): mirror agent-config tags + main as separate pushes so a protected-main reject doesn't drop the status tags 2026-07-17 12:02:05 +02:00
iris
bf913df67a refactor: extract provision_space from matrix::ensure_all instead of allow
Per mara's standing calibration (#2463): extraction > silencing for
too_many_lines. Splits the space + chat-room provisioning tail into its
own fn, bringing ensure_all back under the 100-line threshold without
an #[allow].
2026-07-17 10:02:57 +02:00
iris
8cb130b8d7 fix: allow too_many_lines on matrix::ensure_all (108/100, aggregate-bool sweep) 2026-07-17 10:02:57 +02:00
iris
cfed36582e fix: move matrix_sweep_banner to module scope (items_after_statements) 2026-07-17 10:02:57 +02:00
iris
84ea7b8e7d sweep: dedupe matrix ensure_all banner closure 2026-07-17 10:02:57 +02:00
iris
b889f403d5 sweep: wire matrix::ensure_all() into the warning-banner registry
Fast-follow for #2289 — matrix::ensure_all() (admin user, per-agent
sync, hive Space + chat-room provisioning/invites) ran periodically
every 30 minutes but only ever warn!'d to the journal on failure, so a
persistent problem (missing invites, broken admin token, etc.) was
invisible to the operator.

ensure_all() now returns bool (aggregate ok/fail across every
sub-step) instead of (), and both call sites in main.rs feed that into
a debounced SweepHealth("matrix_ensure_all", warn, threshold=2) —
matches the existing knowledge_pull pattern. A lone bad sweep
self-heals silently; two consecutive failures raise a banner that
clears on the next clean sweep.

forge::ensure_all()'s remaining independent steps are still open —
that sweep only runs once at startup (no periodic loop), so the
debounced pattern doesn't map as directly; left for a follow-up.
2026-07-17 10:02:57 +02:00
atlas
8624da0057 fix(#2550): make pedantic clippy lints a hard error, fix stale checks.nix comment
The clippy check's comment described `-D warnings -A clippy::pedantic` — the
`-A` half dropping pedantic from the CI gate — but the args were only
`-D warnings`, so pedantic was hard-denied contrary to the doc. Operator
call: pedantic should be gated. Encode that as the single source of truth:
set the workspace lint `pedantic = deny` (errors locally and in CI), and
rewrite the checks.nix comment to match. Args unchanged; `-D warnings` still
gates rustc + non-pedantic clippy warnings. No new failures — the tree was
already pedantic-clean under CI's `-D warnings`, which denied pedantic.
2026-07-17 01:58:27 +02:00
atlas
87f8e936d5 refactor(#2502): thread forge_base param into render_flake, drop the localhost fallback
Per operator review (#2547): remove the never-reached branch. render_flake
read HIVE_FORGE_URL inline with an unwrap_or_else(localhost:3000) fallback
that can't be hit — the daemon always sets HIVE_FORGE_URL (environment.nix)
— and the localhost URL wouldn't match the domain-scoped credential helper
anyway. Thread forge_base as an explicit param instead: the caller
sync_agents passes forge::forge_http_base(), tests pass it explicitly. This
removes the dead branch AND the hidden env-read, and drops the racy env
set_var from the forge-url test. render_flake is pure/param-driven again.
2026-07-17 01:48:27 +02:00
atlas
7df9f5d024 docs(#2502): meta flake input is the forge config repo, deploy overrides to local applied 2026-07-17 01:48:27 +02:00
atlas
27a099bb5b feat(#2502): re-lock deploy + rebuild from local applied, not forge
The persistent agent-<n> input url in the meta flake is now the forge
config repo, but the deploy + manual-rebuild re-lock must not follow it:
prepare_deploy and lock_update_for_rebuild override the input back to the
local applied mirror (git+file://<applied_dir>, main head) via
--override-input, which writes the applied rev into flake.lock.

This keeps the forge as the declared, reviewable source while:
- surviving forge unreachability (rebuilds fire on crash-restart and meta
  bumps too, not just config PRs -- coupling every rebuild to forge would
  be a regression),
- deploying exactly the reviewed head applied/<n>/main was fast-forwarded
  to (no TOCTOU on a newer forge head merging mid-deploy),
- reusing verify_commit's local-override pattern so verify and deploy eval
  the same source.

New applied_override_url helper + unit test.
2026-07-17 01:48:27 +02:00
atlas
b806aa253e feat(#2502): git credential helper for hive-core forge fetches
hive-core now fetches each agent's config as a forge-hosted flake input
(git+http://<forge>/agent-configs/<n>.git). Add a git credential helper
(git-credential-hive-forge) that reads the live forge-core admin token on
every invocation and authenticates as the forge core user, wired via the
[credential] stanza in hive-core's $HOME/.gitconfig + on the service PATH.
Reading the token file live means zero stale copies and no resync on
rotation; no token ever lands in a flake URL or lock. Mirrors the existing
github.nix credential-helper pattern.
2026-07-17 01:48:27 +02:00
atlas
a5870c5ddf wip(#2502): render agent config input from forge repo (meta.rs + test) 2026-07-17 01:48:27 +02:00
damocles
5bb5a88aa0 restart preserves wanted intent instead of forcing all agents up (#2540) 2026-07-17 01:46:12 +02:00
damocles
a32f3f6edf add frontend build to flake checks so a stale npmDepsHash fails CI (#2543) 2026-07-17 01:44:32 +02:00
atlas
afeddfced1 docs(#1977): drop exposeHostPorts proxy history from network.md
Trim the historical parenthetical about the dropped systemd-socket-proxyd
forwarder — argus flagged it (🟡 on the merged PR) and the constraint
(bind 0.0.0.0 or the bridge IP, not loopback) stands on its own without
the "why we dropped the proxy" narrative. No-history-in-docs preference.
2026-07-17 01:08:42 +02:00
damocles
2646877a22 document PostSwap in coordinator.md (#2390) 2026-07-17 01:06:33 +02:00