Watch
0
0
Fork
You've already forked hyperhive
0
Commit graph

5,490 commits

Author SHA1 Message Date
atlas
29d956140e hive-c0re: a deploy request for a rev applied/main already contains is a no-op
advance_applied_main refused every rev that does not descend from
applied/main, including an ancestor of it. Ancestors arrive in normal
operation: swarm-controller queues each merge's TriggerDeploy as an
independent node with no resource or edge between them and spawns each
runnable node on its own task, so two merges of one agent can publish in
either order; the meta-update cascade fast-forwards applied/main to the
forge's main on its own; and the webhook handler does not dedup a
redelivered merge event. Since a refusal now logs at ERROR and fires the
`hh-config-rev-refused` alert, each of those would page as a refusal.

An ancestor is now RevAdvance::Superseded, logged at info, nothing queued.
Only a rev that has diverged (neither descendant nor ancestor) is refused.

Refs #4851
2026-10-09 22:25:39 +02:00
atlas
8c222f8910 hive-c0re: a refused rev is an error log + Grafana alert, not a PR comment
A deploy request whose rev hive-c0re refuses (fetch failed, or the rev
does not descend from applied/main) no longer tries to comment on the PR
that merged it. Off the forge host that attempt always failed with "forge
core token absent". The refusal now logs at ERROR, message unchanged, and
the new `hh-config-rev-refused` Grafana rule fires on it.

Deleted with the posting: actions::post_rev_deploy_failure_to_pr, the
whole forge/pr_comment.rs (config_repo, merged_pr_for_commit,
post_pr_comment), and ReaderToken::value, whose only caller was
merged_pr_for_commit.

Refs #4851
2026-10-09 22:25:39 +02:00
atlas
3d81266483 hive-forge: comments --since --limit returns N, not at most 49
The issue-comments endpoint takes no page or limit, so `comments
--since` cut its window client-side from a whole-list response. The
leftover over-fetch-by-one request (`.page_size(limit + 1)`) was a
no-op and the `--limit` clamp to MAX_LIMIT (49) guarded a truncation
check that no longer needs one.

Drop both: `--since T --limit N` now returns N comments. `comments
--json` still emits `since_limit_clamped`, always `false`. `timeline`
is untouched; its endpoint pages and its clamp stays.
2026-10-09 21:46:14 +02:00
atlas
1d56a74aad hive-forge: fetch issue comments in one request; the endpoint has no paging
The forge's issue-comments endpoint takes no page or limit and always
returns the whole thread. #5055 wrapped view's fetch in all_pages, which
never saw a short page on a thread of 50+ comments and fetched the same
list 50 times: `issue view 3782` printed 3000 comments instead of 60.
`comments --limit K` sent K as a page size the forge ignored, so it
returned the whole thread.

view and pr status's last_comment now send one request. comments fetches
the thread once and cuts the --limit / --tail window client-side; the
X-Total-Count probe and the computed-page tail walk are gone, and the
count of comments outside the window is the length of the list.
--since is unchanged.

tests/comments_no_paging.rs runs the binary against a loopback mock
forge whose comments route ignores page and limit, as Forgejo's does.
2026-10-09 21:19:47 +02:00
atlas
6f8a6ab8e0 swarm-controller: bound forge calls in job executors
forgejo-api's client has no request timeout, so a forge that accepts a
connection and never answers held a forge-calling job node forever:
the node never reached Done or Failed, and kept its executor task.

Every executor arm that awaits the forge now runs its work under the
same deadline the periodic passes use. On elapsing, the node fails
through its existing Outcome::Failed path with
"<node>: no answer from the forge within 120s".

Nodes: create_repo, create_forge_user, add_repo_member,
grant_docs_read, init_agent_config_repo, mint_agent_forge_token,
mint_hive_forge_token.

The bound is per node, not per request: the nodes make 1 to about 15
sequential forge and store requests each (mint_hive_forge_token the
most), and the requests live inside forge::Client methods the periodic
passes share. One 2-minute bound covers both, so #5065's
within_pass_deadline / PASS_DEADLINE become within_forge_deadline /
FORGE_DEADLINE, with a log line that no longer says "retrying next
tick" (true of a pass, not of a node).

The InitAgentConfigRepo arm is lifted out of run_swarm_node, like its
siblings, to keep that function under clippy::too_many_lines.

Refs #5066
2026-10-09 21:00:59 +02:00
atlas
45f0582163 swarm-controller: bound each periodic forge pass with a deadline
forgejo-api 0.11.1 builds its reqwest client with no timeout and offers
no way to hand it a caller-built one (the `client` field is private,
`new`/`with_user_agent` are the only constructors, `Endpoint` is
sealed). A forge that accepts a connection and then stalls therefore
held each of the six periodic reconcile loops on one request forever,
with no log line.

