Commit graph

4,382 commits

Author SHA1 Message Date
iris
225b1668ae docs: fix genuine Microsoft.Hyphens hits (redundant -ly adverb hyphens) 2026-09-10 15:49:53 +02:00
atlas
c24dd03485 swarm-bao: put a wrapped bao on the host, with this store's coordinates
Reading a role out of the store took four round-trips of environment
guessing: the certificate has no IP SAN so loopback cannot verify, the
DNS name resolves to the bridge from inside the container, and the CA
the client needs lives on the host, which had no `bao` at all.

The wrapper carries the address, the server CA and — where the PKI glue
minted one — the reader's own leaf, so cert-auth login needs nothing
typed. `--set-default` throughout: an operator pointing BAO_ADDR
elsewhere still wins.

Only the wrapper reaches PATH. `wrapProgram` renames the real binary,
so there is no unwrapped `bao` to reach by accident, and the module-eval
case asserting the package's absence is what keeps a later "install it
too" from undoing that.
2026-09-10 14:56:26 +02:00
atlas
d2c501d1b2 docs/setup: say WHERE bao operator init runs, and how to reach the store from there
"On the host that RUNS the store" is ambiguous in the deployment we
actually ship: openbao runs inside the `swarm-bao` container, so the
host and the container are different vantages with different working
addresses, and the instruction reads as either.

From inside the container neither obvious address works — the server
certificate is issued for the store's DNS name and carries no IP SAN,
while that name resolves to the bridge rather than to the container. The
fix is an SNI override: verify the name, connect on loopback. The host's
own address is stated beside it so the reader can pick a vantage instead
of discovering the constraint from a TLS error.
2026-09-10 11:09:33 +02:00
atlas
c22db5eb57 swarm-bao-tls: drop the unreachable CN fallback
`clientCn` fell back to `cfg.domain` when `hiveName` was unset. That branch
cannot run: `hive-network.nix` asserts `hiveName != null` under
`mkIf services.hyperhive.enable`, and this file's `config` is gated on the same
predicate, so any host that evaluates the conditional has already failed the
assertion.

Worse than dead, it read as a second supported spelling of a hive's identity —
which is what a cert-auth role matches on. It was not even the hive's own
domain: `cfg` here is `services.hyperhive.swarm.bao`, so the fallback resolved
to the store's address, one string shared by every hive in the swarm and the
same CN the server leaf carries.

Reading the option directly matches what other modules needing the name already
do (`hive-c0re/environment.nix`).
2026-09-10 00:25:07 +02:00
atlas
4007fc965d swarm-controller: create each hive's cert-auth role at startup
A hive holds an mTLS pair and a policy naming what it may read, and still
cannot log in: nothing creates the role that maps its certificate to that
policy. The one pre-shared credential in the system therefore buys no
access.

Minting happens here rather than in nix, which was the first plan. Nix
mints from the store's own container, and that path is gated on the
bootstrap token -- so onboarding a hive later would mean placing the one
genuinely pre-shared secret again. Doing it from the controller costs a
public certificate authority as an input and makes the bootstrap token
one-time.

A startup pass, not a hook: the hive list is loaded once and a config
change means a redeploy, so the roles are as static as the list. Only the
policy is derived from something that moves.

The subject is the hive's name because glue-bao-tls.nix mints a hive's
client leaf with its name as the CN, and cert auth matches on that.

Per-hive failures are logged and skipped, matching the queue, bridge and
forge connects above it: a controller whose store is unreachable still
serves everything else, and the next start retries.

Not covered by a test: ensure_hive_roles is IO from end to end, and the
seam that would make it assertable is the one the read-grant sink already
has. Said here rather than implied by a green suite.
2026-09-10 00:25:07 +02:00
atlas
7f9e65e923 swarm-controller: hand the daemon the authority hives are issued from
Creating a hive's cert-auth role means writing the authority into the role
by value -- the store matches a presented certificate against the role's own
copy -- and nothing gave this daemon that file.

Named separately from deploy.bao.clientCaFile rather than read off it: that
option is the store's, saying which readers the store trusts on the host
that runs it, while a controller runs anywhere. The glue module supplies it
where the two are co-located, which is the same split baoClientCertFile
already makes against the hive reader's leaf.

Gated on the identity as well as the CA. Without a leaf there is nothing to
write a role with, so the file would reach a daemon that cannot act on it.

