From 4aa11b3a948d53e7829888174ed369a4e4a7dedd Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 25 Oct 2026 13:33:15 +0100 Subject: [PATCH] build agents only from internal/meta; delete the hive's local meta flake The hive no longer renders, locks or commits a meta flake. A rebuild's MetaSync node picks the internal/meta commit and records it with the hyperhive and config pins it holds; Prebuild, Swap and Create build from it, and a head that does not pin the agent's current config fails the build naming the agent. hive-priv's meta_rev is required. Removed: meta.rs sync/lock/render, the manual-rebuild relock, the boot sweep's hyperhive bump (MetaLock), the update-meta-inputs form and its panel, MetaWindow, the hive's hyperhive_rev setting. Kept: agent_set::refresh holds what sync_agents also did (topology.json, the gateway's agent-socket map and nginx include), called from spawn, destroy, rebuild and the migration. The boot reconcile and the stale-start upgrade compare the agent's marker with the hyperhive pin of internal/meta; the dashboard's deployed chip reads the config marker a successful build writes. --- AGENTS.md | 6 +- docs/agent-lifecycle/agent-roster.md | 8 +- docs/agent-lifecycle/config-changes.md | 139 +- docs/agent-lifecycle/persistence.md | 2 +- docs/integrations/forge.md | 5 +- docs/process/gotchas.md | 8 +- docs/scheduler/coordinator.md | 141 +- docs/swarm/archetypes.md | 5 +- docs/swarm/builder.md | 7 +- docs/tools/matrix.md | 2 +- docs/web-ui/README.md | 6 +- docs/web-ui/dashboard.md | 49 +- frontend/packages/dashboard/src/agents.js | 2 +- frontend/packages/dashboard/src/builds.js | 156 +- frontend/packages/dashboard/src/dashboard.css | 4 +- frontend/packages/dashboard/src/index.html | 46 +- .../dashboard/src/system-sections.css | 96 +- frontend/packages/dashboard/src/tabs.js | 14 +- frontend/packages/dashboard/src/util.js | 4 +- frontend/packages/shared/src/forms.js | 2 +- hive-agent/src/web_ui/actions.rs | 4 +- hive-c0re/src/actions.rs | 6 +- hive-c0re/src/agent_config/archetypes.rs | 15 +- hive-c0re/src/agent_config/topology.rs | 2 +- hive-c0re/src/agent_set.rs | 164 ++ hive-c0re/src/container_view.rs | 143 +- hive-c0re/src/coordinator.rs | 83 +- hive-c0re/src/dashboard/lifecycle_ops.rs | 4 +- hive-c0re/src/dashboard/meta_inputs.rs | 293 --- hive-c0re/src/dashboard/mod.rs | 11 +- hive-c0re/src/dashboard/state_snapshot.rs | 17 +- hive-c0re/src/dashboard_events.rs | 34 +- hive-c0re/src/forge/mod.rs | 5 +- hive-c0re/src/forge/repos.rs | 57 - hive-c0re/src/job_queue/exec.rs | 434 +---- hive-c0re/src/job_queue/mod.rs | 4 +- hive-c0re/src/job_queue/model.rs | 41 +- hive-c0re/src/job_queue/power.rs | 8 +- hive-c0re/src/job_queue/resource.rs | 16 +- hive-c0re/src/job_queue/scheduler.rs | 2 +- hive-c0re/src/job_queue/templates.rs | 91 +- hive-c0re/src/job_queue/tests.rs | 82 +- hive-c0re/src/lifecycle/git.rs | 47 +- hive-c0re/src/lifecycle/host_config.rs | 4 +- hive-c0re/src/lifecycle/mod.rs | 97 +- hive-c0re/src/lifecycle/setup.rs | 4 +- hive-c0re/src/main.rs | 10 +- hive-c0re/src/meta.rs | 1671 +---------------- hive-c0re/src/meta_rev.rs | 440 +++-- hive-c0re/src/migrate.rs | 22 +- hive-c0re/src/paths.rs | 23 +- hive-c0re/src/priv_client.rs | 8 +- hive-c0re/src/server.rs | 2 +- hive-c0re/src/swarm_status.rs | 8 +- hive-c0re/src/workers/agent_sockets.rs | 6 +- hive-c0re/src/workers/auto_update.rs | 109 +- hive-meta-flake/README.md | 4 +- hive-meta-flake/src/lib.rs | 6 +- hive-priv-sock/src/lib.rs | 57 +- hive-priv/src/container.rs | 41 +- hive-priv/src/main.rs | 10 +- nix/host-modules/hive-c0re/default.nix | 5 +- nix/host-modules/hive-c0re/options.nix | 21 +- swarm-controller/src/jobq.rs | 20 +- 64 files changed, 1041 insertions(+), 3792 deletions(-) create mode 100644 hive-c0re/src/agent_set.rs delete mode 100644 hive-c0re/src/dashboard/meta_inputs.rs diff --git a/AGENTS.md b/AGENTS.md index 62b2c7b2..1c267da4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,7 +52,7 @@ Per-hive (host + in-container): - **`hive-c0re/`** — host daemon (runs as the unprivileged `hive-core` user), **daemon-only** — the operator CLI is `hivectl`. Owns the sqlite - broker, reminder queue, meta flake, lifecycle (`nixos-container` + broker, reminder queue, lifecycle (`nixos-container` shellouts), gateway wiring, per-container stats and the axum operator dashboard. Agent forge/matrix accounts are `swarm-controller`'s. Largest crate. @@ -95,8 +95,8 @@ Per-hive (host + in-container): utility crate. - **`hive-metric/`** — one-shot CLI pushing a labeled metric over OTLP. - **`hive-meta-flake/`** — the meta flake's `flake.nix` text, a pure - render, and the reads of its pins. hive-c0re and swarm-controller both call - it. + render, and the reads of its pins. swarm-controller renders with it; + hive-c0re reads the pins of the `internal/meta` it builds from. Wire types + socket plumbing: diff --git a/docs/agent-lifecycle/agent-roster.md b/docs/agent-lifecycle/agent-roster.md index dbeccd7b..ad493fa2 100644 --- a/docs/agent-lifecycle/agent-roster.md +++ b/docs/agent-lifecycle/agent-roster.md @@ -25,7 +25,7 @@ create` (or the swarm UI, via `POST /api/agents`) writes that identity once, by calling `ensure_agent_identity`. This page is about a different, **hive-local** file: the hive-c0re-owned -**meta repo**, alongside `flake.nix`, at +`meta/` directory's `topology.json`, at `/var/lib/hyperhive/meta/topology.json`: ```json @@ -102,10 +102,8 @@ state dirs work as for every other agent. `/var/lib/hyperhive/agents` bind-mounted RW at `/agents` (so it can manage any agent's state dir — config isn't authored there, since a real config change is a PR from a clone), plus RO mounts for - `/applied` (diff against what's deployed) and `/meta` (system-wide - deploy log). Any agent holding it gets the same mounts. - The operator dashboard's `POST /api/meta-update` is the only path - that writes `flake.lock`. + `/applied` (diff against what's deployed) and `/meta` (the hive's + topology and limits files). Any agent holding it gets the same mounts. - **Prompt** — identical for every container but one block (below): `prompts/system.md` carries `` / `` marker diff --git a/docs/agent-lifecycle/config-changes.md b/docs/agent-lifecycle/config-changes.md index 5c1cca40..b79f5401 100644 --- a/docs/agent-lifecycle/config-changes.md +++ b/docs/agent-lifecycle/config-changes.md @@ -113,9 +113,11 @@ until you create ruth again from the swarm dashboard. ## Meta flake -The hive-c0re-owned repo at `/var/lib/hyperhive/meta/` -declares one flake input per agent (`agent-.url = -"git+/agent-configs/.git?rev="`) and one +A hive has no meta flake of its own. It builds every agent from the swarm's +`internal/meta`, which swarm-controller commits ([below](#the-swarms-meta-flake)). +It declares one flake input per agent (`agent-.url = +"git+/agent-configs/.git?rev="`, the head of `main` on the +agent's forge config repo, the one config PRs merge into) and one `nixosConfigurations.` output per agent. Each output wraps `inputs.agent-.nixosModules.default` with the identity + `HIVE_PORT` / `HIVE_LABEL` injection module. Values that differ per host @@ -123,53 +125,32 @@ declares one flake input per agent (`agent-.url = address) aren't in it: `hive-priv` writes them to the container's `/etc/hyperhive-hive-facts` at start (see `docs/process/conventions.md`). -Containers run against `--flake /var/lib/hyperhive/meta#`, or against -`internal/meta` when it pins the agent ([below](#the-swarms-meta-flake)). +A config that sits only in the hive's `applied/` mirror, not on forge +`main`, never deploys. -The declared input url is the head of `main` on the agent's forge config repo -(`agent-configs/`, the one config PRs merge into), which `sync_agents` -reads at render time. The flake text depends only on those shas and the -agent set, so any machine that can reach the forge resolves it. `sync_agents` fetches -each repo's `main` with the hive's read token (`forge::reader_token()`), -pins its head, and takes the canonical `follows` lines from the -`flake.lock` at that commit. If the forge is unreachable or a repo has no -`main`, `sync_agents` fails for that agent with its name and writes -nothing; there is no fallback to a local path. A config that sits only in -the hive's `applied/` mirror, not on forge `main`, never deploys. -The rebuild paths re-lock the input with `nix flake update agent-` against that -same pinned url, so `flake.nix` and `flake.lock` name no local path. +What `hive-c0re` still keeps under `/var/lib/hyperhive/meta/` is plain +state, bound read-only into the root agent's container as `/meta`: +`topology.json`, the swarm resource limits, and the permission seed files of a +hive that predates the swarm's permissions. `agent_set::refresh(agents: +&[AgentSpec])` keeps `topology.json`, the gateway's agent-socket map and its +nginx include in line with the live agents. `provision_container`, `destroy`, +`rebuild` and the startup migration call it. It refuses an empty agent list, +which means `nixos-container list` failed, and it fails when `HIVE_FORGE_URL` +is unset, since every agent would come up with no forge. -Lock updates are single-phase and commit when the lock changed: -`meta::lock_update_for_rebuild(name)` relocks one agent's input for a -relocking rebuild (the manual `↻ R3BU1LD` button, and the rebuild a -merged config commit queues), and `meta::lock_update_hyperhive()` is the -autoupdate flake-rev bump (one shot before per-agent rebuilds). - -`meta::sync_agents(hive: &HiveEnv, agents: &[AgentSpec])` — `hive` -carries `hyperhive_flake`, `dashboard_port`, and the rest of the -per-hive config — is the idempotent reconciler called by `provision_container`, -`destroy`, `rebuild`, and the startup migration. Renders `flake.nix` -from the agent list; if it differs from disk, or `flake.nix` or `flake.lock` -has an uncommitted change (a sync stopped before its commit), runs -`nix flake lock` + commits as `regenerate meta flake` (or -`seed meta from N agent(s)` on the first call). A clean tree whose -`flake.nix` already matches returns without running nix. - -The root agent has `/meta` RO-bound inside its container: -`git -C /meta log --oneline` is the swarm-wide deploy log, -`cat /meta/flake.lock | jq '.nodes["agent-"].locked'` -resolves which sha the flake pins each agent at right now. -Dashboard surfaces the same info as a `deployed:` chip -per container row. +The dashboard's `deployed:` chip per container row shows the config +commit the agent's last successful build pinned. ## The swarm's meta flake -swarm-controller also commits a meta flake of its own, `internal/meta`, with -the same renderer: one `flake.nix` and `flake.lock` for every agent any hive -places (the pins: [`docs/swarm/builder.md`](../swarm/builder.md#meta-flake-lock)). -A hive builds an agent from it when the repo has a commit that pins the -agent's current config, and from its own meta flake above otherwise, so a hive -older or newer than the controller keeps building. +swarm-controller commits `internal/meta`: one `flake.nix` and `flake.lock` for +every agent any hive places (the pins: +[`docs/swarm/builder.md`](../swarm/builder.md#meta-flake-lock)). A hive builds +every agent from it. While the repo has no commit that pins an agent's current +config, that agent's build fails and names the agent. The controller queues +its update of the repo before the deploy request for a merged config commit +and for a new agent, so a failure here is rare. It sends the deploy even when +that update failed, and the build then fails as described. - **Choosing the commit.** The rebuild's `MetaSync` node reads the repo's default-branch head with the hive's read token (`forge::reader_token()`) @@ -178,8 +159,9 @@ older or newer than the controller keeps building. `main`, an archetype agent from the archetype it has now. An agent switched between custom and archetype, or to another archetype, fails that test. A hive without the `core` token file that nix's git reads, a repo with no - commit, a head that lacks the agent and a head behind the config all select - the hive's own flake. `MetaSync` records the commit in `hive-c0re`'s own state, + commit, a head that lacks the agent and a head behind the config each fail + the build with an error that names the agent. `MetaSync` records the commit, + with the hyperhive and config commits the head pins, in `hive-c0re`'s own state, `/var/lib/hyperhive/meta-rev/`, which the agent can't write (the container mounts its runtime directory read-write); `Prebuild` and `Swap` read that record, so both build one flake. A first deploy resolves the commit in its `Create` @@ -190,10 +172,8 @@ older or newer than the controller keeps building. `CreateContainer`). `hive-priv` accepts only a 40-digit lowercase hex commit and builds the flake from its own forge URL (`HIVE_PRIV_FORGE_URL`); the URL never comes from a request. On the wire the - `hive_priv_sock::PrivRequest` variants carry an optional field: + `hive_priv_sock::PrivRequest` variants carry a required field: `{"op":"update_container","name":"atlas","stream":true,"meta_rev":"<40 hex>"}`. - Without it, as from a hive-c0re that predates the field, hive-priv builds the - hive's own flake. - **Fetching.** nix fetches the forge's inputs with `git`. Both `hive-c0re` and `hive-priv` carry a `$HOME/.gitconfig` that answers for the forge host through `git-credential-hive-forge` (the `core` token file) and rewrites the @@ -201,6 +181,11 @@ older or newer than the controller keeps building. `http`. - **Hyperhive.** The commit pins one hyperhive rev for the swarm, so an agent runs the hyperhive of the controller's deploy, not its hive's. +- **Markers.** After a successful build `hive-c0re` writes the hyperhive + commit the head pins to `applied/..hyperhive-rev`, and the config + commit to `applied/..config-rev` (none for an archetype agent). The boot + reconcile ([below](#autoupdate-on-startup)) and the dashboard's update chip + read them. ## Two repos per agent @@ -223,11 +208,9 @@ older or newer than the controller keeps building. ├── agent.nix # working tree of main └── # also tracked -/var/lib/hyperhive/meta/ swarm-wide flake — core -├── .git/ # one commit per lock -│ # change -├── flake.nix # generated from agent set -└── flake.lock # pins each agent's sha +/var/lib/hyperhive/meta/ plain hive state — core +├── topology.json # the live agent set +└── swarm-resource-limits.json # the swarm's per-agent caps ``` Why two physical repos: the submitting agent's `/agents//config/` is @@ -236,8 +219,8 @@ proposed tree. The applied repo is never bind-mounted (except the read-only `.git` exposure described below) so a destructive move inside the container can't reach it. -The container's `--flake` ref is `/var/lib/hyperhive/meta#` (or `internal/meta`) -(see "Meta flake" above). The agent's own `applied//flake.nix` +The container's `--flake` ref is `internal/meta` (see "Meta flake" above). +The agent's own `applied//flake.nix` is a fixed boilerplate that exports `nixosModules.default = import ./agent.nix`; the meta flake imports that module and wraps it with identity + `HIVE_PORT` / `HIVE_LABEL`. @@ -246,7 +229,7 @@ wraps it with identity + `HIVE_PORT` / `HIVE_LABEL`. hive-c0re plants `deployed/0` on the seed commit at first spawn. A merged config commit that deploys plants no tag: the merged PR on the -forge and meta's lock commits record it. A config PR nobody merges +forge and `internal/meta`'s commits record it. A config PR nobody merges carries no extra state on the forge side: the PR stays open, and the submitter pushes again (or closes it) to retry. @@ -335,9 +318,6 @@ git -C /agents//config log applied/main --oneline git -C /agents//config show applied/refs/tags/deployed/0 # the seed commit git -C /agents//config show applied/refs/tags/denied/ # body = operator note git -C /agents//config rebase applied/main # base in-flight work on what's deployed - -git -C /meta log --oneline # swarm-wide deploy history -cat /meta/flake.lock | jq '.nodes | with_entries(select(.key | startswith("agent-")))' ``` The RO binds block push at the kernel level — git plumbing inside the @@ -346,16 +326,17 @@ container can't corrupt either authoritative repo. ## Startup migrations (older hosts) hive-c0re runs a couple of idempotent migrations on every startup so a -host set up before the tag-driven-deploy + meta-flake scheme (both -described above) converges to it automatically. Each phase is a no-op +host set up before the tag-driven-deploy scheme (described above) +converges to it automatically. Each phase is a no-op once already applied: - **Tags**: hive-c0re tags agents from before the tag-driven scheme `deployed/0` on `main` once. Non-destructive — it doesn't touch live containers, state dirs, or claude creds. -- **Meta flake**: rewrites each `applied//flake.nix` to the +- **Agent files**: rewrites each `applied//flake.nix` to the module-only boilerplate, wires the `applied` remote in each proposed - repo, and bootstraps the meta repo from the current agent list. Set + repo, and refreshes the files that follow the agent set + (`agent_set::refresh`) from the current agent list. Set `HIVE_SKIP_META_MIGRATION=1` on the service to defer this phase. No state loss in either migration: claude creds, `/state/` notes, the @@ -450,19 +431,25 @@ swarm-level alert could carry the same signal) — only reach for a new `hive-c0re serve` runs `auto_update::run` in a background task right after opening the coordinator. It enumerates managed containers and -rebuilds any whose recorded hyperhive rev differs from the current -one — sub-agents and the root agent go through the same -`job_queue::templates::rebuild` DAG. +rebuilds any whose recorded hyperhive rev differs from the one +`internal/meta` pins now. That's how a hive that missed the swarm's +fan-out catches up. Sub-agents and the root agent go through the same +graceful rebuild subgraph. -"Rev" = canonical filesystem path of -`services.hyperhive.c0re.hyperhiveFlake`. Marker -file: `/var/lib/hyperhive/applied/..hyperhive-rev`. If the -flake input has no canonical path (for example a `github:` URL), -autoupdate is a no-op — rebuild manually. +"Rev" = the hyperhive commit pinned by the head of `internal/meta`, as +the hive reads it with its forge token. Marker +file: `/var/lib/hyperhive/applied/..hyperhive-rev`, written after +a successful build. If the hive can't read the pin (no forge token, the +forge down, `internal/meta` not published yet), the scan rebuilds +nothing; a rebuild would have no flake to build from either. A hive deploy +alone doesn't rebuild agents. -The dashboard surfaces pending updates per agent: a clickable -"needs update ↻" badge appears whenever the marker differs from -current rev. The badge POSTs `/api/rebuild/`, which inserts the +A stopped agent gets its rebuild at its next start: the start path upgrades +the start of a stale, stopped agent to a rebuild followed by the start. + +The dashboard surfaces pending config changes per agent: a clickable +"needs update ↻" badge appears whenever the agent's applied repo has a +commit that its last build didn't pin. The badge POSTs `/api/rebuild/`, which inserts the same `job_queue::templates::rebuild` DAG so manual triggers and the startup scan can't drift. When at least one container is stale, a top-level `↻ UPD4TE 4LL` button appears that loops over every diff --git a/docs/agent-lifecycle/persistence.md b/docs/agent-lifecycle/persistence.md index a463e94c..b7758ac1 100644 --- a/docs/agent-lifecycle/persistence.md +++ b/docs/agent-lifecycle/persistence.md @@ -438,7 +438,7 @@ this hive's archetype agents runs, as last read from the swarm's with an entry has no `applied/` repo and no `agent-` meta input; see [archetypes](../swarm/archetypes.md). -hive-c0re renders containers onto `meta#` at creation. +hive-c0re creates containers from `internal/meta` (`nixosConfigurations.`). ## Destroy vs purge diff --git a/docs/integrations/forge.md b/docs/integrations/forge.md index 14b30707..0195f8dc 100644 --- a/docs/integrations/forge.md +++ b/docs/integrations/forge.md @@ -75,8 +75,7 @@ forge container: - fetching a merged config commit for a swarm deploy, and `hivectl forge reconcile-config` -- reseeding a purged `applied` repo, and the meta-update cascade's - fast-forward of `applied/main` +- reseeding a purged `applied` repo - seeding `proposed` from `agent-configs/` on first deploy - cloning and pulling `/knowledge` @@ -84,7 +83,7 @@ The builds themselves read as `core`. nix fetches the config inputs and `internal/meta` with `git`, and `git` gets its credential from `git-credential-hive-forge`, which reads the `core` token file. A hive without that file fetches nothing private through nix, whatever its own token reads, so -it builds from its own meta flake rather than `internal/meta`. +its builds fail: they have no other flake than `internal/meta`. Roll a change to any of this out store host first (its granter writes the controller's grant on `swarm/hives/+/forge/token`), then the controller host, diff --git a/docs/process/gotchas.md b/docs/process/gotchas.md index 4f16ca6a..bf497a93 100644 --- a/docs/process/gotchas.md +++ b/docs/process/gotchas.md @@ -294,15 +294,15 @@ internal convention that routes an agent name to ``` # ❌ silently builds the wrong thing (or errors if attr doesn't exist) -nix build /var/lib/hyperhive/meta#argus.config.system.build.toplevel +nix build "git+/internal/meta.git?rev=#argus.config.system.build.toplevel" # ✅ explicit path nix build actually resolves -nix build /var/lib/hyperhive/meta#nixosConfigurations.argus.config.system.build.toplevel +nix build "git+/internal/meta.git?rev=#nixosConfigurations.argus.config.system.build.toplevel" ``` `lifecycle::prebuild_toplevel` hit this once by constructing the attr -path as `{flake_ref}.config…` — which produced `meta#argus.config…` -instead of `meta#nixosConfigurations.argus.config…`. The fix: +path as `{flake_ref}.config…` — which produced `#argus.config…` +instead of `#nixosConfigurations.argus.config…`. The fix: `split_once('#')` to separate flake path from name, then template `{path}#nixosConfigurations.{name}.config.system.build.toplevel`. diff --git a/docs/scheduler/coordinator.md b/docs/scheduler/coordinator.md index 774efd48..c3f7588a 100644 --- a/docs/scheduler/coordinator.md +++ b/docs/scheduler/coordinator.md @@ -14,12 +14,12 @@ section covers the concrete node inventory hive-c0re builds on top of that engine, plus the internals (module layout, scheduler mechanics, resource/lease semantics). -hive-c0re submits every container/meta operation (rebuild, meta-update, first-spawn, power +hive-c0re submits every container operation (rebuild, first-spawn, power changes) to the global job-DAG queue (`hive-c0re/src/job_queue/`) as a **DAG of primitive nodes**. One scheduler task drives all DAGs; concurrency comes from the resource classes below, not from multiple workers. -Special cases like graceful stop, deferred starts, and the meta-update -cascade need no bespoke code paths — each takes the shape of a DAG built +Special cases like graceful stop, deferred starts, and the boot sweep +need no bespoke code paths — each takes the shape of a DAG built from the same primitive nodes. ### Two levels: DAG and node @@ -46,8 +46,7 @@ Nix-heavy — hold one of the `buildSlots` permits for the node's duration: | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Prebuild` | `lifecycle::prebuild_toplevel` — build the toplevel out-of-band while the container keeps serving (its meta preamble is the upstream `MetaSync` node). Skipped when the container is already down; `Swap` builds inline instead | | `Swap` | drop-in rewrite + `nixos-container update` profile-swap (requires the container stopped); the post-swap bookkeeping tail lives in the sibling `RebuildBookkeeping` node | -| `Create` | first-spawn `nixos-container create` proper; assumes the upstream `Provision` node already registered the agent in meta | -| `MetaLock` | meta flake lock bump (`lock_update` / boot-sweep `lock_update_hyperhive`, commit fused — see below); fans out child `Rebuild` DAGs on completion | +| `Create` | first-spawn `nixos-container create` proper; assumes the upstream `Provision` node already registered the agent; builds from the `internal/meta` commit it resolves and marks the agent's pins | Cheap — no build slot: @@ -55,13 +54,13 @@ Cheap — no build slot: | Node | Behavior | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MetaSync` | the rebuild's meta preamble — rebuild-dir prep, idempotent meta `sync_agents`, optional per-agent relock. Holds the `MetaWindow` resource (below); deliberately its own node so the window never covers `Prebuild`'s multi-minute build | -| `Provision` | first-spawn pre-create provisioning — proposed/applied repos, state subvolume, meta registration (`sync_agents`); runs ahead of `Create` so the `nixos-container create --flake meta#` ref resolves. Store/meta-only, no container yet | +| `MetaSync` | the rebuild's preamble — rebuild-dir prep, the agent-set refresh (`agent_set::refresh`), and the `internal/meta` commit `Prebuild` and `Swap` build from, resolved once and recorded; fails when the repo doesn't pin the agent's current config. Declares no resources | +| `Provision` | first-spawn pre-create provisioning — proposed/applied repos, state subvolume, agent-set refresh (`agent_set::refresh`); runs ahead of `Create`. Store-only, no container yet | | `Reconcile` | idempotent power converge: read `wanted` (below) + observed state; start if `Up` & down (cold-start fallback included), stop if `Offline` & up, else noop | | `Start` | mechanical container start — runtime dir + drop-ins, `start_with_fallback`, MCP listener registration, the manager kick. Fanned out by a `Reconcile` that observed `wanted = Up` and the container down | | `Stop` | mechanical container stop — `nixos-container` kill, MCP listener unregister, the `Killed` manager notify. Fanned out by a `Reconcile` that observed `wanted = Offline` and up | | `StopForUpdate` | mechanical `nixos-container stop` for the profile swap; never touches `wanted`; noop if already stopped | -| `RebuildBookkeeping` | the swap's Ok-only bookkeeping tail — rev marker, forge/matrix sync, manager kick, rescan, meta-inputs snapshot; `AfterOk(Swap)` so it runs only on a successful swap (the DAG's `EmitRebuilt` tail node emits the `Rebuilt` manager event, not here). Split out of `Swap` for dashboard visibility + retry granularity, declares no resources of its own — a coordinated child of the `AgentWindow` brace | +| `RebuildBookkeeping` | the swap's Ok-only bookkeeping tail — rev markers, forge/matrix sync, manager kick, rescan; `AfterOk(Swap)` so it runs only on a successful swap (the DAG's `EmitRebuilt` tail node emits the `Rebuilt` manager event, not here). Split out of `Swap` for dashboard visibility + retry granularity, declares no resources of its own — a coordinated child of the `AgentWindow` brace | | `AgentWindow` | pure resource holder — the brace for one agent's rebuild. Declares the build slot + agent lease atomically and holds both for its whole subtree, so `Prebuild` and the `Signal`→`Drain` quiesce window run concurrently instead of one nested under the other. Performs no work; see _Braces_ | | `Signal` | set the graceful fence + kick, so the harness runs one stop-checkpoint turn | | `Drain` | await the harness clearing the fence, bounded by the 3-min graceful-stop timeout; resolves ok either way | @@ -80,29 +79,8 @@ Cheap — no build slot: -Deliberately, **no `GitCommit` node** exists: `meta.rs` fuses each mutation -with its commit under its internal `META_LOCK` mutex, so a standalone commit -node would open a dirty-working-tree window between nodes. - -Two further layers protect the meta repo across _windows_ that span multiple -`META_LOCK` acquisitions: - -- **The deploy window** (`Resource::MetaWindow`): a global, capacity-1 queue - resource declared by every node kind that mutates the meta repo — `MetaSync`, - `MetaLock` and `Provision`'s agent registration. Two meta - mutations can therefore never interleave, so no commit lands inside another - node's window. It's a queue resource rather than a runtime mutex - because a subtree root holds a resource across its whole subtree, which - a `MutexGuard` (bounded by one executor fn) can't. For the same reason the window must stay - _off_ long store-only work: the rebuild's meta preamble is its own - `MetaSync` node, a sibling of (never a parent of) `Prebuild`, so the - toplevel build runs outside the window and `buildSlots > 1` still gives - concurrent rebuilds across agents. -- **Path-limited commits**: the targeted meta committers (perm files, - topology, lock bumps) commit `-- ` with path-scoped - dirty checks, so even a non-queue caller (boot migration, destroy's - `sync_agents`) can never sweep someone else's staged content into its - commit. +Nothing in the queue commits to a meta repo: the hive has none to commit to, +and `agent_set::refresh` serializes itself with its own mutex. ### Every operation as a DAG @@ -138,8 +116,7 @@ restart(a..): online a: [Signal→Drain→ if graceful] StopForUpdate(a) → Re offline a: Reconcile(a) (nothing to stop; Reconcile converges to existing wanted) start(a..): a: SetWanted(a,Up) → Reconcile(a) (down+stale ⇒ SetWanted(a,Up) → «rebuild subgraph») spawn(a): [wanted=Up at deploy] Provision(a) → Create(a) → WriteDropin(a) → Reconcile(a) -meta-update(inp): MetaLock(inp) →(in-DAG) «rebuild subgraph» per affected agent -boot: (if any rev marker stale) MetaLock(hyperhive) →(in-DAG) «rebuild subgraph» per stale agent; +boot: «graceful rebuild subgraph» per stale agent (rev marker ≠ the `internal/meta` pin); plus Reconcile(a) for every drifted agent (all ONE DAG) ``` @@ -157,14 +134,9 @@ Notable collapses: - **Graceful stop needs no watcher thread**: `Signal`/`Drain` are cheap, so a whole-hive graceful stop fires every agent's signal immediately and all drains overlap; each DAG's tail `Reconcile` does the actual stop. -- **The meta-update cascade grows in the same DAG on completion**: - `MetaLock`'s executor computes the affected agent set after the bump lands - and grows one `rebuild` subgraph per agent into its _own_ DAG via - `append_subgraph` (rooted on the `MetaLock`, `relock = false` so the cascade - doesn't revert the bump). Not child DAGs — one DAG, no `parent_id`. A failed - bump appends nothing (no cancel-children dance). Same shape as the startup - sweep; the meta-update DAG carries the `Rebuilding` transient so each cascade - agent keeps crash-watch suppression during its `Swap`. +- **The boot sweep is one DAG**: it declares one graceful `rebuild` subgraph + per stale agent, each an independent root, beside a `Reconcile` per drifted + agent. Not child DAGs — one DAG, no `parent_id`. ### Desired-state (spec vs status) @@ -174,7 +146,7 @@ Per-agent power _intent_ — `wanted: Up | Offline` — is durable as the two. Setting `wanted` is never a queued node: the power layer (`job_queue/power.rs`) writes the row synchronously, then inserts the DAG whose `Reconcile` reads the fresh value — rapid toggles are last-writer-wins. -Power toggles never commit to the meta repo. Every operator power surface — +Every operator power surface — dashboard buttons, the MCP tools, and `hivectl stop/start/restart/kill` — rides the queue through that power layer, so intent, lease serialization, and crash-watch suppression can't drift per surface; the only direct starts @@ -188,9 +160,9 @@ The admin-socket responses carry the submitted DAG ids; `hivectl` polls `HostRequest::QueueNodes` (~1s) and prints a progress line per DAG — roll-up glyph, template, agent, node chain — so CLI verbs block until their jobs finish (`--no-wait` opts out; failures exit -non-zero). Nodes appended in-DAG (a `MetaLock` growing per-agent rebuild -subgraphs, a `Reconcile` fanning its `Start`/`Stop`) join the same DAG, so -they surface under that DAG's id in the same loop — no separate child DAGs. +non-zero). Nodes appended in-DAG (a `Reconcile` fanning its `Start`/`Stop`) +join the same DAG, so they surface under that DAG's id in the same loop — no +separate child DAGs. ### Scheduler semantics @@ -213,8 +185,8 @@ resources are free. Resources: (`SetWanted` is a store write, not a container op, but takes the lease anyway so a power-op DAG's intent write + reconcile is atomic — two racing ops can't clobber intent before either reconciles.) **Lease-exempt**: `MetaSync`, - `Prebuild`, `Provision`, `MetaLock` — they - touch the store / meta, not the running container, which is exactly why a + `Prebuild`, `Provision` — they + touch the store, not the running container, which is exactly why a stop can land while another DAG's prebuild is still building. Also exempt, for a different reason, are the rebuild subtree's own members (`StopForUpdate`, `Swap`, `Signal`, `Drain`, `RebuildBookkeeping`): they genuinely do touch the @@ -317,56 +289,39 @@ render. On startup, `auto_update::run` classifies every agent by rev freshness (the per-agent `.{name}.hyperhive-rev` marker under `/var/lib/hyperhive/applied/` -vs the current flake path) and persisted `wanted` intent, then: +vs the hyperhive commit the head of `internal/meta` pins) and persisted +`wanted` intent, then: -1. **Config path** — when _any_ marker is stale, submit one `Boot` - DAG: a `MetaLock` (hyperhive input bump, non-fatal) that grows an in-DAG - `Rebuild` subgraph for each stale agent whose `wanted = Up` (name-sorted). Stale but wanted-offline agents get no boot-time nix work — their - rebuild happens on their next start (the start submit path upgrades a - stale start to rebuild+start), which is also why the lock bump runs even - when every stale agent is offline: those later start-upgrades must build - against the bumped lock. Each child rebuild's tail `Reconcile` brings the - agent (back) up, covering both the running-stale and stopped-but-wanted-up - cases. +1. **Config path** — submit one `Boot` DAG holding a graceful `Rebuild` + subgraph for each stale agent whose `wanted = Up` (name-sorted). This is how + a hive that missed the swarm's fan-out catches up. Stale but wanted-offline + agents get no boot-time nix work — their rebuild happens on their next start + (the start submit path upgrades a stale start to rebuild+start). Each + rebuild's tail `Reconcile` brings the agent (back) up, covering both the + running-stale and stopped-but-wanted-up cases. A hive that can't read the + pin (no forge token, the forge down, no `internal/meta` yet) rebuilds + nothing. 2. **Power path** — every agent whose observed state drifted from `wanted` gets a plain `Reconcile` DAG (`kind = reconcile`, source `auto_update`). -Booting with no config change performs **no meta commit** — only reconciles. -The sweep reason records the rebuild / deferred / up-to-date counts so the +Booting with no pin change performs no rebuild — only reconciles. +The boot log line records the rebuild / deferred / up-to-date counts so the operator sees at a glance how much work the boot triggered. `auto_update::run` seeds agents without an `agent_power` row from observed state during classification (the one-time migration; thereafter the DB is authoritative). ## Meta flake -`meta.rs` owns the single coordinator-managed flake at `/var/lib/hyperhive/meta/`. -This flake consumes every agent's applied config repo as a flake input and exports -one `nixosConfiguration` per agent. Container lifecycle ops drive the lock file so -meta's git log is the system-wide deploy audit trail. - -Key operations: - -- **`sync_agents`** (idempotent) — render `flake.nix` for the current agent set, - init the repo on first call, relock if the rendered contents changed, commit. - Called by spawn / destroy / startup migration. -- **`lock_update_hyperhive`** — one-shot for the boot-reconcile path (the - sweep DAG's `MetaLock` node): bumps the `hyperhive` input lock and commits; - the scheduler fans out the agent rebuilds on completion. - -Every public `meta.rs` operation takes the module's internal `META_LOCK` -mutex, so concurrent job-queue nodes never -race on the repo's `.git/index.lock`. - -Each git or nix child under `META_LOCK` has a deadline, so a hung forge fetch -fails the operation instead of holding the lock: 10 minutes for -`nix flake update`/`lock` and forge fetch/push, 2 minutes for local git, including -the `rev-parse` and `show` reads `pin_agents` makes. The error names the command -and the deadline. Each child leads its own process group and the timeout -`SIGKILL`s the whole group, so a `git` or credential helper spawned by a hung -`nix` dies too. A caller waiting on `META_LOCK` for over a minute logs a -`meta_lock: still waiting` warning every minute, naming its own operation and -the holder's. Waiters keep their place in the lock's FIFO queue across warnings. +Agents build from the swarm's `internal/meta` flake, which swarm-controller +commits and every hive reads (see +[Config changes](../agent-lifecycle/config-changes.md#the-swarms-meta-flake)). +The hive keeps no flake of its own. `meta_rev.rs` picks the `internal/meta` +commit an agent builds from and records it; `agent_set.rs` keeps the +host-side files that follow the agent set (`topology.json`, the gateway's +agent-socket map and nginx include) in line with the live agents, and +`meta.rs` keeps the one remaining commit to the legacy `meta/` git repo, the +removal of the permission seed files. --- @@ -389,10 +344,8 @@ one go, rather than the double-bounce a live `update` would trigger. Sequence for a rebuild DAG (each step is its own queue node): -1. `MetaSync` — rebuild-dir prep, meta `sync_agents`, and (unless this is a - meta-update cascade child) the per-agent relock. Short, and the only step - that mutates the meta repo, so it's the only one holding the global deploy - window. +1. `MetaSync` — rebuild-dir prep, the agent-set refresh, and the choice of the + `internal/meta` commit to build from. Short, and it declares no resources. 2. `Prebuild` — build the new `system.build.toplevel` **before** stopping. The container keeps serving the previous generation while eval + fetch + build happen out-of-band. `nixos-container update` then finds the result @@ -401,7 +354,7 @@ Sequence for a rebuild DAG (each step is its own queue node): container is already down — a stopped agent has no uptime to preserve, so `Swap` builds inline instead of paying the double eval. 3. `StopForUpdate` — bring the container down (noop when already stopped). -4. `Swap` — `nixos-container update --flake meta#` profile-swap +4. `Swap` — `nixos-container update` profile-swap from `internal/meta` (near-instant after the prebuild). 5. `Reconcile` — boot into the new generation when `wanted = Up`; the in-container activation script transitions old → new. Holds no build @@ -424,15 +377,15 @@ fallback. For a first-time `create`, `nixos-container create` is atomic: if the build fails, it leaves no container record to clean up. A separate prebuild would just duplicate -the eval, so it's skipped. Sequence: `create --flake meta#` → write nspawn +the eval, so it's skipped. Sequence: `create` from `internal/meta` → write nspawn flags → `systemctl daemon-reload` → `start`. ### Prebuild attr path -`nix build` doesn't autoresolve `meta#` against `nixosConfigurations` the +`nix build` doesn't autoresolve `#` against `nixosConfigurations` the way `nixos-container` does internally. nix requires the explicit attr path `#nixosConfigurations..config.system.build.toplevel`; -using the bare `meta#` ref would make nix look in `packages`, `legacyPackages`, +using the bare `#` ref would make nix look in `packages`, `legacyPackages`, or the flake root directly — none of which exist in the rendered meta flake. --- diff --git a/docs/swarm/archetypes.md b/docs/swarm/archetypes.md index ab49de4e..201c1e6e 100644 --- a/docs/swarm/archetypes.md +++ b/docs/swarm/archetypes.md @@ -116,12 +116,11 @@ rebuild, and keeps the last value read in without the queue. When the read fails, a rebuild keeps the value stored there. For an archetype agent the hive: -- seeds no `proposed` or `applied` repo, and gives the meta flake no +- seeds no `proposed` or `applied` repo, and gives `internal/meta` no `agent-` input. `mkAgent` imports `hyperhive.hyperhiveArchetypes..module` in place of the agent's own flake, and sets `HIVE_ARCHETYPE` in the harness environment; -- relocks no agent input on a rebuild or a meta-input bump, and ignores the - `rev` of a swarm deploy request; +- ignores the `rev` of a swarm deploy request; - mounts an empty `/agents//config/`. The agent's system prompt says it has no config repo and names its diff --git a/docs/swarm/builder.md b/docs/swarm/builder.md index 6f0274e0..71bb26a6 100644 --- a/docs/swarm/builder.md +++ b/docs/swarm/builder.md @@ -125,7 +125,7 @@ lock doesn't wait for a build in progress and has ten minutes. The builder commits nothing. swarm-controller sends the request. It renders `internal/meta`'s `flake.nix` -with the same renderer hive-c0re uses (`hive-meta-flake`), all over `https`. +with the `hive-meta-flake` renderer, all over `https`. Each custom agent that a hive places pins to its `agent-configs/` `main`; the controller omits and logs one whose repo has no commit on `main`, so it doesn't block the rest. Hyperhive pins to one rev, a ratchet on the controller's own @@ -147,8 +147,9 @@ repo. It re-renders when it starts, and again every ten minutes while the latest update hasn't succeeded (a tick adds nothing while one is pending or running, or after a success), before it deploys a merged config PR, and before the first deploy of a new agent. Those deploys run whether or not the -update succeeded: a hive that builds from `internal/meta` does so only while it -pins the agent's current config (see +update succeeded: a hive builds only from `internal/meta`, so while the repo +doesn't pin the agent's current config that build fails and names the agent +(see [Config changes](../agent-lifecycle/config-changes.md#the-swarms-meta-flake)). A render equal to the committed `flake.nix` sends no lock. diff --git a/docs/tools/matrix.md b/docs/tools/matrix.md index 43768a2c..35c0a696 100644 --- a/docs/tools/matrix.md +++ b/docs/tools/matrix.md @@ -195,7 +195,7 @@ The agent's `flake.nix` forwards every flake input to `agent.nix` as the `flakeInputs` module arg, so you pull in external MCP-server flakes by adding them to `inputs.*` and reference them as `flakeInputs..packages.${pkgs.system}.default` — the resolved -sha lands in the agent's own `flake.lock` and rolls up to meta's. +sha lands in the agent's own `flake.lock` and rolls up to `internal/meta`'s. `allowedTools` defaults to `["*"]`, which expands to `mcp____*` (every tool from that server autoapproved). Restrict diff --git a/docs/web-ui/README.md b/docs/web-ui/README.md index 5cf78189..97276e68 100644 --- a/docs/web-ui/README.md +++ b/docs/web-ui/README.md @@ -16,13 +16,13 @@ surfaces — Stats, the API docs and the swarm UI — and each of those pages links back to the dashboard. The dashboard is where you'll spend most of your time. It's a single -page with five tabs: +page with four tabs: - **4G3NTS** — every agent, live. This is the default tab and the one you'll check most. -- **R3BU1LD QU3U3**, **M3T4 1NPUTS** and **BUILD L0GS** — the build +- **R3BU1LD QU3U3** and **BUILD L0GS** — the build lifecycle: the rebuild queue with a live log of whatever is building, - the meta flake inputs you can bump, and the build log history. + and the build log history. - **K3PT ST4T3** — kept state from agents with no container (tombstones), with a purge button per entry. diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index 780e5aa9..6dfc56bb 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -33,7 +33,7 @@ dashboard tab strip. `HIVE_AUTHELIA_PUBLIC_URL` (set from `services.hyperhive.swarm.authelia.url`). If the name doesn't load the avatar shows a question mark, and without the URL the popover has no links. -- **Tab strip**: `◆ 4G3NTS ◆`, `◆ R3BU1LD QU3U3 ◆`, `◆ M3T4 1NPUTS ◆`, +- **Tab strip**: `◆ 4G3NTS ◆`, `◆ R3BU1LD QU3U3 ◆`, `◆ BUILD L0GS ◆`, `◆ K3PT ST4T3 ◆`. In-page tabs only — ST4TS lives on its own page, reached from the pages menu. Count pills on 4G3NTS (stale containers), R3BU1LD QU3U3 (rebuilds in flight) and @@ -73,7 +73,7 @@ agent is stale. Passive / rare-interaction state. `kept.js` renders it from the `/api/state` snapshot and keeps it live from the `tombstones_changed` -stream event. (Rebuild queue and meta inputs are dashboard tabs too — +stream event. (The rebuild queue and build logs are dashboard tabs too — see below.) Per-container CPU, memory and disk load isn't shown in the hive UI. The @@ -96,16 +96,16 @@ container is all hive-c0re can actually prove. Fixing it properly needs a recorded destroy — **deferred** to the swarm-controller / snapshot-storage rework where the problem changes shape. -## Build tabs (`#rebuild`, `#meta`, `#buildlogs`) +## Build tabs (`#rebuild`, `#buildlogs`) -The build lifecycle — rebuild queue, live build log, meta inputs, and -build log history — is three tabs of the dashboard: **R3BU1LD QU3U3** -(`#rebuild`), **M3T4 1NPUTS** (`#meta`) and **BUILD L0GS** +The build lifecycle — rebuild queue, live build log, and +build log history — is two tabs of the dashboard: **R3BU1LD QU3U3** +(`#rebuild`) and **BUILD L0GS** (`#buildlogs`). They live in `builds.js`, which `tabs.js` imports: the dashboard's one tab strip, `/api/state` snapshot and `/api/dashboard/stream` subscription serve them, with -`rebuild_queue_changed`, `meta_inputs_changed` and `meta_update_running` -on the stream alongside the dashboard's other kinds. The count pill on +`rebuild_queue_changed` on the stream alongside the dashboard's other +kinds. The count pill on R3BU1LD QU3U3 is the number of rebuilds in flight. The 4G3NTS tab does **not** read the graph endpoint, by design — the swarm view stays independent of job-queue internals. Its per-agent pending @@ -115,7 +115,7 @@ handful of pre-tallied counts, not the graph. The banner's "view queue →" link goes to the `#rebuild` tab. **R3BU1LD QU3U3** — pending, in-flight, and recently settled container -operations: rebuilds, meta-update cascades, and first-spawns. One +operations: rebuilds and first-spawns. One operation runs at a time; the worker drains FIFO. **Is a mounted `JobqGraph`** (the shared generic graph-viewer Preact component, `@hive/shared/jobq-graph.js` — the same one swarm-ui uses) — @@ -184,20 +184,6 @@ own container outside `#rebuild-queue-section` so the mounted `JobqGraph`'s own re-renders never disturb the open poll; it hides when nothing is building. -**M3T4 1NPUTS** — inputs in `meta/flake.lock` the operator can -selectively `nix flake update`, rendered as an indented tree: -every fetched input at every depth (`hyperhive`, -`hyperhive/nixpkgs`, `agent-`, `agent-/mcp-`, …), each -shown once at its shallowest path. `read_meta_inputs` walks the -lock graph with a `visited` set, skipping `follows` aliases and rev-less -nodes. A `select all / select none` -control sits above the tree. Checking inputs + submitting bumps -the lock in `/meta/` and rebuilds the selected agents in -sequence; each outcome reaches the root agent as a `rebuilt` -system event. `POST /meta-update`. While a lock-bump ripple runs, -the panel shows a pulsing "⏳ meta-update running" banner and disables -the update button (snapshot field `meta_update_running`, -live event `meta_update_running`). **BUILD L0GS** — all-agents build log history. Lazy-loaded on first tab activation; autorefreshes when @@ -378,7 +364,7 @@ life). Two consequences for anything rendering it: to rebuild-shaped work — `running_transients()` in hive-c0re is a status-only test (any `Running` node whose payload names a non-empty agent lights a pill), so work the operator never - triggered (a meta-update cascade, a crash-recover rebuild, a + triggered (a crash-recover rebuild, a boot-sweep rebuild, a lease-exempt `prebuild`) lights the same mechanism. Ops with no queue node behind them (destroy, migration) supply @@ -575,8 +561,7 @@ below — some endpoints aren't in it yet. - `POST /api/cancel-reminder/{id}` — hard-delete a pending reminder. - `POST /api/retry-reminder/{id}` — re-arm a reminder whose delivery failed (clears the failure state so the scheduler retries). -- `POST /api/meta-update` — `nix flake update` the selected - `meta/flake.lock` inputs, then rebuild the affected agents. + - `GET /api/dashboard/stream` — unified live event channel: broker `sent` / `delivered`, plus the mutation events listed below. Each frame carries `seq`. @@ -620,18 +605,12 @@ payload): - `tombstones_changed` (seq, tombstones: `Vec`) — full snapshot of destroyed-but-state-kept agents. Emitted on destroy, purge, and revive. The K3PT ST4T3 tab subscribes. -- `meta_inputs_changed` (seq, inputs: `Vec`) — full - snapshot of `meta/flake.lock` inputs. The M3T4 1NPUTS tab - subscribes; same shape as `GET /api/meta-inputs`. -- `meta_update_running` (running: bool) — emitted when a - `nix flake update` ripple starts or completes. The M3T4 1NPUTS - tab uses this to show/hide the "⏳ meta-update running" banner. + `/api/state` is **only fetched on cold-load and on the few -forms that mutate non-event-derived state** (PURG3 + -meta-update, since tombstones + meta_inputs aren't event- -shaped yet). Every other section — transients, +forms that mutate non-event-derived state** (PURG3, since +tombstones aren't event-shaped yet). Every other section — transients, containers — derives from `/api/dashboard/stream` after the initial snapshot, maintaining its own client-side store and applying events on diff --git a/frontend/packages/dashboard/src/agents.js b/frontend/packages/dashboard/src/agents.js index 4e529a46..b295a021 100644 --- a/frontend/packages/dashboard/src/agents.js +++ b/frontend/packages/dashboard/src/agents.js @@ -597,7 +597,7 @@ export function renderContainers(s) { // Containers come from the derived map (event-driven) rather than // `s.containers`; `s` still supplies hostname (for the web-ui - // link) and tombstones/meta_inputs (not event-derived yet). The + // link) and tombstones (not event-derived yet). The // tree builder handles the ordering — we don't pre-sort here. const containers = Array.from(containersState.values()); const portConflicts = derivePortConflicts(containers); diff --git a/frontend/packages/dashboard/src/builds.js b/frontend/packages/dashboard/src/builds.js index 1766873b..cfd05299 100644 --- a/frontend/packages/dashboard/src/builds.js +++ b/frontend/packages/dashboard/src/builds.js @@ -1,9 +1,8 @@ // The build lifecycle tabs of the dashboard — rebuild queue, live build -// log, meta inputs, and build log history. Not an entry point: tabs.js +// log, and build log history. Not an entry point: tabs.js // imports it, owns the tab strip and the dashboard event stream, and calls -// the exports below. Three tabs, the panes' ids matching the tab ids: +// the exports below. Two tabs, the panes' ids matching the tab ids: // - rebuild (R3BU1LD QU3U3): pending + running rebuilds + live build log -// - meta (M3T4 1NPUTS): nix flake input selector + meta-update // - buildlogs (BUILD L0GS): all-agents build log history, lazy-loaded // the first time the tab is shown @@ -11,12 +10,11 @@ import { $, fmtAgeSecs, openBuildLogStream } from "./common.js"; import { el } from "@hive/shared/dom.js"; import { themedConfirm } from "@hive/shared/modal.js"; import { h, render } from "preact"; -import { fmtAgo, fmtDuration, truncate } from "./util.js"; +import { fmtDuration } from "./util.js"; import { JobqGraph } from "@hive/shared/jobq-graph.js"; // ─── derived state ─────────────────────────────────────────────────────────── -let metaInputsState = []; -let metaUpdateRunning = false; + // Kept in sync from JobqGraph's `onUpdate` callback (see mountRebuildQueue // below) — the component owns fetching GET /api/jobq/graph and renders // the tree itself; this flat array exists only for the two things it @@ -24,133 +22,6 @@ let metaUpdateRunning = false; let jobqNodes = []; let jobqGraphToken = 0; -function syncFromSnapshot(s) { - metaInputsState = (s.meta_inputs || []).slice(); - metaUpdateRunning = !!s.meta_update_running; -} - -// ─── meta inputs ───────────────────────────────────────────────────────────── -function renderMetaInputs(s) { - const root = $("meta-inputs-section"); - if (!root) return; - // Snapshot ticked checkboxes before wiping so a concurrent - // MetaInputsChanged doesn't silently clear a pending selection. - const checkedInputs = new Set( - Array.from( - root.querySelectorAll('input[type="checkbox"][data-meta-input]:checked'), - ).map((cb) => cb.dataset.metaInput), - ); - root.replaceChildren(); - const inputs = s.meta_inputs || []; - if (!inputs.length) { - root.append(el("p", { class: "empty" }, "meta repo not seeded yet")); - return; - } - if (metaUpdateRunning) { - root.append( - el( - "p", - { class: "meta-update-running" }, - "⏳ meta-update running — flake lock bump + affected agents rebuilding. " + - "watch the agent cards for per-rebuild progress.", - ), - ); - } - const f = el("form", { - method: "POST", - action: "/api/meta-update", - class: "meta-inputs-form", - "data-async": "", - "data-no-refresh": "", - "data-confirm": - "update selected meta flake inputs + rebuild affected agents?", - }); - const bulk = el("div", { class: "meta-inputs-bulk" }); - const selAll = el( - "button", - { type: "button", class: "meta-bulk-btn" }, - "select all", - ); - const selNone = el( - "button", - { type: "button", class: "meta-bulk-btn" }, - "select none", - ); - bulk.append("bulk: ", selAll, " ", selNone); - f.append(bulk); - const ul = el("ul", { class: "meta-inputs" }); - for (const inp of inputs) { - const depth = (inp.name.match(/\//g) || []).length; - const leaf = inp.name.slice(inp.name.lastIndexOf("/") + 1); - const li = el("li"); - if (depth > 0) li.style.marginLeft = depth * 1.3 + "em"; - const id = "meta-input-" + inp.name.replace(/[^a-z0-9-]/gi, "_"); - const cb = el("input", { - type: "checkbox", - name: "meta_input_" + inp.name, - id, - value: inp.name, - "data-meta-input": inp.name, - }); - if (checkedInputs.has(inp.name)) cb.checked = true; - const label = el("label", { for: id, title: inp.name }); - label.append(cb); - if (depth > 0) label.append(el("span", { class: "meta-input-twig" }, "└ ")); - label.append( - el("span", { class: "meta-input-name" }, leaf), - " ", - el("code", { class: "meta-input-rev" }, inp.rev.slice(0, 12)), - " ", - el("span", { class: "meta-input-ts" }, fmtAgo(inp.last_modified)), - ); - if (inp.url) { - label.append( - " ", - el( - "span", - { class: "meta-input-url", title: inp.url }, - "· " + truncate(inp.url, 48), - ), - ); - } - li.append(label); - ul.append(li); - } - f.append(ul); - const hidden = el("input", { type: "hidden", name: "inputs", value: "" }); - f.append(hidden); - const btn = el( - "button", - { - type: "submit", - class: "btn btn-meta-update", - disabled: "", - }, - metaUpdateRunning ? "⏳ UPD4T1NG…" : "◆ UPD4TE & R3BU1LD", - ); - f.append(btn); - function refreshDisabled() { - const any = f.querySelectorAll("input[data-meta-input]:checked").length > 0; - if (any && !metaUpdateRunning) btn.removeAttribute("disabled"); - else btn.setAttribute("disabled", ""); - } - f.addEventListener("change", refreshDisabled); - function setAllChecked(val) { - for (const b of f.querySelectorAll("input[data-meta-input]")) - b.checked = val; - refreshDisabled(); - } - selAll.addEventListener("click", () => setAllChecked(true)); - selNone.addEventListener("click", () => setAllChecked(false)); - f.addEventListener("submit", () => { - const selected = Array.from( - f.querySelectorAll("input[data-meta-input]:checked"), - ).map((b) => b.dataset.metaInput); - hidden.value = selected.join(","); - }); - root.append(f); -} - // ─── rebuild queue ──────────────────────────────────────────────────────────── // R3BU1LD QU3U3 is `JobqGraph` (Preact, rendered via plain `render(h(...))` // — no JSX pragma needed for either call, no mount wrapper: `render` is @@ -594,20 +465,13 @@ async function fetchBuild() { /** Mount the rebuild queue and wire the build-log refresh button. Called * once, after the tab strip exists. The queue self-fetches on mount - * (JobqGraph's own effect), unlike the meta inputs, which wait for the - * snapshot. */ + * (JobqGraph's own effect). */ export function initBuilds() { const refresh = $("build-refresh"); if (refresh) refresh.addEventListener("click", fetchBuild); mountRebuildQueue(); } -/** The meta inputs, from an `/api/state` snapshot. */ -export function syncBuildsFromSnapshot(s) { - syncFromSnapshot(s); - renderMetaInputs({ meta_inputs: metaInputsState }); -} - /** A tab was shown: BUILD L0GS loads its list the first time. */ export function onBuildsTabShown(tab) { if (tab === "buildlogs" && !buildLogsLoaded) { @@ -629,13 +493,3 @@ export function onRebuildQueueChanged() { buildRefreshTimer = setTimeout(fetchBuild, 2000); } } - -export function onMetaInputsChanged(ev) { - metaInputsState = (ev.inputs || []).slice(); - renderMetaInputs({ meta_inputs: metaInputsState }); -} - -export function onMetaUpdateRunning(ev) { - metaUpdateRunning = !!ev.running; - renderMetaInputs({ meta_inputs: metaInputsState }); -} diff --git a/frontend/packages/dashboard/src/dashboard.css b/frontend/packages/dashboard/src/dashboard.css index d9487002..5304605d 100644 --- a/frontend/packages/dashboard/src/dashboard.css +++ b/frontend/packages/dashboard/src/dashboard.css @@ -2,7 +2,7 @@ it defines the CSS custom properties, typography, and shared component rules that this file depends on. */ -/* SYST3M panel rules (rebuild queue / meta inputs / kept state) +/* SYST3M panel rules (rebuild queue / kept state) live in system-sections.css. */ @import "./system-sections.css"; @import "@hive/shared/jobq-rollup.css"; @@ -674,7 +674,7 @@ body.dashboard-shell.has-selection { shared `.hive-stats-table` moved to common.css. */ /* ─── build tabs (builds.js) ──────────────────────────────────────── - R3BU1LD QU3U3 and M3T4 1NPUTS are styled by system-sections.css and + R3BU1LD QU3U3 is styled by system-sections.css and jobq-graph.css; the BUILD L0GS list rules are below. */ .logs-toolbar { diff --git a/frontend/packages/dashboard/src/index.html b/frontend/packages/dashboard/src/index.html index a50f5234..199c92bf 100644 --- a/frontend/packages/dashboard/src/index.html +++ b/frontend/packages/dashboard/src/index.html @@ -33,9 +33,9 @@ ◆ 4G3NTS ◆ - + ◆ R3BU1LD QU3U3 ◆ - - ◆ M3T4 1NPUTS ◆ - + - @@ -140,9 +130,9 @@ aria-labelledby="tab-rebuild" >

- pending + running rebuilds, meta-updates, and first-spawns. one runs - at a time; meta-update cascades nest under their parent. dedup: - re-enqueueing a still-queued op collapses into the existing entry. + pending + running rebuilds and first-spawns. one runs at a time. + dedup: re-enqueueing a still-queued op collapses into the existing + entry.

loading…

@@ -154,26 +144,6 @@ - - -