Wrap each loop's per-tick work in `forge::within_pass_deadline`
(`PASS_DEADLINE`, 2 min, under the 5-minute interval): on elapsed the
pass is dropped, a warn is logged, and the loop waits for its next tick.
Every pass is an idempotent reconcile, so abandoning one mid-way leaves
nothing the next pass does not redo.

Loops: config-pr poll, runner registration token, hive forge tokens,
agent forge tokens (observe + legacy sweep), docs grants, swarm forge
objects.

Refs #5060
2026-10-09 21:00:59 +02:00
atlas
dd172d9cf8 hive-c0re, hivectl: make the htpasswd and wg key tests fail on the in-place write
The htpasswd test now seeds the temp path at 0600, so only the explicit
chmod on the temp yields 0644. The wg test blocks the temp path with a
directory and asserts the write fails with the existing key untouched,
which an in-place write cannot satisfy.
2026-10-09 17:22:25 +02:00
atlas
01b8387ab8 write secret and key files atomically with their final mode
Each of these wrote its file in place (or set the mode after the
write), so a write cut short by a kill or a full disk left a partial
file behind, and some secrets existed for a moment at umask mode:

- swarm-authelia/swarm-grafana/hive-forge/swarm-bao generators: a
  partial file passed the `-s`/`-e` guard and was kept forever. They now
  capture the value first and land it with `atomic_write_secret`.
- swarm-controller webhook secret: a truncated file failed the 64-hex
  check and the secret was regenerated, out of step with the forge
  hooks. It is now written 0600 to a sibling temp, fsynced, renamed.
- hive-c0re htpasswd: the temp is now fsynced and set 0644 before the
  rename, like the sibling agents.conf writer, instead of relying on
  c0re's umask.
- hivectl wg init: the private key is created 0400 in a temp and
  renamed, instead of written at umask mode and chmod'ed after.

Closes #5068
2026-10-09 17:22:25 +02:00
atlas
1726ad3bad hive-c0re: bound the forge readiness probe
wait_until_ready only checked READY_TIMEOUT between polls, so a half-open forge
hung the first get_version forever. The probe now goes through forge::bounded; a
timeout counts as not-ready and falls into the existing retry/deadline path.
2026-10-09 17:06:00 +02:00
atlas
2b0154be28 hive-c0re: bound forge API calls so a stalled forge can't wedge the swarm event loop
forgejo-api 0.11.1 takes no caller-built client, so a request to a forge that
accepts the connection and never answers waited forever. Every forge::api()
call site now goes through forge::bounded, a 10s timeout that errors naming the
operation. The PR-comment calls run inline in swarm_status::drain_swarm_events,
the one task serving both swarm subscriptions, so a stalled forge stopped both.

Refs #5066
2026-10-09 17:06:00 +02:00
atlas
7214b4c22f hive-c0re: keep is_running unbounded, bound only the periodic status reads
Bounding is_running turned a hung systemctl into "not running" for
callers that record or act on the answer: the broad-stop restore set
dropped the agent, start_many could rebuild-and-restart a running
agent, reconcile could seed Offline. is_running is back to the
unbounded call it was.

The periodic workers read through the bounded running_state instead:
crash_watch (unchanged), and the swarm_agent_status publisher via the
new container_view::read_agent_status_bounded, which skips the agent
for that sweep when the unit state is unreadable. The GetAgentMeta
socket handler keeps the unbounded read_agent_status_live.
2026-10-09 16:18:58 +02:00
atlas
3cb7be29cf hive-c0re: bound the read-only container listing and systemctl status calls
priv_client::list_containers now waits at most 30s (LIST_WAIT) for
hive-priv's `nixos-container list`, via the existing `within` helper. Its
periodic callers (crash_watch, agent_sockets, swarm_agent_status, wanted,
mcp_sockets, swarm_status via container_view) already treat an Err as a
skipped cycle, so a hung listing no longer stops their loops for good.

lifecycle::is_running / unit_state run `systemctl is-active` with
kill_on_drop and a 10s timeout (IS_ACTIVE_TIMEOUT); a timeout warns and
reads as not-running / UnitState::Other, the same as a systemctl that
could not be spawned. The new lifecycle::running_state returns None for
that case, and crash_watch keeps the agent's previous observation on None
so a hung systemctl is neither reported as a crash nor as a recovery.

`call` itself and every mutating op (start/stop/destroy/send-snapshot/
daemon-reload) stay unbounded.