The module-eval arm needed a fixture of its own: a deployment that
self-signs both ends points clientCaFile and serverCaFile at one file, so on
the existing fixture the two authorities are the same string and wiring
either into the other's slot passes. controllerTwoCas is where they differ.
2026-09-10 00:25:07 +02:00
damocles
d26b754701 subagent: fix a real test race on the process-wide OTEL_RESOURCE_ATTRIBUTES env var 2026-09-09 23:47:21 +02:00
damocles
1ad7145cc5 swap hive-claude back to the published 0.1.1 release now that it's out 2026-09-09 23:45:12 +02:00
damocles
561bd09618 subagent: close the start/continue TOCTOU race with an atomic reservation 2026-09-09 23:45:12 +02:00
damocles
c2fb3c6e3e subagent: add status tool, cut docs down to operator-facing + no cli flags 2026-09-09 23:45:12 +02:00
damocles
e64639c4d0 docs: document the independent subagent daemon (start/continue/interrupt) 2026-09-09 23:45:12 +02:00
damocles
c280664d74 nix: wire the independent hive-subagent-daemon systemd unit and MCP server 2026-09-09 23:45:12 +02:00
damocles
7699db6500 hive-subagent-mcp: new crate for the subagent daemon, independent of hive-bash-mcp 2026-09-09 23:45:12 +02:00
iris
adfb0f9e02 shared terminal: align row icons via a shared grid, not offsets
Per mara's screenshot report (agent-terminal icons not aligned in the
first column) — and her explicit follow-up steer on the first version of
this fix: 'dont do it by offsets at all, it should be part of the layout
that they align.'

Root cause traced first, not guessed: .row-glyph relied on inheriting
text-indent: -1.4em from .live .row to paint its glyph in the reserved
prefix slot; details.row > summary separately reset text-indent: 0 for
its own hanging-caret needs, which also zeroed the inherited value for
the icon nested inside it (indent inherits by computed value, not by
rule). Two independently-computed offsets that had to agree by hand,
and silently didn't.

Replaced the whole offset scheme with a real structural fix: every row
is a 2-column CSS grid (icon column, width from one shared
--row-icon-col custom property, then content column), and a details
row grids its own <summary> with the exact same grid-template-columns
value instead of griding itself (so its <pre> body still stacks full
width below, not squeezed into column 2). Icon and content are placed
by explicit grid-column, not auto-placement inference, so an icon-less
row's lone child still lands in the content column. .row-content is a
new wrapper class (Row.tsx, terminal.js's row()/mutableRow()/
placeholder()) giving that content an element the grid can target by
class - a DocumentFragment (what linkify() returns) doesn't persist as
a node once appended, so without an explicit wrapper there was nothing
for the grid to place.

Caught and fixed a second real bug while building this for real (not
just reasoning about the CSS): the first draft's details.row { display:
block } had lower specificity than .live .row's display: grid and never
actually applied, squeezing <summary> into the row's own 1.4em icon
column and wrapping its text one character per line. Needed
.live details.row to out-specify it.

Verified with a headless-chromium render of all 6 row shapes (flat
icon / flat icon-less / flat markdown-body / details icon / details
icon-less / a long-wrapping flat row) - all align and wrap correctly.
Also verified via the earlier Range.getBoundingClientRect() measurement
(glyph paint position, not just the element's own box): diff 0.00 for
both this and the previous fix, but only this one is structural rather
than two numbers that happen to still agree today.
2026-09-09 23:35:20 +02:00
iris
915c6c6f92 swarm-ui: persist table filters, add reset button, multi-select, negate
Per mara's issue: tables should remember their filters (with a reset
button), the labels filter should be multi-select, and any filter should
support negation (search vs exclude).

All in the shared Table component (ui/table/Table.tsx), used by
AgentsPage/HivesPage/IssueReportPage:

- filters now persist via the same useLocalSetting hook IssueReportPage
  already used for its own state, keyed by a new required storageKey
  prop (required, not optional, so no caller can forget it and every
  table gets persistence for free)
- a small 'reset filters' button clears every column's filter at once,
  shown only when at least one is active
- new filterMode: "multiselect" (+ a filterValues extractor, alongside
  the existing single-value filterValue) renders a checkbox list and
  matches on any overlap - IssueReportPage's own bespoke label-checkbox
  sidebar is folded into this instead of staying a second, separate
  filter mechanism
- a negate toggle ('exclude') sits under every filter mode's control,
  applying uniformly to text/select/multiselect

