# `checks.module-eval-agent-matrix` — see ./lib.nix for the shared # rationale (why this suite exists, naming convention, "evaluates # not executes"). { pkgs, lib, self, nixosSystem, }: let inherit (import ./lib.nix { inherit pkgs lib self nixosSystem ; }) agent agentWith runGroup ; # Matrix's enable signal, which is the account set itself — there is no # `matrix.enable` option left to read. Three arms, because the property has # three distinct shapes and only one of them is the common case: # # - a homeserver URL, which is what the module turns into a `main` account; # - neither URL nor operator account, the state that replaced # `matrix.enable = false`. ⚠️ **This is the arm that matters.** `main` is # declared by the module itself, so "any account declared" would be # trivially true — and matrix would render for every agent in every hive — # the moment that declaration stops being gated on the URL. Nothing else in # this suite would notice; # - an operator account carrying its own homeserver and no hive one, which is # matrix on with no `main` at all. agentMatrix = agent { matrix.url = "https://chat.t.local"; }; agentNoMatrix = agent { }; agentMatrixExternalOnly = agent { matrixAccounts.ccc = { tokenFile = "/agents/a1/state/matrix-token-ccc"; sessionDir = "/agents/a1/state/matrix-sdk-state-ccc"; homeserver = "https://matrix.example.invalid"; }; }; # The daemon that reads its tokens from the store, `main` included. Paired # with `agentMatrix` above — same daemon, no store — so each case below can # tell "carries the store's coordinates" from "every matrix daemon does". agentMatrixBao = agentWith { services.hyperhive.agent.bao.addr = "https://bao.t.local:8200"; services.hyperhive.agent.matrix.url = "https://chat.t.local"; }; daemon = machine: machine.systemd.services.hive-matrix-daemon; cases = [ { # A homeserver URL is the whole input: from it the module derives the # hive-internal `main` account, and from a non-empty account set the three # things that used to hang off `matrix.enable`. name = "an agent with a homeserver gets a main account and the matrix units"; ok = let a = agentMatrix.services.hyperhive.agent.matrixAccounts; in lib.attrNames a == [ "main" ] && a.main.tokenFile == "/agents/a1/state/matrix-token" && a.main.homeserver == "https://chat.t.local" && agentMatrix.systemd.services ? hive-matrix-daemon && agentMatrix.systemd.paths ? hive-matrix-daemon && agentMatrix.services.hyperhive.agent.extraMcpServers ? matrix; } { # The absence arm, and the reason the enable signal is not vacuous. An # agent the hive gave no homeserver, whose operator declared nothing, must # come out with an EMPTY account set — not a `main` that can never log in # — and therefore with none of the three. Assert the emptiness itself and # not just the units: it is the account set that is load-bearing now, and # a `main` sneaking back in is the regression this case exists to name. name = "an agent with no homeserver and no declared account gets no matrix at all"; ok = agentNoMatrix.services.hyperhive.agent.matrixAccounts == { } && !(agentNoMatrix.systemd.services ? hive-matrix-daemon) && !(agentNoMatrix.systemd.paths ? hive-matrix-daemon) && !(agentNoMatrix.services.hyperhive.agent.extraMcpServers ? matrix); } { # Matrix without a hive homeserver: one operator account, its own # homeserver, no `main`. Under the deleted `matrix.enable` this config was # an assertion failure ("extras require enable") even though every account # in it was complete; the account set being the signal is what makes it # expressible, and the serialized env var is where that has to show up. name = "an external-only account enables matrix with no main entry"; ok = let accts = agentMatrixExternalOnly.services.hyperhive.agent.matrixAccounts; env = agentMatrixExternalOnly.systemd.services.hive-matrix-daemon.environment; in lib.attrNames accts == [ "ccc" ] && !(accts ? main) && builtins.fromJSON env.HIVE_MATRIX_ACCOUNTS == [ { name = "ccc"; token_file = "/agents/a1/state/matrix-token-ccc"; state_dir = "/agents/a1/state/matrix-sdk-state-ccc"; homeserver = "https://matrix.example.invalid"; } ] # No hive homeserver, so nothing may claim one. && !(env ? HIVE_MATRIX_URL); } { # The daemon reads this agent's tokens from the store ITSELF, as itself, # from inside this container — `main` too, since the swarm mints it and # no hive does. So it needs the same identity ./agent-forge-bao.nix's # fetch carries, in its own credentials directory. name = "the matrix daemon carries this agent's own store identity"; ok = let u = daemon agentMatrixBao; in u.serviceConfig.LoadCredential == [ "hive-agent-bao-cert" "hive-agent-bao-key" "hive-agent-bao-server-ca" ] && u.environment.BAO_ADDR == "https://bao.t.local:8200" && u.environment.BAO_CLIENT_CERT == "%d/hive-agent-bao-cert" && u.environment.BAO_CLIENT_KEY == "%d/hive-agent-bao-key"; } { # The agent's name, and nothing derived from it: the daemon builds its # store path and its cert-auth role from this one string. name = "the matrix daemon is told which agent it is and not where its credentials live"; ok = let e = (daemon agentMatrixBao).environment; in e.HIVE_AGENT_NAME == agentMatrixBao.services.hyperhive.agent.user.name && !(lib.any (lib.hasInfix "swarm/agents") (lib.attrValues e)); } { # 🩸 A secret is a path: every `BAO_*` entry is the store's address or a # file under this unit's own credentials directory, never bytes in an # environment `/proc//environ` publishes. name = "the matrix daemon is handed store paths and never store values"; ok = let store = lib.filterAttrs (n: _: lib.hasPrefix "BAO_" n) (daemon agentMatrixBao).environment; in store != { } && lib.all (n: n == "BAO_ADDR" || lib.hasPrefix "%d/" store.${n}) (lib.attrNames store); } { # A token the swarm mints or replaces in the store changes no file, so # the path watcher never sees it. The timer is what re-starts a daemon # that exited on a missing or replaced token. name = "a store-backed matrix daemon is re-started while it is down"; ok = agentMatrixBao.systemd.timers.hive-matrix-daemon.timerConfig.OnUnitInactiveSec or null == "5min"; } { # The absence arm for the four above: with no store, no identity, no # timer, and the file is the whole mechanism. name = "a matrix daemon on an agent with no store declares no identity and no timer"; ok = let u = daemon agentMatrix; in !(u.serviceConfig ? LoadCredential) && !(u.environment ? BAO_ADDR) && !(u.environment ? HIVE_AGENT_NAME) && !(agentMatrix.systemd.timers ? hive-matrix-daemon); } ]; in runGroup "agent-matrix" cases