Closes #5061
2026-10-09 15:46:55 +02:00
atlas
d1ee3d8cc5 hive-jobq: treat a panicking job runner as a failed node
The future `Scheduler::claim_next` hands back completed its node only
after `run`'s future returned. A panic while running (the workspace
unwinds) skipped that completion, so the node stayed `Running` and its
resource units stayed held until the process restarted; later nodes
needing the same resource never started.

The run future is now polled under `catch_unwind`. A panic completes
the node as `Outcome::Failed("runner panicked: <msg>")` with nothing
grown, through the same `complete_growing` path as an ordinary failure:
`AFTER_OK` dependents are skipped, pending children cascade, and the
node's units are released.

The node's future is boxed: wrapping it inline pushed the futures
swarm-controller and hive-c0re spawn over clippy's `large_futures`
limit.

Closes #5058
2026-10-09 15:14:04 +02:00
atlas
7c16212fdd swarm-secret-client: bound vault requests with a 30s timeout
vaultrs sets no request timeout by default, so a store that accepts a connection and never answers hung every SecretStore call forever. connect_with_identity now sets VAULT_REQUEST_TIMEOUT; a stalled store surfaces as Error::Vault.
2026-10-09 15:11:38 +02:00
atlas
7129ee0015 swarm: keep the webhook secret on read errors; surface hidden KV/auth/forge failures
- swarm-controller webhook: regenerate the secret only when the file is
  absent. Any other read error is returned with the path; the caller
  already logs "webhook secret unavailable" and the endpoint answers 503.
  Overwriting meant every registered hook kept the old secret and every
  delivery failed HMAC, with nothing re-registering them.
- swarm-queue-client wanted: open_read_only/watch return Result; only
  STREAM_NOT_FOUND on the bucket maps to None. hive-c0re's wanted pull
  fails the DAG node with the cause instead of logging "nothing has been
  declared"; the watch warns with the error chain.
- swarm-nats-auth: warn when the OIDC secret re-read task dies.
- swarm-controller forge: listing a repo's config PRs failing is warn,
  not debug.