Verified: tsc --noEmit and the esbuild bundle both clean.
2026-09-09 23:31:24 +02:00
damocles
948525de09 check-issue-refs: scan .yml/.yaml too, closes #4148 2026-09-09 23:25:54 +02:00
damocles
a5b07c55c8 docs/scheduler/ci.md: drop bare issue tag #4146 missed in ci.yml's own cleanup 2026-09-09 23:00:38 +02:00
iris
00b7f5063a ci: drop bare issue tags from ci.yml comments (mara, #4146)
hive-rules.md: no #NNN tags in code, prose only. The two mentions in
the new prose-lint-errors job's comments were exactly that -- reworded
to describe the split without a ticket number.
2026-09-09 22:55:28 +02:00
iris
c13499c839 ci: split an error-only vale check out of the full prose-lint job
mara, on #4128: add a CI step (separate from the existing one) that
only flags error-level vale hits, so it can be made a required check
without also gating merges on the pre-existing warning/suggestion
backlog. The full job stays as-is, advisory, for that backlog.

Also brought docs/scheduler/ci.md's CI-checks table back in sync with
reality (it was missing the doc-pointer lint job already; now lists
both vale jobs and their required-check status).
2026-09-09 22:55:28 +02:00
iris
3ca81d388c docs: reword away from 'backend' instead of whitelisting it (mara, #4146)
mara disagreed with exempting this section from #4041's ruling.
'Backend' here meant 'which LLM API provider', and the doc already
uses 'provider' for the same concept a few lines down ('provider-
specific model string') - so reword to that instead of carving out
an exception. Drops the inline vale suppression comment entirely.
2026-09-09 22:55:28 +02:00
iris
397c0a9c7a docs: fix invalid -- inside an HTML comment body (argus nit on #4146)
Em dash to match house style everywhere else, plus HTML comment
bodies can't contain a literal --.
2026-09-09 22:55:28 +02:00
iris
f22791b7a4 docs: clear the remaining error-level vale lints
Per #4128 (mara: allow-everywhere false positives go in a central
list, otherwise fix in source). Testing surfaced better fixes than
the plan posted on the issue:

- 5x Microsoft.Contractions 'that is' idiom false positives: adding
  the missing comma ("that is, ...") both reads better and satisfies
  the rule's own negative-lookahead, so no suppression is needed at
  all. Fixed in docs/integrations/forge.md, docs/tools/forge.md,
  docs/tools/hivectl.md, docs/web-ui/dashboard.md, and
  swarmctl-cli.md's generated source (swarmctl/src/main.rs, doc
  comment regenerated via markdown-docs).
- persistence.md's 'is not' matching inside 'is nothing': reworded to
  'there'\''s nothing' rather than add any exception -- dodges the trap
  and is a genuine contraction besides.
- ca.md's 'it is' matching inside the already-correct 'it isn'\''t':
  tried a central .vale.ini TokenIgnores entry first per the
  allow-everywhere framing, but testing against the real file (not
  just a synthetic snippet) found it silently fails to suppress
  whenever markdown emphasis syntax appears earlier in the same
  file -- an offset-drift bug in how Vale applies TokenIgnores, not
  a config mistake. Reworded to "it'\''s not" instead, same fix
  shape as persistence.md.
- config.md's 3 genuine Microsoft.Avoid 'backend' exceptions (already
  flagged and accepted on #4139 -- an actually-pluggable LLM API
  provider, matching the nix option's own name, not one internal
  system to name): scoped inline vale suppression around just that
  section, since this one really is context-specific rather than a
  rule bug.

Verified: fresh 'vale docs/ --minAlertLevel=error' is 0 errors AND
0 warnings (was 10 errors). nix fmt 0 changed beyond the edits
themselves. pre-push lints (tracker-tag/comment-block/doc-pointer)
clean. cargo clippy -p swarmctl -- -D warnings clean. Diffed the
regenerated swarmctl-cli.md against the old copy to confirm only
the intended line moved.
2026-09-09 22:55:28 +02:00
damocles
a4f72365c7 check-issue-refs: catch full forge issue URLs too, drop internal links from docs entirely 2026-09-09 21:15:28 +02:00
damocles
e1e913015d check-issue-refs: blanket-ban tracker tags in markdown too, no exceptions 2026-09-09 21:02:48 +02:00
damocles
786ab304d6 check-issue-refs: catch ambiguous owner#N cross-repo refs in markdown too 2026-09-09 20:21:35 +02:00
iris
9d02d81144 docs: name the actor instead of the vague 'we'/'our'
Per mara's ruling on hyperhive#4041 (Microsoft.We): keep the rule enabled,
same treatment as the 'backend' rewrites. Traced all 21 genuine hits (4
gateway.md 'Let's Encrypt' hits are a substring-match false positive, left
alone) to their actual referent: some name a specific component already
established nearby in the same doc (forge_notify, hive-github-notify's
poller, hive-forge, hive-agent/the harness, colors.css, the dashboard),
others were pure filler that adds nothing once dropped.
2026-09-09 18:42:53 +02:00
atlas
bd64a8590d docs/security: use contractions, so vale's Microsoft style is clean
The section this branch adds was the only source of `Microsoft.Contractions`
errors in the file — 9 of them, and vale over the pre-change bytes still
reports exactly those 9, so the count is attributable rather than inferred
from a repo-wide total that was already red.

One of the nine is not a contraction: vale reads `it is` in "Narrowing it is
tracked in ..." as a subject-verb pair, but it is `it` (the object of
narrowing) plus `is tracked`. Rephrased so the sentence leads with the issue
instead, which drops the false hit and the passive-voice warning together.
2026-09-09 18:40:41 +02:00
atlas
3752482524 swarm: let every hive read every agent's credential, and say so
A hive reads its agents' credentials with its own certificate, and nothing
said which paths that certificate may read, so the read half of a delivery
answered 403.

