mara via triage on #619 (post-merge follow-up):
> "I thought all nixpkgs follow the one hyperhive was deployed
> with?! if not, thats what we should fix."
This is the fix. Flip the rendered meta flake from:
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
hyperhive.url = "...";
hyperhive.inputs.nixpkgs.follows = "nixpkgs";
hyperhive.inputs.nixpkgs-unstable.follows = "nixpkgs-unstable";
to:
hyperhive.url = "...";
nixpkgs.follows = "hyperhive/nixpkgs";
nixpkgs-unstable.follows = "hyperhive/nixpkgs-unstable";
Net effect: hyperhive's `flake.nix` is the single channel-pin
authority. Operators who want to slide the whole swarm onto a
different channel do it at the host level via
`inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"`, which makes
hyperhive's nixpkgs = the host's nixpkgs and cascades through every
agent transparently.
This is the same shape I shipped in #619 v1 (then reverted per mara
on comment 7354, then re-affirmed via triage). Going with the
re-affirmed direction.
Closes#317 invariant still satisfied — `nixpkgs` is a single
canonical name in the meta tree, just resolving through hyperhive
instead of being its own root input.
Tests:
- rename `render_flake_declares_canonical_nixpkgs` →
`render_flake_aliases_nixpkgs_to_hyperhive`, asserts the new
follows-form AND the absence of any literal `nixpkgs.url`
- drop `render_flake_collapses_hyperhive_nixpkgs_via_follows` (no
separate meta-level nixpkgs to collapse anymore)
mara on #619 comment 7354:
> agent flake needs the url actually so the configuring agent
> can eval against it
Reverts the meta.rs `nixpkgs.follows = "hyperhive/nixpkgs"` shape from
this PR's earlier commit. Restores the pre-PR rendered shape with the
concrete `nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"` baked
into meta + the `hyperhive.inputs.nixpkgs.follows = "nixpkgs"`
redirect, so a configuring agent (or manager pre-apply check) can
evaluate the rendered meta flake without having to resolve through
hyperhive first.
Net behaviour for this PR is now:
- `flake.nix` line 5 bumped 25.11 → 26.05 (kept)
- `meta.rs` literal bumped to match (channel-pin in two places stays
acknowledged as duplication — drift fixable later if needed)
- `flake_check.rs` test fixtures bumped cosmetically (kept)
- meta.rs tests restored to assert the concrete-URL shape
3 meta tests pass via `nix develop -c cargo test`.
Drops the hardcoded `nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"`
from the meta flake renderer. meta now declares:
nixpkgs.follows = "hyperhive/nixpkgs";
nixpkgs-unstable.follows = "hyperhive/nixpkgs-unstable";
so every agent-level `inputs.<X>.inputs.nixpkgs.follows = "nixpkgs"`
resolves transitively to hyperhive's own pin. One channel decision
in the whole tree (hyperhive/flake.nix line 5), no second source
to drift.
`closes #317` invariant still holds: `nixpkgs` is a single canonical
name in the meta tree, it just resolves through hyperhive instead
of being its own root input.
Also:
- bump hyperhive/flake.nix line 5: `nixos-25.11` → `nixos-26.05`,
flake.lock relocked
- flake_check.rs fixtures bumped cosmetically (synthetic data, not
shape-affecting)
- rename + rewrite `render_flake_declares_canonical_nixpkgs` →
`render_flake_aliases_nixpkgs_to_hyperhive`; asserts the new
follows-form AND the absence of any literal `nixpkgs.url`
- drop the now-redundant `render_flake_collapses_hyperhive_nixpkgs_via_follows`
test (the old indirection it covered no longer exists)
Closes#526.
The naersk → crane swap in the parent commit flips clippy from
silently passing to actually failing on `-D warnings` (naersk's
`mode = "clippy"` mangled the `--` separator so the deny never took
effect). This commit clears the surfaced lints so the workspace
builds clean under the new enforcement — every fix is mechanical and
preserves behaviour. Tests still pass (160 across the workspace).
Auto-fixes via `cargo clippy --fix`:
- `doc_markdown` (19 sites): bare identifiers in doc comments
wrapped in backticks
- `format_in_format_args`, `explicit_into_iter_loop`,
`redundant_closure_for_method_calls`, `useless_conversion`, and
a few more — mechanical rewrites of the kind cargo can apply
safely.
Hand-fixed:
- `match_same_arms` (forge_notify::is_atx_heading): two arms returning
`true` collapsed into a single `matches!` pattern.
- `cast_sign_loss` + `format_push_string` (mcp.rs status formatter):
guarded `i64 → u64` through `u64::try_from(…).unwrap_or(0)` (status
timestamps are always positive in practice; clamp the skew edge to
0) and swapped `out.push_str(&format!(…))` for `write!` into the
buffer with an infallible-writer `let _ =`.
- `doc_lazy_continuation` in turn.rs + manager_server.rs + sh4re/lib.rs:
doc paragraphs that the markdown parser was treating as list-item
continuations got either a separating blank line or a `/`-for-`+`
word swap so the parser stops seeing a list.
- `unused_async` (manager_server::handle_request_schedule_prompt):
function has no `.await`; dropped the `async` and its `.await` call
site.
- `needless_pass_by_value` (scheduled_prompts::submit): take
`&NewSchedule` instead of moving the struct in; updated two prod
callers and eight test sites to pass references.
- `type_complexity` (approvals::mark_cancelled): hoisted the
7-tuple SELECT row shape into a `type CancelLookupRow = (…);` alias.
Allow-with-reason for intentional patterns:
- `option_option` (6 sites across dashboard / scheduled_prompts /
manager_server): `Option<Option<T>>` carries three-state PATCH
semantics (missing key = leave alone, `Some(None)` = clear,
`Some(Some(v))` = set). Collapsing to `Option<T>` loses the
"clear" state.
- `dead_code` (rebuild_queue::QueueKind::Destroy /
QueueSource::CrashRecover; topology::parent_of / default_seed):
wire-shape variants + API surfaces kept for the upcoming features
(#361 follow-ups, future `Destroy` queue routing, crash-recovery
path). Allowed at the variant / function level with the rationale
in `reason = "…"`.
- `too_many_lines` on three specific call-sites: a 117-line
exhaustive-variant test (dashboard_events::kind_tag_matches_…),
the meta-flake string template renderer
(meta::render_flake_with_lookup), and the notification poll loop
(forge_notify::poll_once) — splitting any of them would just hide
the contiguous shape they exist to keep visible.
`nix flake check` formatting target is still broken on main itself
(pre-existing nixfmt drift across ~28 files unrelated to this PR);
left alone here so the scope stays "crane port + lints the port
exposed" and the operator's review doesn't have to triage drive-by
nixfmt churn.
startup sweep adds ensure_repo('meta', core_token) after the orgs
so the first push isn't a 404. meta::git_commit now calls
forge::push_meta after every successful commit — token-in-URL
`git push http://core:$token@localhost:3000/core/meta.git` —
gated on the core token file existing (no-op when forge isn't
seeded). push failures log warn, don't bubble up.
no tea needed on the host; git is already on the hive-c0re service
PATH via /run/current-system/sw.
new section 'M3T4 1NPUTS' between approvals and message flow:
one row per input in meta/flake.lock (hyperhive first, then
agent-<n> alphabetically). each row shows the input name, the
first 12 chars of the locked sha, a relative timestamp from
locked.lastModified, and the original.url when available.
checkbox per row; submit button is disabled until at least one
box is checked; submitting confirms then POSTs the selected
names to /meta-update.
backend:
- meta::lock_update(inputs: &[String]) — runs 'nix flake update
<names>' in the meta dir, commits the lock change with a
combined message ('lock update: hyperhive, agent-coder').
preserves the existing META_LOCK serialization. existing
lock_update_for_rebuild / lock_update_hyperhive stay for
their single-input callers.
- POST /meta-update — comma-separated 'inputs' form field
(JS joins checkboxes since axum::Form doesn't natively
decode repeated keys); spawns a background task that runs
the lock update + per-agent rebuild loop. hyperhive
selection fans out to all agents; agent-<n> selection only
rebuilds <n>. each rebuild fires Rebuilt to the manager
exactly like dashboard / admin-CLI / auto-update.
rebuild loop is sequential — auto_update::run too (was
parallel via tokio::spawn). parallel rebuilds collide on
nix-store's sqlite cache ('sqlite db busy, not using cache')
and the meta META_LOCK contention. nix-daemon serializes the
heavy build steps anyway, so this isn't a throughput loss.
lifecycle::GIT_{NAME,EMAIL}, meta::GIT_{NAME,EMAIL}, and the
inline strings migrate.rs uses for its bootstrap commits all
move from 'hive-c0re' / 'hive-c0re@hyperhive' to 'c0re' /
'c0re@hyperhive'. shows up shorter in git log everywhere
(applied + meta repos).
journal showed three concurrent rebuilds racing on the meta
repo's .git/index.lock — auto_update::run kicks off parallel
tokio::spawn for every stale agent, each rebuild eventually
calls into meta::sync_agents / lock_update_for_rebuild which
do git add + commit, git isn't safe across concurrent processes
on the same .git/, and one of the failing-mid-write children
left index.lock behind. subsequent ops blocked until somebody
rm'd it manually.
fix: static META_LOCK (tokio::sync::Mutex<()>) acquired at the
top of every public meta function. concurrent rebuilds take
turns on meta ops; the actual nix build (nixos-container update)
releases the lock first and runs without it, so parallel agent
builds still parallelize on nix-daemon's own concurrency model.
migrate::run additionally clears /var/lib/hyperhive/meta/.git/
index.lock on startup if it exists — we just booted, nothing
of ours is holding it. covers the 'previous crash left a stale
lock' case the user just hit so the daemon recovers without
manual intervention.
new NixOS module option services.hive-c0re.operatorPronouns
(free text, default 'she/her', example 'they/them'). hive-c0re
takes it as a CLI flag (--operator-pronouns, lib.escapeShellArg'd
in the systemd unit), stores it on Coordinator, threads it into
the meta flake's mkAgent so each agent's systemd service gets
HIVE_OPERATOR_PRONOUNS set. the harness reads the env at boot
and substitutes {operator_pronouns} into the agent / manager
system prompt alongside {label}. nix string is escaped against
backslash + double-quote so non-ascii / quoted values
round-trip safely. prompt addendum: both agent.md and
manager.md mention the operator's pronouns up front so claude
uses them naturally in third-person reference. propagates on
next ↻ R3BU1LD (meta lock bump, no per-agent approval).
git+file://'s dirty-tree fetcher reads tracked + staged content
from the index (not the working tree, not untracked files). so
staging is enough to make a new flake.nix or flake.lock visible
to nix without committing first. sync_agents now stages flake
.nix, runs lock, stages the resulting flake.lock, then commits
both together in a single 'regenerate meta flake' (or 'seed
meta from N agents') commit — no more two-commit churn.
prepare_deploy applies the same trick to the two-phase deploy:
runs nix flake update, stages flake.lock so nixos-container
update sees it, doesn't commit yet. finalize_deploy commits
with the deployed/<id> message on build success; abort_deploy
git-restores the staged lock back to HEAD on failure. meta
history continues to record only successful deploys (and now
one commit per success instead of one + amend).
runtime error on first deploy attempt: 'source tree referenced
by git+file:///var/lib/hyperhive/meta does not contain
/flake.nix'. cause: sync_agents wrote flake.nix then ran
'nix flake lock' against a directory nix had just discovered
as a git repo (auto-upgraded to git+file://), which only sees
TRACKED content. fresh flake.nix was untracked, so nix saw an
empty source tree.
fix: commit flake.nix before locking. sync_agents now does
write → init (if first) → git add + commit → nix flake lock
→ commit lock if changed. two commits per change — one
'regenerate meta flake' and one 'lock update' — instead of
one combined; cleaner history.
same git+file:// gotcha bit the two-phase deploy: prepare_
deploy used to write the lock without committing, expecting
nixos-container update to read the working tree. it doesn't —
it reads the tracked commit. prepare_deploy now commits with
a placeholder 'deploy <n> (building)' message; finalize_deploy
amends to 'deploy <n> deployed/<id> <sha12>' on success;
abort_deploy git-reset --hard HEAD~1's it on failure. meta
history still records only successful deploys.
current nix CLI removed 'nix flake lock --update-input X' in
favour of 'nix flake update X'. switch all three call sites
(prepare_deploy, lock_update_for_rebuild,
lock_update_hyperhive). 'nix flake lock' with no args still
works for the seed path in sync_agents — it resolves missing
inputs without bumping existing ones.
rebuild now does sync_agents (idempotent — no-op when the
rendered flake matches disk; recovers from a divergent meta
repo on the side) followed by lock_update_for_rebuild which
relocks just this agent's input and commits the lock change
if any. flake ref for nixos-container update flips from
applied/<n>#default to meta#<name>. new helper
meta::lock_update_for_rebuild is single-phase (no separate
finalize): rebuild has no failure-revert semantics — it always
wants the latest applied/<n>/main. spawn already syncs meta
before container create; rebuild now picks up the meta side
on every manual ↻ R3BU1LD.
leaf module with no runtime callers yet (every public item is
#[allow(dead_code)] until lifecycle / actions / auto_update
rewire to use it). API surface:
- sync_agents — idempotent: render flake.nix for the given
agent set, git-init on first call, nix flake lock, commit if
anything changed.
- prepare_deploy / finalize_deploy / abort_deploy — two-phase
for the request_apply_commit path. prepare runs nix flake
lock --update-input agent-<n> without committing; finalize
commits with a 'deploy <n> deployed/<id> <sha12>' message;
abort git-restores the lock so a failed build leaves no
orphan commit.
- lock_update_hyperhive — one-shot for the auto-update path.
flake.nix template defines mkAgent that pulls each agent's
nixosModules.default from its input and wraps with the
identity / HIVE_PORT / HIVE_LABEL / HIVE_DASHBOARD_PORT
module — what setup_applied used to generate inline. nix
invocations carry --extra-experimental-features as a belt
in case flakes aren't enabled in nix.conf.