diff --git a/nix/module-eval/agent-forge-bao.nix b/nix/module-eval/agent-forge-bao.nix index 5b72a293..8f4ccb80 100644 --- a/nix/module-eval/agent-forge-bao.nix +++ b/nix/module-eval/agent-forge-bao.nix @@ -202,6 +202,10 @@ let && !(lib.hasInfix "rm -f \"$sidecar\"" s) && !(lib.hasInfix "forge-*" s); } + { + name = "the account fetch re-runs every two minutes"; + ok = agentForgeBao.systemd.timers.hive-agent-forge-accounts.timerConfig.OnUnitInactiveSec == "2min"; + } ]; in runGroup "agent-forge-bao" cases diff --git a/nix/module-eval/agent-github-bao.nix b/nix/module-eval/agent-github-bao.nix index 8786ebae..0f7af1e3 100644 --- a/nix/module-eval/agent-github-bao.nix +++ b/nix/module-eval/agent-github-bao.nix @@ -102,6 +102,10 @@ let name = "the fetch runs before the github poller starts"; ok = builtins.elem "hive-github-notify.service" (fetchUnit agentGithubBao).before; } + { + name = "the fetch re-runs every two minutes"; + ok = agentGithubBao.systemd.timers.hive-agent-github-token.timerConfig.OnUnitInactiveSec == "2min"; + } ]; in runGroup "agent-github-bao" cases diff --git a/nix/module-eval/agent-user.nix b/nix/module-eval/agent-user.nix index 4f3de42d..6c5f5af3 100644 --- a/nix/module-eval/agent-user.nix +++ b/nix/module-eval/agent-user.nix @@ -26,6 +26,25 @@ let (agentWith { services.hyperhive.agent.user.name = "iris"; }) .system.activationScripts.hive-agent-user-migrate.text; cases = [ + { + # hive-c0re creates the host work dir as hive-core; this activation is + # the only thing that lets the agent write to it. + name = "the agent's activation hands its own work dir to the agent user"; + ok = + lib.hasInfix ''if [ "$(stat -c %U "/agents/$userName/work")" != "$userName" ]; then'' migrate + && lib.hasInfix ''heal_own_dir "/agents/$userName/work"'' migrate; + } + { + # The helper is the only recursive chown of these dirs; it must hand the + # tree to the agent user and log a failure rather than discard it. + name = "the agent's activation hands state, harness and .claude to the agent user, logging failures"; + ok = + lib.hasInfix ''chown -hR "$userName:$userName" "$1" 2>&1'' migrate + && lib.hasInfix "could not chown $1 to $userName" migrate + && lib.hasInfix ''heal_own_dir "/agents/$userName/state"'' migrate + && lib.hasInfix ''heal_own_dir "/agents/$userName/harness"'' migrate + && lib.hasInfix ''heal_own_dir "$homeDir/.claude"'' migrate; + } { # hive-priv creates the host socket dir `0751 root` and never chowns it, # so this activation is the only thing that lets the harness bind there. diff --git a/nix/module-eval/nats-authelia.nix b/nix/module-eval/nats-authelia.nix index b98af789..fe6dfe10 100644 --- a/nix/module-eval/nats-authelia.nix +++ b/nix/module-eval/nats-authelia.nix @@ -303,6 +303,17 @@ let refusedWithoutIdentity natsWithHiveReaderOnly && natsWithHiveReaderOnly.services.hyperhive.deploy.nats.authBaoClientCertFile == null; } + { + # Reads the RENDERED settings, not the option: `calloutBlocks {…} // { + # … }` is a shallow merge, and a future edit that dropped or shadowed + # this key would still evaluate cleanly — the only reader that would + # notice is a publisher whose row exceeds upstream's much smaller + # default, and by then it is a dropped row, not an eval failure. + # Piggybacks on the queue-only fixture above, which already + # renders this container's full config. + name = "the queue's payload ceiling is set, not inherited from the server's default"; + ok = natsQueueOnly.containers.swarm-nats.config.services.nats.settings.max_payload == 8388608; + } { # What keeps a callout request inside one principal: the responder is # the callout account's only member, and no system account exists to diff --git a/nix/module-eval/snapshot-store.nix b/nix/module-eval/snapshot-store.nix index 9aab4513..a2dc9af3 100644 --- a/nix/module-eval/snapshot-store.nix +++ b/nix/module-eval/snapshot-store.nix @@ -47,6 +47,10 @@ let snapshotStore.enable = true; }; + unit = store.systemd.services."swarm-snapshot-store@"; + + moduleSource = builtins.readFile ../host-modules/swarm-snapshot-store.nix; + socket = store.systemd.sockets.swarm-snapshot-store.socketConfig; refusedAsWildcard = @@ -81,6 +85,18 @@ let name = "a wildcard mesh address, IPv4 or IPv6, is refused"; ok = refusedAsWildcard storeOnWildcard && refusedAsWildcard storeOnWildcardV6; } + { + # Without it a stalled `btrfs receive` holds a root instance forever. + name = "the receiver unit has a runtime ceiling"; + ok = unit.serviceConfig.RuntimeMaxSec or null == "6h"; + } + { + # The script is a store path, so its text is not evaluable without + # building it; the module source is the nearest thing to read. A + # silent peer must not hold the root instance past the header read. + name = "the receiver script bounds its header read"; + ok = lib.hasInfix "read -r -t \${toString headerTimeoutSeconds} keyword agent" moduleSource; + } ]; in runGroup "snapshot-store" cases