Closes #5056
2026-10-09 15:10:20 +02:00
damocles
d11c9537b6 hive-forge: drop subscription --ignore entirely — Forgejo only has watch/unwatch 2026-10-09 13:26:43 +02:00
damocles
7b4ef40688 hive-forge: inline test Args literals, drop the 4-bool helper (clippy::fn_params_excessive_bools) 2026-10-09 13:26:43 +02:00
damocles
f85db2b773 hive-forge: fix displaced run() doc comment, make the --ignore test pin behaviour not a string 2026-10-09 13:26:43 +02:00
damocles
31c7623a69 hive-forge: regenerate forge-cli.md, pin --ignore refusal with a test 2026-10-09 13:26:43 +02:00
damocles
902c1ae574 hive-forge: subscription --ignore refuses instead of silently watching (#5052) 2026-10-09 13:26:43 +02:00
atlas
4e6c3b4a2f hive-forge: pr create --draft: extract draft_warning so the warning decision is tested 2026-10-09 12:51:22 +02:00
atlas
4d164819b3 hive-forge: drop the subscription --ignore change (covered by #5053) 2026-10-09 12:51:22 +02:00
atlas
6e32b432ec hive-forge: subscription --ignore errors instead of subscribing; warn on --draft; always print agit PR URL 2026-10-09 12:51:22 +02:00
atlas
0e5c61d12c hive-forge: page past the forge's 50-row clamp; show list --json truncation
The forge clamps page_size to 50. branches, pr reviews, the review
bodies merged into `comments <pr>`, and view's comment list each sent
one request, so anything past the first page was dropped silently.
labels::repo_labels did loop, but asked for 100/page and stopped on
`len < 100`, so a clamped full page of 50 ended the walk.

Add verbs::all_pages: requests 50/page until a short page, capped at
50 pages with a stderr warning when the cap is hit. Use it in all of
the above; repo-labels now calls labels::repo_labels instead of its
own copy of the loop.

list --json now prints the same "N of M shown" stderr trailer as the
pretty output; its paging and stdout shape are unchanged.

Closes #5051
2026-10-09 12:35:51 +02:00
atlas
52f589780f agent: order bao/github/forge-accounts logins after the isolated-DNS rewrite 2026-10-09 12:34:30 +02:00
atlas
252b893bbb module-eval: agent-user test asserts the heal_own_dir form 2026-10-09 11:30:30 +02:00
atlas
6e1d72bfdb user.nix: log failed ownership heals of the agent's own dirs 2026-10-09 11:30:30 +02:00
atlas
6bb13f964d hive-c0re: validate forge env at startup; char-safe short revs
HIVE_FORGE_URL (set + parseable) and HIVE_ASSETS_DIR are checked once in
main before workers start. Short revs in container_view and pr_status
take 12 chars instead of byte-slicing.
2026-10-09 10:51:39 +02:00
atlas
9b711d5ffa hive-forge: mirror actions/upload-artifact alongside actions/checkout
With actionsFromSelf, Forgejo resolves bare `uses: actions/<name>` against
the forge itself, so every action a workflow names needs a mirror there.
Only actions/checkout was seeded, and coverage.yml's
`actions/upload-artifact@v3` has failed at job setup with "repository not
found" since the CI runner moved to swarm-ci-runner (e2da5509).

The single hardcoded checkout mirror becomes a list of action names both
share, mirrored from github.com/actions/* like checkout already was. v3
points at the same commit (ff15f03) on github.com and code.forgejo.org.

The controller's forge-objects pass creates any missing mirror on every
pass, and the changed SWARM_CONTROLLER_FORGE_MIRRORS env restarts the unit
on switch, so an existing forge picks the mirror up on deploy.

Closes #5040
2026-10-09 10:40:28 +02:00
atlas
b874d21cda hive-c0re/swarm-*: drop clippy allows and dedupe unix_now + KV open_or_create
No behaviour change. Casts replaced by try_from / Duration math; schedules.rs
reuses agent_renewal::unix_now; the five swarm-queue-client KV open-or-create
copies share one private kv::open_or_create.
2026-10-09 10:25:48 +02:00
atlas
f53ccb2740 hive-c0re: stop silently dropping errors on credential/marker writes
Add secret_file::write_secret_0600: create the parent dir, write a
sibling temp file opened create_new + mode 0600, sync_all, rename over
the target. Use it for the matrix sender token, the space/chat room-id
files and the forge core token, which previously wrote at the process
umask, then chmod-ed with the error discarded, and dropped the
create_dir_all error. agent_identity::write_identity uses it too
instead of its own open-0600 loop; that loop opened with create +
truncate, so a pre-existing file kept its old mode.

Also log instead of discarding:
- the manager's applied-rev marker write in auto_update
- the knowledge `git remote set-url` scrub (spawn error and non-zero
  exit; the exit status only, since stderr may echo the old URL)
- the parent create_dir_all in paths::move_if_legacy

Closes #5037
2026-10-09 10:24:15 +02:00
atlas
54226e027d swarm-bao: build the granter policy units from one helper
The six hand-written granter policy units (secret-publisher, matrix-ctl,
services-issuer, nats-tls, hive-leaf, operator-viewer) repeated the same
skeleton readerPolicyUnit already carried: ordering after the controller's
unit, pki requirement, path, granter environment, unseal retry window and
oneshot serviceConfig. They differed only in description and the script
after the granter login.

granterPolicyUnit now owns that skeleton; the six units and readerPolicyUnit
pass a description and a script body. Every rendered unit is unchanged
byte-for-byte.
2026-10-09 09:37:03 +02:00
flake-bot
4fa1cee620 nix flake update 2026-10-09 05:00:55 +02:00
damocles
8053462e2a tool_groups: add test for the case-insensitive is_known match 2026-10-09 00:50:27 +02:00
damocles
67fd164dfc tool_groups: match names case-insensitively, like capabilities::is_known 2026-10-09 00:50:27 +02:00
damocles
1d120289d1 hive-c0re: extract read_from so the prune_unknown test exercises the real wiring 2026-10-09 00:50:27 +02:00
damocles
e24259dde1 hive-c0re: add prune_unknown table tests + read() coverage, mirroring capabilities.rs 2026-10-09 00:50:27 +02:00
damocles
06951ddc30 hive-c0re: drop unrecognised tool-group names on read, matching capabilities' prune_unknown 2026-10-09 00:50:27 +02:00
atlas
597fdebaf3 swarm-bao-forge-publisher starts on the deploy that adds it
The publisher was wanted only by container@hive-forge.service. A switch
starts a new unit only through the targets that want it, and the container
is already running, so the deploy that introduced the publisher left it dead
until the next container restart or boot; the path unit cannot help, because
the mint's only-once guard never rewrites the file.

- The publisher is also wanted by multi-user.target, like swarm-secret-publish.
- A token file that is not there yet exits 0 instead of waiting 120s and
  failing: the path unit runs the publisher when the mint writes it, so a
  switch with the forge container down does not fail. TimeoutStartSec is 60.
- swarm-controller loses its After= on the publisher: a switch issues the two
  jobs separately, so it ordered nothing there, and the background re-read
  covers the gap.
- The controller's background re-read logs at info once the backoff is
  capped, one line per five minutes, so a forge host that never got its
  publisher leaf stays visible; earlier retries stay at debug.

Refs #4998
2026-10-08 13:59:26 +02:00
atlas
2d2d7529fc swarm-bao-forge-publisher re-runs on container start and token writes
The publisher was a oneshot with RemainAfterExit=true, started only through
`wantedBy = container@hive-forge.service`. Once it had run it stayed
`active (exited)`, so a later container start made its start a no-op: a token
re-minted without a host reboot or a unit change never reached the store.

- RemainAfterExit is dropped, so each start runs the script again.
- systemd.paths.swarm-bao-forge-publisher watches the token file in the
  container (PathChanged) and starts the publisher when the mint writes it,
  the same shape as swarm-secret-publish's path unit.
- The code comment, secrets.md and credentials.md now name both triggers;
  forge-placement pins the missing RemainAfterExit and the path unit.

Refs #4998
2026-10-08 13:45:56 +02:00
atlas
c45f7a5330 swarm-controller gains forge access when the token arrives, without a restart
A controller that started before the forge's host had published its token
ran without forge access until someone restarted it: on a split-host first
deploy that is a manual step. Now the forge client lives in a slot that is
filled once (`Late<T>`, an `Arc<OnceLock<T>>`):

- `forge::enable` tries the store once at start. On failure it logs once and
  re-reads in the background with backoff (30s doubling to a 5-minute cap,
  no log per retry), fills the slot and logs once when the token is there.
- The forge passes (#5014's per-hive token mint among them), the config-PR
  poll and the webhook registration start from that moment, through
  `start_forge`'s callback.
- Job nodes and HTTP handlers read the slot when they run, so they answer
  "no forge" before and work after.

A token replaced in the store after the slot is filled still reaches the
controller with its next restart. The round-1 change that shared
`queue_identity::fetch_with_retry` is reverted; nothing else uses it.

Refs #4998
2026-10-08 13:40:24 +02:00
atlas
121c4678aa swarm-controller reads its forge token from the store, not a host file
The controller's forge token never entered bao: the forge minted it in its
container, hive-forge-swarm-controller-token copied it to a host file, and the
controller LoadCredential'ed that file. forgeTokenFile defaulted to that path
only where the forge ran, so a controller on any other host had no forge
access unless an operator copied the file by hand.

Now the forge's host publishes it and the controller reads it:

- swarm-bao-forge-publisher (replaces hive-forge-swarm-controller-token)
  waits for the in-container token, logs in with its own forge-publisher leaf
  and writes it to swarm/controller/swarm-controller/forge/token. It runs on
  every forge container start, the old unit's trigger.
- New principal swarm-forge-publisher: leaf minted by glue-bao-tls.nix,
  policy create/update on that one path, written by
  swarm-bao-forge-publisher-policy, CN reserved as a hive name.
- The controller policy gains read on that path. swarm-controller reads it
  at start under its own certificate with the OIDC secret's retry, and runs
  without forge access if it still cannot.
- forgeTokenFile, its LoadCredential and the forgeEnv gate are gone;
  SWARM_CONTROLLER_FORGE_URL is always set. hostSwarmControllerTokenFile is
  removed, and a tmpfiles rule deletes the old host copy on the forge host.

The in-container file stays: the mint unit's only-once guard reads it, and
it is the publisher's source.

Refs #4998
2026-10-08 13:25:47 +02:00
damocles
c433bdef92 hive-runtime: log the acp permission decision at the point it is made 2026-10-08 10:29:13 +02:00
flake-bot
90c7600c8a nix flake update 2026-10-08 08:38:11 +02:00
damocles
d071c8df00 hive-subagent-mcp: cover the non-goal interrupted-turn case for #4337 2026-10-08 08:19:10 +02:00
damocles
04a8e2b328 hive-subagent-mcp: write the fake-claude test script via write_executable 2026-10-08 02:01:04 +02:00
damocles
70644cb004 hive-c0re: widen nix stderr predicate to cover error:/evaluation warning:, add tests 2026-10-08 00:46:23 +02:00
damocles
25893d0f95 hive-c0re, hive-priv: demote three routine, non-actionable log sites 2026-10-08 00:46:23 +02:00
damocles
77034a7c9c hive-matrix-mcp: fix matrix.nix comment to match the current diff 2026-10-08 00:28:03 +02:00
damocles
7eb338a8c1 hive-matrix-mcp: drop the main.rs fallback edit, keep only the nix RUST_LOG change 2026-10-08 00:28:03 +02:00