The grant is wide on purpose. An agent's path does not name the hive
hosting it -- agents move -- so a per-hive grant has to be an enumeration
the controller re-emits whenever the roster changes, and an enumeration
that can drift or land out of order advertises a boundary it does not
hold. A wide grant that says what it is beats a narrow one that only looks
narrow. mara's call, on the PR: rather a too-lax scope than one that
pretends to be strict.

What that buys, beyond honesty: the document is identical for every hive
and depends on nothing, so it is written once at startup beside the rest of
a hive's provisioning instead of on every declaration. No derived state, no
re-emission, and the ordering hazard that came with one stops existing.

What still holds is read-only. A hive cannot write an agent's credential,
so it cannot hand itself an agent's identity, and the grant reaches nothing
in the store outside the agent-credential prefix.

The fact is documented where someone meets the boundary rather than only in
this message, and the two ways to narrow it later -- scope per hive, or
give agents their own store identity -- are tracked.
2026-09-09 18:40:41 +02:00
atlas
e638db262e swarm-controller: keep a hive's read grant in step with its declaration
The controller writes an agent's credential; the hive fetches it back with
its own certificate. Nothing said which paths that certificate may read, so
the read half of a delivery answers 403 with no way to tell why.

The grant is derived from the declaration, so it is re-rendered at the one
place the declaration changes -- WantedWriter::set -- rather than at its
caller, which would work today and break on the second caller.

Emitted before the KV write: a grant that lands late is a 403 on an agent's
first fetch, while one that shrinks early only affects an agent already
being torn down. A failed write then leaves a superset the next declaration
re-renders.

Destroyed agents are filtered out. The declared set is a hive's whole
history -- a destroyed entry stays so that redeclaring it Up is refused as
the terminal transition it is -- so granting every declared agent would
leave a torn-down agent's credentials readable forever.

The sink is a trait because a missed emission is that same untraceable 403:
the double pins which agents were published, and the no-sink and refusing
arms pin the two deployments that are not a happy path. Not covered: the
call site inside set(), which needs a live queue.

The cert role moves to its own module on the way past. It is the
controller's identity at the store, not something the matrix route owns,
and the policy writer needs the same login.
2026-09-09 18:40:41 +02:00
atlas
c590447e8f swarm-secret-client: give the store handle a policy write
The renderer beside this produces a hive's read policy; nothing could put
one in the store. `read`/`write` are KV v2 verbs and a policy is not a
secret, so this is a second endpoint rather than another path.

A whole-document write, because the store has no merge verb: the caller
renders from the current agent set, so a stanza missing from the render
is a grant that is meant to be gone.
2026-09-09 18:40:41 +02:00
atlas
7792b62609 swarm-secret-client: render a hive's read policy, and refuse names that inject
The controller writes an agent's credential; the hive reads it back with its
own token. Nothing says which paths that token may read, so the read half of
a delivery has no identity at all and answers 403.

This is the pure half of the fix: the policy text is a function of a hive name
and its agent set, so the shape can be asserted with no store to talk to.

One stanza per hosted agent rather than a prefix grant. An agent's credential
path does not name the hive hosting it -- deliberately, since agents migrate --
so "this hive's agents" has no prefix expression and must be enumerated. The
grant is read-only: the controller mints these and never reads one back.

The paths come from MOUNT and AGENT_PREFIX rather than literals, so the policy
cannot drift from the module that builds the paths it grants.

checked_segment runs before any name reaches HCL. That is policy injection
rather than path traversal -- a name can close a stanza and open a wider one --
so the test carries a real injection string, paired with the reachable-charset
control that stops it passing by refusing everything.

An empty agent set renders an empty policy, which grants nothing.
2026-09-09 18:40:41 +02:00
damocles
004cf7f449 swarm-queue-client: fix AgentState::Destroyed doc, entry never drops out of the set 2026-09-09 18:40:39 +02:00
iris
29927b7dfe docs: name the specific daemon instead of the vague 'backend'
Per mara's ruling on hyperhive#4041 (Microsoft.Avoid): 'backend' never says
which system it means. Fixed all 21 fixable hits to name the actual daemon
(mostly hive-c0re; one hive-agent instance and one hive-matrix-daemon one,
both confirmed against surrounding context, not guessed). Also tightened
Weston's own 'VNC backend'/'RDP backend' plugin references to their literal
upstream names (vnc-backend/rdp-backend) rather than dropping the accurate
term entirely, and reworded one unrelated Microsoft.Avoid hit ('and so on').

