matrix: drop the per-agent matrix.enable; accounts are the enable signal
`services.hyperhive.agent.matrix.enable` was a second source of truth for a fact the account set already carried: after ①-③ the hive-internal `main` account is an ordinary `matrixAccounts` entry, so "does this agent have matrix" and "does this agent have an account" were the same question asked twice, with the boolean able to disagree. The option is gone and a non-empty `matrixAccounts` now gates the daemon unit, its token path-watcher and the injected `extraMcpServers.matrix` entry. That is only a real condition because `matrixAccounts.main` is itself gated: it is declared when `matrix.url != null`, never unconditionally. A `main` with no homeserver is an account the daemon can never log in as, so declaring one always would have made the signal trivially true and turned matrix on for every agent in every hive. With the URL gate, the empty set is reachable exactly for an agent the hive gave no homeserver and whose operator declared no account of its own — the state the old `enable = false` expressed. Assertions: "extras require enable" is deleted, having become the definition of the thing it checked (an external-only account with its own homeserver is now rendered rather than rejected). `main.tokenFile` stays pinned, re-guarded on `? main` instead of on the flag, since `main` is absent whenever the URL is null and an unguarded index would throw there. Both spellings of the option get `mkRemovedOptionModule`, following ../host-modules/deploy.nix's registrationTokenFile pair rather than a silent delete: the definition whose meaning changes is `false`, and left undeclared it would be ignored and hand the agent the tools its operator turned off. Failing the eval with the replacement spelling is the only outcome that cannot. module-eval gains the three arms — URL, nothing, external-only — with the middle one carrying why it exists: it is the only thing in the suite that would notice `main` becoming unconditional again. Refs #4475
This commit is contained in:
parent
e95e988965
commit
99b141f5f2
7 changed files with 254 additions and 92 deletions
|
|
@ -18,6 +18,25 @@
|
|||
flakeInputs ? { },
|
||||
...
|
||||
}:
|
||||
let
|
||||
# What an `agent.nix` still setting the dropped per-agent matrix switch is
|
||||
# told. Shared by the two `mkRemovedOptionModule` entries below, one per
|
||||
# spelling of the path.
|
||||
matrixEnableRemoved = ''
|
||||
The per-agent matrix switch is gone. Matrix is enabled for an agent exactly
|
||||
when it has at least one services.hyperhive.agent.matrixAccounts entry, and
|
||||
the hive-internal `main` entry is declared for you whenever
|
||||
services.hyperhive.agent.matrix.url is non-null.
|
||||
|
||||
- `enable = true` was the default: drop the line, nothing else to do.
|
||||
- `enable = false`: set services.hyperhive.agent.matrix.url = null instead
|
||||
(and declare no accounts of your own). That is what leaves the account set
|
||||
empty, which is now what suppresses the daemon, its path watcher and the
|
||||
injected extraMcpServers.matrix entry.
|
||||
|
||||
See docs/tools/matrix.md.
|
||||
'';
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./agent-service.nix
|
||||
|
|
@ -47,6 +66,20 @@
|
|||
The built-in Bash tool is fully disabled; agents use
|
||||
mcp__bash__run instead. Remove the setting from your agent.nix.
|
||||
'')
|
||||
# The dropped per-agent matrix switch. A removal, not a silent delete,
|
||||
# because the definition that *changes meaning* is `false`: left undeclared
|
||||
# it would be ignored and the agent would quietly gain the matrix tools its
|
||||
# operator turned off. Failing the eval with the replacement spelling is the
|
||||
# only outcome that cannot do that.
|
||||
#
|
||||
# Both spellings, for the same reason ../host-modules/deploy.nix lists both
|
||||
# of `swarm|deploy.matrix.registrationTokenFile`: an existing agent.nix may
|
||||
# carry either the pre-rename `hyperhive.*` path or the current one, and only
|
||||
# the one it actually carries will fire. The rename shim that used to bridge
|
||||
# them is gone from ./renamed-options.nix — a rename pointing at an
|
||||
# undeclared option breaks evaluation for everyone, removed or not.
|
||||
(lib.mkRemovedOptionModule [ "hyperhive" "matrix" "enable" ] matrixEnableRemoved)
|
||||
(lib.mkRemovedOptionModule [ "services" "hyperhive" "agent" "matrix" "enable" ] matrixEnableRemoved)
|
||||
];
|
||||
|
||||
options.services.hyperhive.agent.icon = lib.mkOption {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
# hive-matrix-daemon (serves its MCP tools directly over
|
||||
# streamable-http), its token-arrival path trigger, and the
|
||||
# auto-injected extraMcpServers entry.
|
||||
#
|
||||
# There is no `matrix.enable`. An agent has matrix exactly when it has an
|
||||
# account to serve — see `matrixEnabled` below.
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
|
|
@ -16,6 +19,21 @@ let
|
|||
# its matrix-sdk store. Shared by the `main` account entry below and
|
||||
# the path-watcher glob at the bottom of this file.
|
||||
stateDir = "/agents/${userName}/state";
|
||||
accounts = config.services.hyperhive.agent.matrixAccounts;
|
||||
# **The enable signal.** Matrix is on for this agent exactly when it has at
|
||||
# least one account, because an account is the only thing the daemon has to
|
||||
# do: no account, no Client, no sync, no tool surface worth injecting.
|
||||
#
|
||||
# This is not trivially true even though the module declares `main` itself:
|
||||
# that definition is gated on `matrix.url != null` (see below), which is the
|
||||
# per-agent "does this agent have a homeserver to reach" fact. So an agent the
|
||||
# hive handed no homeserver URL, whose operator declared no external account
|
||||
# either, has an empty set here and gets none of the units — the case the
|
||||
# deleted `matrix.enable = false` used to express.
|
||||
#
|
||||
# No cycle: `matrixAccounts`'s own definition reads `matrix.url`, never this
|
||||
# binding, so the `mkIf`s below may read the merged option value freely.
|
||||
matrixEnabled = accounts != { };
|
||||
# Rasterize the operator-set agent icon (`services.hyperhive.agent.icon`, an SVG) to a
|
||||
# 512x512 PNG so the matrix daemon can upload it as each account's avatar
|
||||
# over the live authenticated Client (see hive-matrix-mcp::client::sync_avatar).
|
||||
|
|
@ -27,40 +45,6 @@ let
|
|||
'';
|
||||
in
|
||||
{
|
||||
options.services.hyperhive.agent.matrix.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Enable per-agent matrix integration via `hive-matrix-daemon`.
|
||||
When true (the default), the harness:
|
||||
|
||||
- runs `hive-matrix-daemon` as a systemd unit that holds a
|
||||
matrix-sdk Client + sync against the homeserver named by
|
||||
`HIVE_MATRIX_URL` (see `services.hyperhive.agent.matrix.url` — there is no
|
||||
default, since an agent's own netns makes a loopback guess
|
||||
wrong). The daemon auto-skips when that URL or
|
||||
`<state>/matrix-token` is missing, and a `systemd.paths`
|
||||
watcher restarts it the moment hive-c0re provisions the token
|
||||
(same path-trigger shape as `forge-avatar-sync`).
|
||||
- exposes the matrix tool surface (send_message, send_dm,
|
||||
send_reaction, send_reply, mark_read, list_rooms,
|
||||
list_room_members, read_room) to claude via an auto-injected
|
||||
`extraMcpServers.matrix` entry pointed at the daemon's own
|
||||
streamable-http listener (`services.hyperhive.agent.mcp.matrixHttpPort`) — no
|
||||
stdio bridge, no per-turn respawn, same shape as the built-in
|
||||
hyperhive surface and `hive-bash-daemon`.
|
||||
- wakes the agent on incoming room events via a short teaser
|
||||
Wake signal (`[matrix] <sender> in <room>: <first 100c>…`)
|
||||
to the hyperhive control socket; the full event stays
|
||||
unread server-side until `read_room` consumes it.
|
||||
|
||||
Set to `false` for agents that should NOT have matrix tools at
|
||||
all (e.g. agents on a host without `services.hyperhive.agent.matrix.enable` on
|
||||
the meta side). When token file is absent the daemon and MCP
|
||||
both no-op cleanly anyway, so `false` is rarely necessary.
|
||||
'';
|
||||
};
|
||||
|
||||
options.services.hyperhive.agent.matrix.url = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
|
|
@ -80,10 +64,18 @@ in
|
|||
different host from the agents, and inside an agent's network
|
||||
namespace `localhost` reaches the agent rather than the
|
||||
homeserver, so a default would be a value that builds fine and
|
||||
then talks to the wrong machine. When this is `null` the daemon
|
||||
is left without a homeserver and no-ops, exactly as it does when
|
||||
the token file is absent --- an absent integration, never a
|
||||
then talks to the wrong machine. An absent integration, never a
|
||||
misdirected one.
|
||||
|
||||
`null` is also this agent's **matrix off switch**, and the
|
||||
replacement for the `services.hyperhive.agent.matrix.enable`
|
||||
boolean that used to exist: the hive-internal `main` account in
|
||||
`services.hyperhive.agent.matrixAccounts` is declared from this
|
||||
URL, so `null` leaves that set empty and the whole integration —
|
||||
daemon unit, path watcher, injected MCP entry — is not generated
|
||||
at all. Declaring an external account with its own `homeserver`
|
||||
turns matrix back on without a hive homeserver, which is the
|
||||
honest reading of that config.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -143,12 +135,41 @@ in
|
|||
each account (unique by construction) and is the handle the matrix
|
||||
MCP tools target via their `account` argument.
|
||||
|
||||
The **hive-internal account is always present and is the primary**:
|
||||
it is named `main`, and this module declares it for you (when
|
||||
`services.hyperhive.agent.matrix.enable` is set) from
|
||||
**This set is also the enable signal for per-agent matrix** —
|
||||
there is no separate boolean. A non-empty set means the harness:
|
||||
|
||||
- runs `hive-matrix-daemon` as a systemd unit that holds a
|
||||
matrix-sdk Client + sync per account against that account's
|
||||
homeserver (`homeserver`, else
|
||||
`services.hyperhive.agent.matrix.url`). The daemon auto-skips an
|
||||
account whose URL or token file is missing, and a `systemd.paths`
|
||||
watcher restarts it the moment hive-c0re provisions the token
|
||||
(same path-trigger shape as `forge-avatar-sync`).
|
||||
- exposes the matrix tool surface (send_message, send_dm,
|
||||
send_reaction, send_reply, mark_read, list_rooms,
|
||||
list_room_members, read_room) to claude via an auto-injected
|
||||
`extraMcpServers.matrix` entry pointed at the daemon's own
|
||||
streamable-http listener (`services.hyperhive.agent.mcp.matrixHttpPort`) — no
|
||||
stdio bridge, no per-turn respawn, same shape as the built-in
|
||||
hyperhive surface and `hive-bash-daemon`.
|
||||
- wakes the agent on incoming room events via a short teaser
|
||||
Wake signal (`[matrix] <sender> in <room>: <first 100c>…`)
|
||||
to the hyperhive control socket; the full event stays
|
||||
unread server-side until `read_room` consumes it.
|
||||
|
||||
An **empty** set is an agent with no matrix at all: none of those
|
||||
three exist. That is the state an agent reaches by having no
|
||||
homeserver (`services.hyperhive.agent.matrix.url = null`) and no
|
||||
account of its own, and it replaces the removed
|
||||
`services.hyperhive.agent.matrix.enable = false`.
|
||||
|
||||
The **hive-internal account is the primary whenever it exists**: it
|
||||
is named `main`, and this module declares it for you from
|
||||
`services.hyperhive.agent.matrix.url` + `<state>/matrix-token` +
|
||||
`<state>/matrix-sdk-state`. It is the account a tool call acts as
|
||||
when it omits `account`. It is an ordinary entry of this option
|
||||
`<state>/matrix-sdk-state` — so it is present exactly when that URL
|
||||
is non-null, which on a real hive is always (hive-c0re renders it
|
||||
per agent). It is the account a tool call acts as when it omits
|
||||
`account`. It is an ordinary entry of this option
|
||||
like any other, so it shows up in the account list --- what you
|
||||
add here are the *further* accounts (e.g. an external
|
||||
public-matrix account). Its `tokenFile` stays pinned to
|
||||
|
|
@ -160,8 +181,7 @@ in
|
|||
Leave it alone (the default) for the common single-account case:
|
||||
the agent then has only `main`. The whole set is serialized to the
|
||||
daemon's `HIVE_MATRIX_ACCOUNTS` environment variable, `main`
|
||||
first. Declaring extras requires
|
||||
`services.hyperhive.agent.matrix.enable` (there is no `main` to extend otherwise).
|
||||
first.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -185,29 +205,26 @@ in
|
|||
|
||||
config = {
|
||||
assertions = [
|
||||
# Extra matrix accounts only make sense alongside the hive-internal
|
||||
# `main` account they extend, which exists only when matrix is
|
||||
# enabled.
|
||||
{
|
||||
assertion =
|
||||
config.services.hyperhive.agent.matrixAccounts == { }
|
||||
|| config.services.hyperhive.agent.matrix.enable;
|
||||
message =
|
||||
"services.hyperhive.agent.matrixAccounts requires services.hyperhive.agent.matrix.enable = true "
|
||||
+ "(the extras extend the hive-internal `main` account, which only "
|
||||
+ "exists when matrix is enabled).";
|
||||
}
|
||||
# `main` is no longer a forbidden key --- this module declares it
|
||||
# The "extras require `matrix.enable`" assertion that used to head this
|
||||
# list is gone with the option: a non-empty account set is now what
|
||||
# enables matrix, so the condition it checked has become the definition
|
||||
# of the thing it was checking. An operator declaring only an external
|
||||
# account, with no hive homeserver, is a config this module now renders
|
||||
# rather than rejects — matrix on, no `main`.
|
||||
#
|
||||
# `main` is not a forbidden key --- this module declares it
|
||||
# itself (see the `matrixAccounts.main` definition below), so the
|
||||
# name must be allowed. What stays rejected is retargeting *its
|
||||
# token file*: hive-c0re writes the hive-internal account's token
|
||||
# to `<state>/matrix-token` and nowhere else, so an override there
|
||||
# is an account that evaluates fine and then never restores. The
|
||||
# other two fields are free to override (a `mkDefault` each).
|
||||
#
|
||||
# Guarded on `? main` rather than on an enable flag: `main` is absent
|
||||
# whenever `matrix.url` is null, and an unguarded `.main.tokenFile`
|
||||
# would throw on exactly those agents instead of passing vacuously.
|
||||
{
|
||||
assertion =
|
||||
!config.services.hyperhive.agent.matrix.enable
|
||||
|| config.services.hyperhive.agent.matrixAccounts.main.tokenFile == "${stateDir}/matrix-token";
|
||||
assertion = !(accounts ? main) || accounts.main.tokenFile == "${stateDir}/matrix-token";
|
||||
message =
|
||||
"services.hyperhive.agent.matrixAccounts.main.tokenFile must stay "
|
||||
+ "\"${stateDir}/matrix-token\" --- that is where hive-c0re provisions the "
|
||||
|
|
@ -222,7 +239,7 @@ in
|
|||
# `matrix-catgirl-token`) is caught at build time, not silently.
|
||||
{
|
||||
assertion = lib.all (a: lib.hasPrefix "matrix-token" (baseNameOf a.tokenFile)) (
|
||||
lib.attrValues config.services.hyperhive.agent.matrixAccounts
|
||||
lib.attrValues accounts
|
||||
);
|
||||
message =
|
||||
"every services.hyperhive.agent.matrixAccounts.<name>.tokenFile basename must start with "
|
||||
|
|
@ -230,9 +247,7 @@ in
|
|||
+ "(matrix-token* in the agent's state dir) picks it up live. Offending: "
|
||||
+ lib.concatStringsSep ", " (
|
||||
lib.mapAttrsToList (n: a: "${n}=${baseNameOf a.tokenFile}") (
|
||||
lib.filterAttrs (
|
||||
_n: a: !lib.hasPrefix "matrix-token" (baseNameOf a.tokenFile)
|
||||
) config.services.hyperhive.agent.matrixAccounts
|
||||
lib.filterAttrs (_n: a: !lib.hasPrefix "matrix-token" (baseNameOf a.tokenFile)) accounts
|
||||
)
|
||||
)
|
||||
+ ".";
|
||||
|
|
@ -246,19 +261,29 @@ in
|
|||
# operator can retarget e.g. the homeserver without a
|
||||
# conflicting-definition error (the token file is pinned by an
|
||||
# assertion above, since hive-c0re owns that path).
|
||||
services.hyperhive.agent.matrixAccounts = lib.mkIf config.services.hyperhive.agent.matrix.enable {
|
||||
main = {
|
||||
tokenFile = lib.mkDefault "${stateDir}/matrix-token";
|
||||
sessionDir = lib.mkDefault "${stateDir}/matrix-sdk-state";
|
||||
homeserver = lib.mkDefault config.services.hyperhive.agent.matrix.url;
|
||||
};
|
||||
};
|
||||
#
|
||||
# ⚠️ Gated on the homeserver URL, and that gate is what keeps `matrixEnabled`
|
||||
# from being trivially true for every agent in the hive. A `main` with no
|
||||
# homeserver is an account the daemon can never log in as, so declaring one
|
||||
# unconditionally would enable matrix everywhere and inject a tool surface
|
||||
# backed by a permanently no-opping daemon. On a real hive hive-c0re renders
|
||||
# this URL per agent (meta.rs's `FORWARDED_VAR_OPTIONS`), so the common case
|
||||
# is still "every agent has `main`".
|
||||
services.hyperhive.agent.matrixAccounts =
|
||||
lib.mkIf (config.services.hyperhive.agent.matrix.url != null)
|
||||
{
|
||||
main = {
|
||||
tokenFile = lib.mkDefault "${stateDir}/matrix-token";
|
||||
sessionDir = lib.mkDefault "${stateDir}/matrix-sdk-state";
|
||||
homeserver = lib.mkDefault config.services.hyperhive.agent.matrix.url;
|
||||
};
|
||||
};
|
||||
|
||||
# Auto-inject the matrix MCP entry alongside the bash entry from
|
||||
# ./mcp.nix. `lib.mkDefault` so the operator's own agent.nix can
|
||||
# override it. Points at the daemon's own persistent
|
||||
# streamable-http listener — no stdio bridge, no per-turn spawn.
|
||||
services.hyperhive.agent.extraMcpServers = lib.mkIf config.services.hyperhive.agent.matrix.enable {
|
||||
services.hyperhive.agent.extraMcpServers = lib.mkIf matrixEnabled {
|
||||
matrix = lib.mkDefault {
|
||||
type = "http";
|
||||
url = "http://127.0.0.1:${toString config.services.hyperhive.agent.mcp.matrixHttpPort}/mcp";
|
||||
|
|
@ -271,7 +296,7 @@ in
|
|||
# signals on incoming room events via `/run/hive/mcp.sock`. See
|
||||
# `docs/agent-lifecycle/persistence.md::Matrix per-agent daemon + token-arrival
|
||||
# trigger` for the first-boot-ordering rationale.
|
||||
systemd.services.hive-matrix-daemon = lib.mkIf config.services.hyperhive.agent.matrix.enable {
|
||||
systemd.services.hive-matrix-daemon = lib.mkIf matrixEnabled {
|
||||
description = "long-running matrix-sdk Client + MCP daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "hive-agent.service" ];
|
||||
|
|
@ -304,7 +329,10 @@ in
|
|||
# synthesizing it from the per-agent paths only when this JSON
|
||||
# carries no `main` --- which is how an agent whose harness
|
||||
# predates this entry keeps working.
|
||||
// lib.optionalAttrs (config.services.hyperhive.agent.matrixAccounts != { }) {
|
||||
#
|
||||
# Unconditional, not `optionalAttrs (accounts != {})`: a non-empty set is
|
||||
# what generated this unit at all, so the guard could only ever be true.
|
||||
// {
|
||||
HIVE_MATRIX_ACCOUNTS = builtins.toJSON (
|
||||
lib.mapAttrsToList (
|
||||
name: a:
|
||||
|
|
@ -314,7 +342,7 @@ in
|
|||
state_dir = a.sessionDir;
|
||||
}
|
||||
// lib.optionalAttrs (a.homeserver != null) { inherit (a) homeserver; }
|
||||
) config.services.hyperhive.agent.matrixAccounts
|
||||
) accounts
|
||||
);
|
||||
}
|
||||
# Rasterized agent icon path for the daemon's avatar sync. Only set
|
||||
|
|
@ -347,7 +375,7 @@ in
|
|||
# the daemon would exit 0 silently on first boot and the MCP
|
||||
# would have no backend until next restart. See
|
||||
# `docs/agent-lifecycle/persistence.md` (same section as above).
|
||||
systemd.paths.hive-matrix-daemon = lib.mkIf config.services.hyperhive.agent.matrix.enable {
|
||||
systemd.paths.hive-matrix-daemon = lib.mkIf matrixEnabled {
|
||||
description = "trigger hive-matrix-daemon when a matrix token appears";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
# `matrix-token*` (not just `matrix-token`) so a secondary
|
||||
|
|
|
|||
|
|
@ -273,7 +273,8 @@ in
|
|||
# Auto-inject the built-in bash MCP server — always present, every
|
||||
# agent needs bash tools. `lib.mkDefault` so the operator's own
|
||||
# agent.nix can override the entry. (The matrix sibling lives in
|
||||
# ./matrix.nix, gated on services.hyperhive.agent.matrix.enable.) `hive-bash-daemon`
|
||||
# ./matrix.nix, gated on that agent having a
|
||||
# services.hyperhive.agent.matrixAccounts entry at all.) `hive-bash-daemon`
|
||||
# serves its MCP tools directly over streamable-http (no stdio bridge,
|
||||
# no round-trip socket) — see the `hive-bash-daemon` service below.
|
||||
services.hyperhive.agent.extraMcpServers.bash = lib.mkDefault {
|
||||
|
|
@ -284,8 +285,9 @@ in
|
|||
|
||||
# Auto-inject the subagent MCP server — default-on for every agent for
|
||||
# now (operator's call: "default on for now, should be a capability
|
||||
# later" — not gated behind an enable option yet, unlike `matrix.nix`'s
|
||||
# pattern). `lib.mkDefault` so an agent.nix can still override/disable
|
||||
# later" — unconditional, where `matrix.nix`'s sibling entry appears only
|
||||
# for an agent that has a matrix account to serve).
|
||||
# `lib.mkDefault` so an agent.nix can still override/disable
|
||||
# the entry in the meantime.
|
||||
services.hyperhive.agent.extraMcpServers.subagent = lib.mkDefault {
|
||||
type = "http";
|
||||
|
|
|
|||
|
|
@ -119,10 +119,10 @@
|
|||
[ "hyperhive" "logs" "queryUrl" ]
|
||||
[ "services" "hyperhive" "agent" "logs" "queryUrl" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "hyperhive" "matrix" "enable" ]
|
||||
[ "services" "hyperhive" "agent" "matrix" "enable" ]
|
||||
)
|
||||
# `matrix.enable` is deliberately absent from this list: the option it
|
||||
# renamed to no longer exists, and a rename pointing at an undeclared
|
||||
# option breaks evaluation for everyone. Both of its spellings are
|
||||
# `mkRemovedOptionModule` in ./default.nix instead.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "hyperhive" "matrix" "url" ]
|
||||
[ "services" "hyperhive" "agent" "matrix" "url" ]
|
||||
|
|
|
|||
|
|
@ -611,6 +611,29 @@ let
|
|||
# RAM percentage and so hands the module no byte count to size against.
|
||||
agentCapped = agent { claudeMemoryMaxBytes = 8589934592; };
|
||||
agentUncapped = agent { };
|
||||
|
||||
# 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";
|
||||
};
|
||||
};
|
||||
agentHarness = machine: machine.systemd.services.hive-agent;
|
||||
agentSubagentDaemon = machine: machine.systemd.services.hive-subagent-daemon;
|
||||
agentSettings = machine: machine.services.opentelemetry-collector.settings;
|
||||
|
|
@ -1793,6 +1816,62 @@ let
|
|||
name = "one subagent losing the OOM draw does not stop the daemon";
|
||||
ok = (agentSubagentDaemon agentUncapped).serviceConfig.OOMPolicy == "continue";
|
||||
}
|
||||
{
|
||||
# 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 doctrine three glue files state, as a property a rewrite has to
|
||||
# keep: a client is defined by holding a certificate the store accepts,
|
||||
|
|
|
|||
Loading…
Reference in a new issue