matrix: make the hive-internal main account an ordinary matrixAccounts entry
`matrixAccounts` is meant to be the agent's full account list, but the hive-internal `main` account was outside it: the nix module emitted only the extras and `hive-matrix-daemon` prepended a `main` it synthesized from the per-agent paths, with the option schema forbidding the name outright. nix/agent-modules/matrix.nix now declares `main` itself, as an ordinary entry under `matrix.enable`, from the state-dir paths the module already used for its token path-watcher (now a shared `stateDir` binding) plus `matrix.url`. The whole set, `main` included, is serialized to HIVE_MATRIX_ACCOUNTS. accounts::configured therefore synthesizes `main` only when the parsed list carries none, and otherwise takes the declared one verbatim — hoisting it to index 0, since the daemon reads index 0 as the primary and nix serializes an attrset, so `main` sorts wherever its key falls. Declared xor synthesized: an agent whose harness predates this entry keeps working, a current one gets its own, and there is no arrangement where `main` is duplicated or missing. The reserved-name assertion is replaced rather than dropped: the name must now be legal (the module uses it), but `main`'s tokenFile stays pinned to `<state>/matrix-token`, since hive-c0re provisions the hive-internal token there and nowhere else — a retarget would evaluate fine and then never restore. The other two fields are mkDefault and free to override. Refs #4475
This commit is contained in:
parent
c7f6c33321
commit
c74249f371
4 changed files with 220 additions and 80 deletions
|
|
@ -69,17 +69,22 @@ room you haven't read yet.
|
|||
|
||||
## Multiple accounts
|
||||
|
||||
`services.hyperhive.agent.matrixAccounts` (declared in `agent.nix`) gives an agent
|
||||
*additional* matrix identities beyond the hive-internal one — for example an
|
||||
`services.hyperhive.agent.matrixAccounts` (declared in `agent.nix`) is
|
||||
the agent's full set of matrix identities — for example an
|
||||
external-facing account alongside the internal one. Each entry is
|
||||
keyed by account name and specifies `tokenFile` (bearer token,
|
||||
provisioned out-of-band; basename must start with `matrix-token`),
|
||||
`sessionDir` (per-account matrix-sdk sqlite state — crypto keys +
|
||||
cache), and an optional `homeserver` (defaults to
|
||||
`services.hyperhive.agent.matrix.url`). The hive-internal account is always named
|
||||
`main`, synthesized from `services.hyperhive.agent.matrix.url` + agent state — this
|
||||
option only declares extras, and `main` is a reserved key here.
|
||||
Requires `services.hyperhive.agent.matrix.enable = true`.
|
||||
`services.hyperhive.agent.matrix.url`). The hive-internal account is
|
||||
always named `main` and is always the primary; the matrix module
|
||||
declares it for you as an ordinary entry of this map, from
|
||||
`services.hyperhive.agent.matrix.url` + agent state, so what you add
|
||||
here are the *further* accounts. Its `tokenFile` is pinned to
|
||||
`<state>/matrix-token` (hive-c0re provisions it there), and the
|
||||
dashboard's link-account route refuses to create an account by that
|
||||
name. Declaring extras requires
|
||||
`services.hyperhive.agent.matrix.enable = true`.
|
||||
|
||||
Every matrix tool above takes an optional `account` parameter (a name
|
||||
from this map) to act as that identity instead of the primary one.
|
||||
|
|
|
|||
|
|
@ -2,13 +2,18 @@
|
|||
//!
|
||||
//! A single `hive-matrix-daemon` can serve N matrix accounts (one
|
||||
//! matrix-sdk `Client` each, with its own session/store dir + sync
|
||||
//! loop). The **hive-internal account** (named `main`) is always
|
||||
//! present and is the primary: it is synthesized from the per-agent
|
||||
//! single-account paths (`<state>/matrix-token` +
|
||||
//! loop). Accounts come from the `HIVE_MATRIX_ACCOUNTS` env var (JSON,
|
||||
//! written by the nix harness module from
|
||||
//! `services.hyperhive.agent.matrixAccounts`), which declares the
|
||||
//! **hive-internal account** (named `main`) as an ordinary entry
|
||||
//! alongside any others.
|
||||
//!
|
||||
//! `main` is always present and is always the primary: it is moved to
|
||||
//! index 0 whichever position it was declared at, and if the env var
|
||||
//! declares no `main` at all (it is unset, or an agent's harness
|
||||
//! predates the nix module emitting it) one is synthesized from the
|
||||
//! per-agent single-account paths (`<state>/matrix-token` +
|
||||
//! `<state>/matrix-sdk-state`) and the daemon-wide `HIVE_MATRIX_URL`.
|
||||
//! Any **extra** accounts come from the `HIVE_MATRIX_ACCOUNTS` env var
|
||||
//! (JSON, written by the nix harness module from
|
||||
//! `services.hyperhive.agent.matrixAccounts`) and are appended after `main`.
|
||||
//!
|
||||
//! So a single-account agent (no `HIVE_MATRIX_ACCOUNTS`) gets exactly
|
||||
//! `main` — zero config, same behaviour as before: omitting `account` on
|
||||
|
|
@ -58,46 +63,29 @@ impl AccountCfg {
|
|||
}
|
||||
}
|
||||
|
||||
/// Build the account list: the always-present hive-internal `main`
|
||||
/// account (primary, synthesized from the per-agent single-account
|
||||
/// paths) followed by any extras declared in `HIVE_MATRIX_ACCOUNTS`.
|
||||
/// Name of the hive-internal account: the primary, and what a tool call
|
||||
/// resolves to when it omits `account`.
|
||||
const HIVE_ACCOUNT: &str = "main";
|
||||
|
||||
/// Build the account list: everything declared in `HIVE_MATRIX_ACCOUNTS`,
|
||||
/// with the hive-internal `main` account hoisted to index 0 (= primary)
|
||||
/// or synthesized there when the declaration doesn't carry one, plus any
|
||||
/// dashboard-provisioned accounts discovered on disk.
|
||||
///
|
||||
/// With no `HIVE_MATRIX_ACCOUNTS` set this returns just `main`, so a
|
||||
/// single-account agent is unchanged. When set, the env var carries
|
||||
/// only the *extra* accounts (the nix `matrixAccounts` option never
|
||||
/// redeclares the hive account); they are appended after `main`, which
|
||||
/// stays index 0 = primary.
|
||||
/// single-account agent is unchanged.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if `HIVE_MATRIX_ACCOUNTS` is set but is not valid
|
||||
/// JSON, or if an extra account's name collides with `main` or another
|
||||
/// extra.
|
||||
/// JSON, or if two declared accounts share a name.
|
||||
pub fn configured() -> anyhow::Result<Vec<AccountCfg>> {
|
||||
// The hive-internal account: always primary, named `main`, built
|
||||
// from the legacy single-account paths + the daemon-wide homeserver.
|
||||
let hive = AccountCfg {
|
||||
name: "main".to_owned(),
|
||||
token_file: paths::token_file(),
|
||||
state_dir: paths::matrix_state_dir(),
|
||||
homeserver: None,
|
||||
let declared: Vec<AccountCfg> = match std::env::var_os("HIVE_MATRIX_ACCOUNTS") {
|
||||
Some(raw) => serde_json::from_str(&raw.to_string_lossy())
|
||||
.map_err(|e| anyhow::anyhow!("parse HIVE_MATRIX_ACCOUNTS as JSON array: {e}"))?,
|
||||
None => Vec::new(),
|
||||
};
|
||||
let mut accounts = vec![hive];
|
||||
if let Some(raw) = std::env::var_os("HIVE_MATRIX_ACCOUNTS") {
|
||||
let extras: Vec<AccountCfg> = serde_json::from_str(&raw.to_string_lossy())
|
||||
.map_err(|e| anyhow::anyhow!("parse HIVE_MATRIX_ACCOUNTS as JSON array: {e}"))?;
|
||||
accounts.extend(extras);
|
||||
}
|
||||
// Reject duplicate names among the *configured* set (hive + extras).
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for a in &accounts {
|
||||
if !seen.insert(a.name.as_str()) {
|
||||
anyhow::bail!(
|
||||
"duplicate matrix account name {:?} (the hive-internal account is named \"main\")",
|
||||
a.name
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut accounts = ensure_hive_account(declared)?;
|
||||
// Append dashboard-provisioned accounts (a `matrix-token-<name>` file +
|
||||
// its `matrix-account-<name>.json` homeserver sidecar) that aren't
|
||||
// already declared in config, so an account logged in via the dashboard
|
||||
|
|
@ -112,6 +100,56 @@ pub fn configured() -> anyhow::Result<Vec<AccountCfg>> {
|
|||
Ok(accounts)
|
||||
}
|
||||
|
||||
/// Put the hive-internal `main` account at index 0 of `declared`, then
|
||||
/// reject duplicate names.
|
||||
///
|
||||
/// `main` is synthesized from the per-agent single-account paths **only
|
||||
/// if `declared` doesn't already carry one** — the nix harness module
|
||||
/// emits it as an ordinary `matrixAccounts` entry, so on a current
|
||||
/// harness the declaration is authoritative and is used verbatim (just
|
||||
/// moved to the front, since the module serializes an attrset and `main`
|
||||
/// sorts wherever its key falls). A harness that predates that emits
|
||||
/// nothing for it, and the synthesized account keeps that agent working
|
||||
/// unchanged. Never both: `main` is declared xor synthesized, so there
|
||||
/// is no arrangement where it is duplicated or missing.
|
||||
///
|
||||
/// Index 0 is load-bearing: the daemon's startup loop (`main.rs`) takes
|
||||
/// index 0 as the primary — the account a tool call acts as when it
|
||||
/// omits `account`, and the only one whose failure to restore is fatal.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if two declared accounts share a name.
|
||||
fn ensure_hive_account(mut declared: Vec<AccountCfg>) -> anyhow::Result<Vec<AccountCfg>> {
|
||||
match declared.iter().position(|a| a.name == HIVE_ACCOUNT) {
|
||||
// Declared: keep it verbatim, just make it the primary. `remove` +
|
||||
// `insert` rather than `swap`, so the other accounts keep their
|
||||
// declared order.
|
||||
Some(idx) => {
|
||||
let hive = declared.remove(idx);
|
||||
declared.insert(0, hive);
|
||||
}
|
||||
// Not declared: synthesize it from the legacy single-account paths
|
||||
// + the daemon-wide homeserver.
|
||||
None => declared.insert(
|
||||
0,
|
||||
AccountCfg {
|
||||
name: HIVE_ACCOUNT.to_owned(),
|
||||
token_file: paths::token_file(),
|
||||
state_dir: paths::matrix_state_dir(),
|
||||
homeserver: None,
|
||||
},
|
||||
),
|
||||
}
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for a in &declared {
|
||||
if !seen.insert(a.name.as_str()) {
|
||||
anyhow::bail!("duplicate matrix account name {:?}", a.name);
|
||||
}
|
||||
}
|
||||
Ok(declared)
|
||||
}
|
||||
|
||||
/// Scan the agent state dir for extra matrix accounts provisioned via the
|
||||
/// dashboard login form: each is a `matrix-token-<name>` file plus a
|
||||
/// `matrix-account-<name>.json` sidecar carrying the homeserver. Returns one
|
||||
|
|
@ -417,11 +455,71 @@ fn write_accounts_snapshot_inner(
|
|||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::{
|
||||
AccountStatus, discover_token_accounts_in, heartbeat_accounts_snapshot, pick_name,
|
||||
write_accounts_snapshot,
|
||||
AccountCfg, AccountStatus, discover_token_accounts_in, ensure_hive_account,
|
||||
heartbeat_accounts_snapshot, pick_name, write_accounts_snapshot,
|
||||
};
|
||||
|
||||
/// A declared account, as the nix module would serialize it.
|
||||
fn cfg(name: &str) -> AccountCfg {
|
||||
AccountCfg {
|
||||
name: name.to_owned(),
|
||||
token_file: PathBuf::from(format!("/agents/a/state/matrix-token-{name}")),
|
||||
state_dir: PathBuf::from(format!("/agents/a/state/matrix-sdk-state-{name}")),
|
||||
homeserver: Some(format!("https://{name}.example")),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn declared_main_is_used_verbatim_and_not_duplicated() {
|
||||
// A current harness declares `main` itself; it must be taken as-is
|
||||
// (its own paths, not the synthesized ones) and must not gain a
|
||||
// second, synthesized copy.
|
||||
let out = ensure_hive_account(vec![cfg("main")]).unwrap();
|
||||
assert_eq!(out.len(), 1);
|
||||
assert_eq!(out[0].name, "main");
|
||||
assert_eq!(
|
||||
out[0].token_file,
|
||||
PathBuf::from("/agents/a/state/matrix-token-main")
|
||||
);
|
||||
assert_eq!(out[0].homeserver.as_deref(), Some("https://main.example"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_main_is_synthesized_as_primary() {
|
||||
// A harness predating the nix module's `main` entry declares extras
|
||||
// only (or nothing at all): `main` is synthesized at index 0 from
|
||||
// the per-agent paths, so such an agent keeps working.
|
||||
let out = ensure_hive_account(Vec::new()).unwrap();
|
||||
assert_eq!(out.len(), 1);
|
||||
assert_eq!(out[0].name, "main");
|
||||
|
||||
let out = ensure_hive_account(vec![cfg("public")]).unwrap();
|
||||
let names: Vec<&str> = out.iter().map(|a| a.name.as_str()).collect();
|
||||
assert_eq!(names, vec!["main", "public"]);
|
||||
// Synthesized, so it carries no per-account homeserver and falls
|
||||
// back to the daemon-wide `HIVE_MATRIX_URL`.
|
||||
assert!(out[0].homeserver.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn declared_main_alongside_extras_is_not_a_collision() {
|
||||
// The nix module serializes an attrset, so `main` arrives wherever
|
||||
// its key sorts. It is hoisted to index 0 (= primary), the extras
|
||||
// keep their declared order, and nothing trips the duplicate check.
|
||||
let out = ensure_hive_account(vec![cfg("ccc"), cfg("main"), cfg("public")]).unwrap();
|
||||
let names: Vec<&str> = out.iter().map(|a| a.name.as_str()).collect();
|
||||
assert_eq!(names, vec!["main", "ccc", "public"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_accounts_of_the_same_name_are_rejected() {
|
||||
let err = ensure_hive_account(vec![cfg("public"), cfg("public")]).unwrap_err();
|
||||
assert!(err.to_string().contains("duplicate matrix account name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discovery_skips_configured_names_and_orphan_tokens() {
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@
|
|||
}:
|
||||
let
|
||||
userName = config.services.hyperhive.agent.user.name;
|
||||
# This agent's own state dir, where hive-c0re provisions the
|
||||
# hive-internal account's token (`matrix-token`) and the daemon keeps
|
||||
# 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";
|
||||
# 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).
|
||||
|
|
@ -92,9 +97,11 @@ in
|
|||
description = ''
|
||||
Path to this account's bearer-token file. The daemon reads
|
||||
the token from here to restore the matrix session; how the
|
||||
file gets populated is the provisioner's concern (an
|
||||
operator-supplied secret for an external account). The
|
||||
daemon skips an extra account whose token file is absent.
|
||||
file gets populated is the provisioner's concern
|
||||
(hive-c0re for the hive-internal `main` account, an
|
||||
operator-supplied secret for an external one). The daemon
|
||||
skips an extra account whose token file is absent, and
|
||||
exits cleanly to wait on the path watcher when `main`'s is.
|
||||
'';
|
||||
};
|
||||
sessionDir = lib.mkOption {
|
||||
|
|
@ -131,24 +138,29 @@ in
|
|||
}
|
||||
'';
|
||||
description = ''
|
||||
Declare *additional* matrix accounts served by the single
|
||||
`hive-matrix-daemon` (one matrix-sdk Client + sync loop each),
|
||||
beyond the agent's built-in hive-internal account. The
|
||||
attribute name keys each account (unique by construction) and is
|
||||
the handle the matrix MCP tools target via their `account`
|
||||
argument.
|
||||
Every matrix account served by the single `hive-matrix-daemon`
|
||||
(one matrix-sdk Client + sync loop each). The attribute name keys
|
||||
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`, synthesized by the daemon from
|
||||
it is named `main`, and this module declares it for you (when
|
||||
`services.hyperhive.agent.matrix.enable` is set) from
|
||||
`services.hyperhive.agent.matrix.url` + `<state>/matrix-token` +
|
||||
`<state>/matrix-sdk-state`, and is the account a tool call acts as
|
||||
when it omits `account`. You never declare it here --- this option
|
||||
is only for the extras (e.g. an external public-matrix account).
|
||||
`<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
|
||||
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
|
||||
`<state>/matrix-token` (an assertion; that is the one path
|
||||
hive-c0re provisions the hive-internal token to), and the
|
||||
dashboard's link-account route refuses to create an account named
|
||||
`main` --- the entry belongs to the module, not to a provisioner.
|
||||
|
||||
Leave empty (the default) for the common single-account case: the
|
||||
agent then has only `main`. When non-empty, the extras are
|
||||
serialized to the daemon's `HIVE_MATRIX_ACCOUNTS` environment
|
||||
variable and the daemon appends them after `main`. Requires
|
||||
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).
|
||||
'';
|
||||
};
|
||||
|
|
@ -185,13 +197,22 @@ in
|
|||
+ "(the extras extend the hive-internal `main` account, which only "
|
||||
+ "exists when matrix is enabled).";
|
||||
}
|
||||
# `main` is reserved for the synthesized hive-internal account; a
|
||||
# declared extra by that name would silently collide with it.
|
||||
# `main` is no longer 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).
|
||||
{
|
||||
assertion = !builtins.hasAttr "main" config.services.hyperhive.agent.matrixAccounts;
|
||||
assertion =
|
||||
!config.services.hyperhive.agent.matrix.enable
|
||||
|| config.services.hyperhive.agent.matrixAccounts.main.tokenFile == "${stateDir}/matrix-token";
|
||||
message =
|
||||
"services.hyperhive.agent.matrixAccounts cannot contain a key named \"main\" "
|
||||
+ "--- that name is reserved for the hive-internal account.";
|
||||
"services.hyperhive.agent.matrixAccounts.main.tokenFile must stay "
|
||||
+ "\"${stateDir}/matrix-token\" --- that is where hive-c0re provisions the "
|
||||
+ "hive-internal account's token. Declare a separate account instead of "
|
||||
+ "pointing `main` elsewhere.";
|
||||
}
|
||||
# Token files must land at the `matrix-token*` name the daemon
|
||||
# path-watcher globs (`matrix-token*` inside this agent's own state
|
||||
|
|
@ -218,6 +239,21 @@ in
|
|||
}
|
||||
];
|
||||
|
||||
# The hive-internal account as an ordinary `matrixAccounts` entry,
|
||||
# rather than something the daemon conjures behind the option's
|
||||
# back: `matrixAccounts` is the list of *all* this agent's accounts,
|
||||
# so the one it always has belongs in it. `mkDefault` per field so an
|
||||
# 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;
|
||||
};
|
||||
};
|
||||
|
||||
# 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
|
||||
|
|
@ -260,13 +296,14 @@ in
|
|||
// lib.optionalAttrs (config.services.hyperhive.agent.matrix.url != null) {
|
||||
HIVE_MATRIX_URL = config.services.hyperhive.agent.matrix.url;
|
||||
}
|
||||
# Multi-account: serialize the *extra* accounts to the JSON the
|
||||
# daemon parses (`accounts::configured`). Only set when extras are
|
||||
# declared; the daemon always synthesizes the primary `main`
|
||||
# (hive-internal) account itself from the per-agent paths and
|
||||
# prepends it, so we emit extras only. Each entry is in the
|
||||
# daemon's `AccountCfg` serde shape: name (the attr key) /
|
||||
# token_file / state_dir / optional homeserver.
|
||||
# Serialize the whole account set --- `main` included --- to the
|
||||
# JSON the daemon parses (`accounts::configured`). Each entry is in
|
||||
# the daemon's `AccountCfg` serde shape: name (the attr key) /
|
||||
# token_file / state_dir / optional homeserver. The daemon hoists
|
||||
# `main` to primary wherever the attr key sorted, and falls back to
|
||||
# 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 != { }) {
|
||||
HIVE_MATRIX_ACCOUNTS = builtins.toJSON (
|
||||
lib.mapAttrsToList (
|
||||
|
|
@ -325,7 +362,7 @@ in
|
|||
# sibling's token still matches, and it fires again until the start
|
||||
# limit stops it. Scoped to this agent, the condition is false exactly
|
||||
# when the daemon would have nothing to do.
|
||||
pathConfig.PathExistsGlob = "/agents/${userName}/state/matrix-token*";
|
||||
pathConfig.PathExistsGlob = "${stateDir}/matrix-token*";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,8 +163,8 @@ pub async fn put_matrix_account(
|
|||
let secret_path = matrix::account_path(&agent, &account)
|
||||
.map_err(|e| error_problem(StatusCode::BAD_REQUEST, &e.to_string()))?;
|
||||
// `main` is the hive-internal account `nix/agent-modules/matrix.nix`
|
||||
// synthesizes per agent from `services.hyperhive.agent.matrix.url` — the schema there
|
||||
// forbids declaring a key by that name for the same reason this route
|
||||
// declares per agent from `services.hyperhive.agent.matrix.url` — the
|
||||
// module owns that `matrixAccounts` entry, which is why this route
|
||||
// refuses to write one: an extra account literally named `main` would
|
||||
// not overwrite the real one (it lands at a different token-file suffix)
|
||||
// but would confuse anything that lists accounts by name. Same guard
|
||||
|
|
@ -173,7 +173,7 @@ pub async fn put_matrix_account(
|
|||
if is_reserved_account(&account) {
|
||||
return Err(error_problem(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"'main' is the hive-internal account, synthesized per agent from \
|
||||
"'main' is the hive-internal account, declared per agent from \
|
||||
services.hyperhive.agent.matrix.url — it cannot be set through this route.",
|
||||
));
|
||||
}
|
||||
|
|
@ -236,7 +236,7 @@ pub async fn put_matrix_account(
|
|||
Ok(Json(PutMatrixAccountResponse { user_id }))
|
||||
}
|
||||
|
||||
/// Whether `account` is the hive-internal name every hive synthesizes per
|
||||
/// Whether `account` is the hive-internal name every hive declares per
|
||||
/// agent (`nix/agent-modules/matrix.nix`) — see the call site's own comment
|
||||
/// for why this route must never write one.
|
||||
fn is_reserved_account(account: &str) -> bool {
|
||||
|
|
|
|||
Loading…
Reference in a new issue