Left alone (flagged on the issue, not guessed): docs/turn-loop/config.md's
3 remaining hits match the actual nix option name (backendEnvironmentFile)
and describe a genuinely pluggable concept (which LLM API provider), not
one specific internal system.
2026-09-09 17:59:10 +02:00
iris
08074807cc docs: fix a Contractions + Passive regression in setup.md
cc8fb0ee44 (swarm-bao controller-write-credentials
work) added a fresh 'did not' warning callout in the bao-provision section,
right after #4129 zeroed out the genuine error-level Microsoft.Contractions
backlog, and its passive 'has been exercised' phrasing put getting-started/
back above its documented Passive-pass accepted-residual baseline (10) too.

Contracted the 'did not' and reworded the passive opener to an active one
('This has only run once...') in the same sentence, since both hits were on
the same line.
2026-09-09 15:58:32 +02:00
iris
15890f9d5b docs: fix genuine Microsoft.Contractions/Negative errors
16 of the 23 error-level hits from a full-repo vale run were real:
'is/are/does/do NOT' and bold 'not' expanded per Microsoft.Contractions,
one hyphen-as-negative-number fixed to an en dash per Microsoft.Negative
(docs/web-ui/dashboard.md's -45 deg column-header tilt).

Where the caps/bold was there for emphasis (contrasting X does this,
Y does not), kept the emphasis by bolding the contraction rather than
dropping it silently -- 'is not' -> 'isn't', 'does NOT' -> '**doesn't**',
etc.

docs/tools/swarmctl-cli.md's one genuine hit is generated -- fixed the
source doc comment in swarmctl/src/main.rs and regenerated via
'cargo run --bin swarmctl -- markdown-docs', diffed against the old
copy first to confirm only the intended line changed.

Left 7 hits unfixed, false positives from the rule matching a
substring rather than the actual pattern it names:
- 4x 'that's instead of that is': every one is the explanatory 'that
  is' idiom (~= i.e.), not a contractible subject+verb -- 'that's'
  would misparse as a demonstrative-pronoun contraction and change the
  sentence's meaning. docs/integrations/forge.md:282,
  docs/tools/forge.md:260, docs/tools/hivectl.md:243,
  docs/web-ui/dashboard.md:457 (and the swarmctl-cli.md generated
  twin of the same category, source left alone for the same reason).
- docs/agent-lifecycle/persistence.md:482 -- 'is not' matched inside
  'is nothing' (the same has-not/has-nothing substring trap noted
  before in this doc's own git history).
- docs/swarm/ca.md:182 -- 'it is' matched inside the already-correct
  'it isn't'.

Per #4128. Remaining errors on main after this: Microsoft.Avoid's 26
hits, already tracked + awaiting a house-style ruling on #4041.
2026-09-09 01:48:46 +02:00
iris
ec67d2dd36 address review: extract mode resolution, add unit tests, drop Debug
- resolve_credential (+ token_credential / password_fields helpers)
extracted out of put_matrix_account, which was tripping
clippy::too_many_lines (101/100). Both helpers surface plain &str
errors rather than ProblemDetails to dodge clippy::result_large_err on
a private fn (put_matrix_account itself is exempt only via clippy's
avoid-breaking-exported-api default, which does not cover these);
resolve_credential boxes its own ProblemDetails Err for the same
reason, unboxed at its one call site.
- Added unit tests for the extracted logic: main-reserved, token-mode
missing-token / pass-through, password-mode missing-fields / trailing-
slash trim, and two async resolve_credential end-to-end checks
(token mode, unknown mode) that need no network access.
- Dropped PutMatrixAccountRequest's Debug derive to match
hive-c0re::dashboard::matrix_accounts::MatrixLoginForm's existing
precedent of not deriving Debug on a struct carrying a password field.
2026-09-09 01:36:52 +02:00
iris
52446cb273 matrix accounts: server-side password login for swarm-controller
Extends PutMatrixAccountRequest with a mode field (token, the existing
behavior and default; or password). Password mode has swarm-controller
itself perform m.login.password against the caller-given homeserver
(mirrors hive-c0re's own /api/matrix-account-login for the hive-local
case) and stores the resulting token instead of a caller-supplied one
-- the password is used once, over this PUT, and never stored. Also
adds the 'main is reserved' guard hive-c0re's login form already has,
which swarm-controller had no equivalent of before this.

swarm-ui's link-matrix-account form gets a credential-mode toggle
wired to the same contract: token mode is unchanged, password mode
swaps the token field for user-id + password fields and makes
homeserver required (no hive-side fallback to resolve it against, per
PutMatrixAccountRequest::homeserver's own doc).

Per #4122.
2026-09-09 01:36:52 +02:00
atlas
cc8fb0ee44 swarm-bao: let the controller write agent credentials, and gate that it can
Closes #4124.

The controller's policy granted only the bootstrap paths -- hive cert-auth
roles and hive ACLs. #4113 then made it a secret WRITER, and nothing related
the grants to the paths the code writes, so every matrix token provision
answered 403. The two halves landed on different issues and neither looked
wrong on its own.

`secret/data/` is KV v2's ACL prefix and is absent from the path the code
passes, so matching `swarm-secret-client`'s spelling literally would have
granted nothing. Write-only: the controller mints these and never reads one
back, and a read capability would let it recover every agent's credentials
rather than only replace them.

The gate is the point. Two module-eval arms -- the grant exists and is not
a broader wildcard, and its capability list is pinned whole, because an
ADDED capability is what a presence check misses -- plus a test in path.rs
pinning MOUNT/AGENT_PREFIX and naming the nix file, since renaming either
constant is a silent 403 rather than a compile error.

setup.md carried two warnings this makes false: that nothing in the tree had
ever authenticated to the store, and that no deployment shape mints a leaf
whose CN reads swarm-controller. glue-bao-tls.nix has minted one since #3726
item 1.
2026-09-09 01:19:57 +02:00
iris
85d0b8de5d swarm-ui: stop suggesting the reserved default account as an example name
The link-matrix-account form's placeholder read 'e.g. primary', which
sounds like it names the hive-provided default account. That account is
actually named main (reserved in hyperhive.matrixAccounts's schema) and
this route can neither create nor touch it. Swapped to an example with
no relationship to the real default, per #4122.
2026-09-09 00:30:27 +02:00
iris
a407736e20 docs/scheduler/coordinator.md: repad RebuildBookkeeping table row
nix fmt table-column-width drift from the earlier comma fix (f3fe0604/82236952)
- shortening that cell by one char left the row under-padded relative to the
table's other rows. Pure whitespace, no content change.
2026-09-08 20:23:54 +02:00
iris
fd633f674e docs: drop the comma outside the closing quote in coordinator.md
argus's review on this PR caught a real regression: the earlier
passive-voice rewrite ('answers "did the agent come back up?", the
same way...') put a comma immediately outside a closing double-quote,
which vale's Microsoft.Quotes rule flags ("punctuation should be
inside the quotes") -- a brand new error not present on main. The
original phrasing never had a quote directly followed by a comma, so
this was a real side effect of the rewrite, not a pre-existing hit.

Simplest fix: drop the comma. The sentence reads fine without it and
the closing "?" already does the job.

Verified via vale: back to 2 errors (matching main's baseline, down
from the 3 this rewrite introduced), write-good.Passive count
unchanged at 24.
2026-09-08 20:23:54 +02:00
iris
09e4e2f5e9 docs: fix genuine passive-voice hits in docs/scheduler
Seventh batch of the ongoing write-good.Passive pass (hyperhive#4042):
read all 65 hits across jobq.md/ci.md/observability.md/coordinator.md
in context and rewrote 41 with a clearly nameable actor -- mostly
hive-c0re, nix/the nix module, the harness, or a specific fn/type
named right there or nearby (coordinator.md's node-inventory table
and DAG-shape descriptions name concrete Rust items constantly, so
the actor is almost always sitting in the same sentence).

Left 24 alone: predicate-adjective-copula state descriptions ("is
stuck", "is gone", "is unaffected", "is done", etc. -- the largest
recurring bucket this batch, especially in observability.md's
scope/status descriptions), negative-capability idioms ("no X is
needed/left", "X can't be written down"), the established "is
tracked as a follow-up" idiom, a firewall-shorthand notation
("bridge->127.0.0.0/8 is dropped") where rewriting would break the
compact rule-like format, a CLI-flag "(repeatable)" annotation ("May
be repeated"), a Rust type-signature fact ("`moves` is typed ..."),
a hypothetical/counterfactual maintenance-burden clause, a
readiness-condition list ("a node is ready when ... every dep is
satisfied"), and one deliberately-parallel idiom pair
("When OTEL is enabled" used identically twice as a section-opening
convention -- fixing one would break the parallelism, not the
opposite).

One self-caught regression: an early attempt to fix "used by every
`Reconcile` node's start action" (a reduced participial clause, not
flagged) into "is used by every `Reconcile` node's start action"
introduced a brand-new flagged passive. Caught by the post-edit vale
count (expected 65->24, got 65->25) not matching, same discipline as
the docs/turn-loop batch's tail-truncation catch -- re-ran with
active voice instead ("Every `Reconcile` node's start action uses
this fallback").

Verified via vale before/after: 65 -> 24 write-good.Passive hits,
exactly the 24 left alone above; error count and other warning
categories unchanged. Re-read every changed line in full surrounding
context after editing before running the final vale check.
2026-09-08 20:23:54 +02:00
iris
ec433a07dd docs: fix Microsoft.UIVerbs hits in dashboard.md (click -> select)
Two genuine instruction-verb hits: the container-row selection toggle
and the schedule-table per-target cancel button both said 'click' where
the action also works via keyboard (Enter/Space) or is otherwise
input-device-neutral.

Left the other 4 hits found in the same #4041 sweep alone (false
positives, not fixed here): agent.md's 'ctrl/middle-click' and
'clicks' (real mouse-button-specific browser/VNC behavior, not reader
instructions), shape.md's 'swipes in' (describes the drawer's own
animation, not a user gesture), and process/gotchas.md's
'click-to-unlock' (weston's actual lock-screen feature name).
2026-09-08 16:05:33 +02:00
iris
acdd9e884b docs: fix genuine passive-voice hits in docs/web-ui/dashboard.md (part 2/2)
Second half (lines 762-1389) of dashboard.md's write-good.Passive backlog --
22 rewrites, reusing an actor already named nearby wherever one exists
(the dashboard, hive-c0re, the harness, the operator, the endpoint, the
broker forwarder, CSS, Client, the manual fan-out, ...). Companion to
part 1 (PR #4117, lines 1-697).

10 left alone in this half: five established idioms (config-state
conditionals, negative-capability "nothing is X", state descriptors
"is untouched"/"is gone"), and one literal quoted UI string
("container will be created") that documents exact displayed text
rather than the doc's own voice. One rewrite (naming "the dashboard"
for the old L762 'is known stopped') incidentally leaves behind a new
instance of the same 'is stopped' idiom already accepted at L797 --
21 net fewer hits from this half, not 22, though 22 edits were made.

Together with part 1 (31 rewrites, 7 left alone), this closes out
dashboard.md's 70-hit Passive backlog: 53 edits total, 52 net fewer
hits, 18 left alone.
2026-09-08 15:54:39 +02:00
iris
a52d344600 docs: fix genuine passive-voice hits in docs/web-ui/dashboard.md (part 1/2)
First half (lines 1-697) of dashboard.md's write-good.Passive backlog --
31 of the file's 70 hits rewritten to name an actor (hive-c0re, the page,
the operator, the table, the filter, builds.js, Provision, the matrix
daemon, ...), reusing an actor already named nearby wherever one exists.
7 left alone in this half: a fixed idiom ("not to be confused with"),
two false positives ("named buckets" / compound adjective; a
field-is-set data-state conditional), a negative-capability idiom
("nothing is written"), and two established state-descriptor idioms
("is unchanged", "is tooltipped").

Part 2 (lines 762-1389, ~32 more hits) is a separate follow-up, not
started -- dashboard.md is too big for one PR.
2026-09-08 15:54:31 +02:00
iris
260c4eb854 docs/swarm: re-pad README table after the unknown-row rewrite
The rewrite widened the unknown row's cell by one character; nix fmt
re-pads the whole table to match. Pure whitespace, no content change.
2026-09-08 15:54:16 +02:00
iris
fab2a0dedc docs: fix genuine passive-voice hits in docs/swarm
Read all 94 write-good.Passive hits across docs/swarm/ (ca.md,
README.md, secrets.md, services.md, sso.md, ui.md) in context. 44 are
genuine catches with a nameable, usually already-established actor
(swarm-controller, authelia, swarmctl, the controller, the gateway,
this module, hyperhive itself, or 'the operator' for manual actions) —
rewritten to active. 50 are legitimate passives or false catches, left
alone: predicate-adjective state descriptions (is expected/misconfigured/
broken), negative-capability idioms (no X is needed/placed, can't be
Yed/listed/fetched), config-state conditionals (whenever/when X is
enabled/configured/set), requirement-list labels (is required),
'is tracked as' idiom, backward-looking changelog facts with no actor
(was removed/verified/introduced), ambiguous-actor statements left
conservatively alone (agents are created and destroyed — could be
hive-c0re or swarm-controller, doc doesn't say), and a couple of
deliberately-parallel idiom pairs.

Several sibling-inconsistency fixes: a passive clause sitting next to
an already-active sibling describing the same fact/mechanism (ca.md's
two-bullet consumer list, README's 4-item WireGuard-mesh bullet list,
README's controller-registers-hooks paragraph, sso.md's followed-a-302
sentence).

Verified via vale on the whole directory, diffed against main's exact
baseline (not just the Passive count): write-good.Passive 94 -> 50
exactly, every other category unchanged (1 pre-existing
Microsoft.Contractions error at services... at secrets.md:182,
8 TooWordy, 1 Microsoft.We, 1 Microsoft.FirstPerson — same counts,
same locations).
2026-09-08 15:54:16 +02:00
iris
e9d59f3d10 docs: fix genuine passive-voice hits in docs/web-ui (small files)
agent.md, css-vars.md, design-guide.md, shape.md, terminal-rendering.md --
26 of 40 write-good.Passive hits rewritten to name the actor (hive-agent,
browsers, the harness, the client, lifecycle::{spawn,rebuild}, CSS, the
router, ...), reusing an actor already named nearby wherever one exists.
14 left alone: established config-state conditionals ("when X is
set/enabled/given/called"), negative-capability/state-descriptor idioms
("is gone", "is hidden", "is unchanged"), one false-positive
("typed slash commands" as a compound noun, not a passive action), a
backward-looking changelog fact with no actor worth naming, and two
deliberately-parallel rhetorical contrasts (sanitized vs XSS-safe;
cache-hit vs cache-miss) left symmetric on purpose.

dashboard.md (70 more hits) is the next docs/web-ui/ batch, not part of
this one -- big enough to deserve its own PR.
2026-09-08 15:54:07 +02:00
iris
767f87610a swarm-ui: link a matrix account to an agent
New per-row action on AgentsPage: a quiet-variant icon badge (LinkIcon,
matching the LinksMenu/SettingsMenu chrome-not-chip convention) opens a
dialog with an account/token/homeserver form, PUTting
/api/hives/{hive}/agents/{agent}/matrix-accounts/{account} per the
contract atlas posted on hyperhive#3726 (issuecomment-72355).

Built against the contract before the backend endpoint exists per
atlas's explicit note that it doesn't change when the implementation
lands -- this 404s until that item merges. No linked-accounts list:
no route exposes one, and a credential store shouldn't hand a secret
back out anyway, so this is a blind set/update action, matching
mara's 1:1-for-now ruling on the issue.

Verified: tsc --noEmit and nix fmt clean, esbuild build clean. Real
DOM-interaction screenshots against a throwaway mock server (deleted
before this commit, never tracked) -- table column render + disabled
state on a hiveless row, dialog open, form filled with the token
masked, and the success path end to end (token field clears, success
message shows) against a mocked 200 response.
2026-09-08 15:54:01 +02:00
atlas
0d88ca5e7f swarm-controller: accept an agent's external matrix account and put it in the store
The swarm UI had nowhere to POST an external matrix account to: this daemon
had no matrix-account code at all and no `swarm-secret-client` dependency, so
the last leg of #3726 — a credential reaching an agent — had no entry point.

`PUT /api/hives/{hive}/agents/{agent}/matrix-accounts/{account}` writes the
credential to the store under the agent's own path and publishes a
`CredentialNotice` on that hive's credential subject. All three path names are
load-bearing: agent + account locate the secret, hive routes the notice. The
account is a path segment rather than a body field so that splitting the 1:1
account-to-agent mapping later is a new route, not a changed payload.

Store first, notify second, and the order cannot be swapped: a notice that
overtakes its own write reaches a hive that reads nothing, and the hive
deliberately does not retry. The publish is followed by a flush for the reason
`publish_deploy` flushes — `publish` hands the message to the connection's
write buffer and returns, so the response could otherwise outrun the notice it
reports as sent.

The store client is built per request rather than held in `AppState`, matching
what the hive side does inside `deliver`: a login that expires is not worth
caching for a route this cold.

`swarm_hive` is `declaration_target`'s two name checks, extracted so this
handler makes them identically rather than in a second copy free to drift.
`declaration_target` still tests the writer first, so a deployment with no
queue answers 503 whatever the caller spelled.

## The nix half

#4081 minted the controller's leaf and gave it `baoClientCertFile` /
`baoClientKeyFile`, deliberately stopping there — the leaf is minted whether or
not a controller runs on that host. Nothing consumed those options, so the
identity never reached the process. Measured before writing: `git grep
baoClientCertFile` returned 5 sites and zero consumers, against a control
(`tokenEndpoint`, 4 hits in the same file) proving the search can see
consumption where it exists.

The unit now gets `BAO_ADDR` / `BAO_CLIENT_CERT` / `BAO_CLIENT_KEY` /
`BAO_CACERT` and the matching `LoadCredential` entries, following
`hive-c0re/environment.nix`'s `%d` credential shape.

The gate is `deploy.swarm-controller.baoClientCertFile`, NOT
`deploy.bao.clientCertFile`. The latter is the hive reader's identity and its
policy scopes a hive's own secrets; wiring it here would evaluate, deploy, and
fail only when the daemon tried to write an agent's credential.

Two `module-eval` arms cover exactly that. The presence arm asserts the
`LoadCredential` *source path* (`…:/var/lib/swarm-bao-pki/controller.pem`) and
not just the `%d` name, because a `%d`-only assertion passes while the daemon
holds the wrong policy. The absence arm (`controllerNoStore`) is what makes the
presence arm mean anything.

`RestrictAddressFamilies` already covers the store client; its own comment asks
for the family to be added with the client, and AF_INET/AF_INET6 are present.

Contributes to #3726
2026-09-08 15:53:50 +02:00