Commit graph

4,688 commits

Author SHA1 Message Date
atlas
fcb8a594bb swarm-controller: make socketPath readOnly instead of asserting it
readOnly makes a bad socketPath inexpressible rather than diagnosed
after the fact. Since readOnly rejects any definition including one
arriving through a rename shim, drop the deploy.nix rename entry for
it and the fixture line exercising it, and update the controllerOldPath
comment's shim count from seven to six.

Refs #4208
2026-09-17 19:27:30 +02:00
atlas
205986f77e swarm-controller: assert socketPath is not in a shared runtime dir
The socket is 0666 (nginx needs to connect(2) as a different user), so its
directory is the only thing bounding what the gateway's nginx can reach —
the crate's README already says so. A unit test pins DEFAULT_SOCKET, but
that only catches a developer editing the const; an operator setting the
documented socketPath option to a shared dir (notably /run/hyperhive,
which holds the host admin socket, or /run/hive) went unenforced.

Denylist rather than the general "not any other module's socket dir"
form — narrower, but it's what the README names and lands without pulling
in other modules' constants.

Refs #4208
2026-09-17 19:27:30 +02:00
iris
9466b82870 swarm-grafana: move CLAUDE.md size to the health tab, not activity
mara: "claude md size is agent health not activity." Moves panel-33 from the
activity tab to health (below the storage bargauge), content untouched.
2026-09-17 18:56:22 +02:00
atlas
29b53ca8d0 claude-plugins: format the swarm-logs skill with nix fmt
treefmt normalises markdown emphasis to underscores; the file was
written with asterisks and never run through `nix fmt`. Also ends the
sentence before the scoping note with a full stop rather than a colon,
since a paragraph now sits between it and the example block.

Refs #4460
2026-09-17 15:17:33 +02:00
atlas
b705949c24 claude-plugins: drop a scratch report file from the branch
A scratch report written inside the worktree was swept in by a
`git add -A`. It is not part of the change, and its contents trip the
tracker-reference lint.

Refs #4460
2026-09-17 15:12:33 +02:00
atlas
d4551124f7 claude-plugins: scope the swarm-logs example by machine id
The worked example keyed a stream selector on `_HOSTNAME`, which the
collector config warns against: a hostname is a config value two
machines can share, so keying on it merges their streams. Use
`_MACHINE_ID`, which is the field that actually distinguishes them.

Refs #4460
2026-09-17 15:04:42 +02:00
atlas
c95f721f81 claude-plugins: add a swarm-logs skill
`swarm-logs --help` documents the CLI surface but not the LogsQL
constructs an agent needs to build a bounded query with it: no
time predicate, and no way to know a bare word matches across the
whole swarm rather than one hive. Add a skill teaching `_time:`
predicates, hive/service scoping via the collector's stream
fields, message vs json format, distinguishing an empty result
from a refused request, and running a control query before
trusting an empty one.

Refs #4460
2026-09-17 15:00:45 +02:00
atlas
02f90b7bc6 swarm-bao: add a pki mount, role and issuing policy
First slice of the move off the hand-rolled swarm services sub-CA: the
store gains the three objects the eventual minter needs, and nothing
else.

- a `pki` secrets mount, enabled ask-first off the same `bao secrets
  list` snapshot the kv-v2 mount beside it uses, so a rebuild that finds
  it mounted does nothing;
- a `swarm-services` role on it, `allowed_domains` read straight out of
  `swarm.serviceDomains` — the same swarm-tier list swarm-ca.nix
  name-constrains its sub-CA to and hive-tls.nix carries as the leaf's
  SANs — with subdomains, localhost, globs and IP SANs all off;
- a `swarm-services-issuer` policy granting `update` on
  `pki/issue/swarm-services`.

Inert: the mount has no issuer generated into it, the role therefore
cannot issue, and no login role attaches the policy, so no token in the
swarm carries it. No consumer changes; swarm-ca.nix still mints the
services sub-CA exactly as before. Reversible with
`bao secrets disable pki`.

The cert-auth role that attaches the policy waits for the leaf carrying
its CN, which glue-bao-tls.nix mints — a later step.

Refs #4256
2026-09-17 14:01:38 +02:00
flake-bot
1991fa9565 nix flake update 2026-09-17 13:58:50 +02:00
atlas
6e23af36c9 docs/swarm/credentials: a hive is an identity too
Line 26's list named two identity kinds; mara flagged it as
should-be-three and the third went unnamed. A hive holds its own mTLS
leaf (glue-bao-tls.nix:130-131, CN = hiveName from :49) and logs in
with it (glue-queue-agent-credential.nix:171, bao login
-method=cert); swarm-bao.nix mints matching cert-auth roles for it
alongside swarm-controller (:1049-1052) and swarm-secret-publisher
(:1101-1104), and swarm-controller/src/read_policy.rs:184-188 writes
the per-hive role dynamically. The table further down this page
already lists a hive as a reader (swarm/hives/<hive>/matrix/appservice-token),
so the line-26 list was inconsistent with the rest of the page.
2026-09-17 13:56:34 +02:00
atlas
ded0379f97 hive-forge: lint unlabeled --scope rejects an unknown scope
Refs #4468

A typo'd or invented --scope matched no label at all, and because
nothing carries a label in a scope that does not exist, every open
item came back reported as missing it -- the failure direction reads
as a finding instead of an error.

Validate --scope the same way list --label already validates
--label: reuse labels::repo_labels (the paginated label fetch) and
error with the bad scope plus the available ones, using the same
with_suggestions near-miss helper list --label's message uses.
2026-09-17 13:23:05 +02:00
atlas
d9d6d37951 swarm-queue-client: trim token_request comment block under the 30-line lint
The scope-parameter addition pushed the doc comment on token_request to 39
lines. Moved the HTTP-Basic incident story and the audience/scope rationale
to the crate README's new "Token request shape" section (docs/ is markdown,
exempt from the lint); the code comment keeps the pointer plus the one-line
summary of the invariant. Refs #4464
2026-09-17 10:03:27 +02:00
atlas
d8f6d99bf9 swarm-queue-client: request the bearer-authz scope when minting an agent token
`swarm-logs query` got a bare nginx 401 from the swarm log store on every
query. The agent OIDC client is registered for `authelia.bearer.authz`
(`swarm-authelia.nix`'s `agentClients` sets `bearerAuthz`), but registration
is not issuance: the token request asked for no scope, so the token came back
carrying none, and authelia's `/api/authz/auth-request` refuses that exactly
as it refuses an unauthenticated caller.

The same failure is already recorded in `swarm-otel.nix` against the
collector's client, on the same scope string — prometheus asks for no scopes
unless told to, and every scrape was refused at introspection. This is that
bug one layer down, so it gets the same shape of fix.

`scope` becomes an opt-in parameter alongside `audience`, not a hardcoded
value or a config field: the two travel together (registered ≠ requested
applies to both) and only the destination decides whether either is needed.
`None` keeps every other caller byte-identical — the NATS connect callback,
`auth.rs`'s bridge client and the OTLP push client all pass it.

Refs #4464
2026-09-17 09:51:44 +02:00
atlas
42dcf10064 swarm-logs-cli.md: regenerate from the binary, prettierignore it
The committed doc was stale (missing help text under --limit/--format
that the binary's clap-markdown output now includes). Regenerated with
the documented command. While at it: docs/tools/swarm-logs-cli.md was
never added to .prettierignore alongside its three siblings, so nix fmt
silently reformatted it back to a stale layout right after
regeneration — the exact failure mode .prettierignore's own comment
warns about. Added it so the *-docs-fresh check in nix/checks.nix stays
satisfiable.
2026-09-17 01:02:14 +02:00
atlas
a39399f037 swarm-logs: an agent's CLI for the swarm log store
An agent can reach VictoriaLogs only through the gateway, and since the
machine query route landed the way to read it has been to hand-roll a
client_credentials token request and a curl, per query. This is the CLI
that closes that: `swarm-logs query '<LogsQL>'`, matched log lines on
stdout, so the answer pipes into grep like any other command's.

Built to the plan posted on the tracker thread: own crate, own
docs/tools reference generated off the clap tree, `query` as the one
verb, and the JSON error body surfaced on a non-200 rather than
swallowed. No `tail`: streaming is a different endpoint with a different
response shape, and folding it in here would be a fatter scope than the
ask.

Minting the token is NOT implemented here — swarm-queue-client already
owns the client_credentials request, its error type and its CA handling,
and a token-endpoint fix has to be findable in one place. What this crate
adds is the agent-shaped half: the client id arrives as a *file* beside
the secret, so nothing outside nix/agent-modules/queue.nix spells
`hive-<name>-agent` twice. That is the same problem hive-agent's
swarm_queue module solves, and swarm-logs/src/auth.rs is its `decide`
restated over this binary's inputs.

⚠️ The plan named one thing to verify empirically before calling the auth
settled: whether authelia's bearer policy for the logs vhost accepts the
agent client's audience. Measured from inside a container: it does not.
The client minted a token fine but with `aud: []` and `scp: []`, asking
for the logs URL as an audience answered `invalid_target`, and presenting
the audience-less token to the gateway answered a bare 401. So
swarm-authelia.nix's agentClients gains `authelia.bearer.authz` and the
query URL as a second audience — authelia authorises a bearer token by
the URL being requested, and that URL is now one binding read by three
places rather than three spellings of one address.

The URL reaches an agent the same way its queue coordinates do: computed
on the host (a container cannot derive a gateway address), forwarded by
hive_c0re::meta into the container's option set, and consumed by a new
agent module that installs the binary *wrapped* with its coordinates —
the shape swarm-controller.nix installs swarmctl in. Gated on the queue
credential as well as on the URL: a binary that can only answer 401 is
worse than no binary, because an agent reads a 401 as "no logs", which is
the exact confusion the store's machine route was added to end.
2026-09-17 01:02:14 +02:00
flake-bot
2186b82485 nix flake update 2026-09-17 00:55:03 +02:00
atlas
b71ff90661 docs/swarm/credentials.md: restore the new-vs-existing progressive-enhancement split
The rewrite in #4445 flattened this section's new-functionality-vs-
existing-functionality split into unconditional rules, because it had
been anchored to the current-state table that PR deleted. Restore the
split on its own terms, per #4445's own description of the rule: new
functionality has to match the target shape immediately; existing
functionality can move step by step, provided each step moves toward
the target shape.

Refs #4445
2026-09-17 00:02:00 +02:00
atlas
0c50913db9 docs/swarm/credentials.md: mTLS cert is per-identity, not per-host
Two lines still framed the invariant as one file total: line 19 ('plus
exactly one file') and line 21 ('the only credential on disk') implied
a single mTLS cert page-wide, contradicting the earlier fix that made
line 26 say 'every host has at least one'. Agents and swarm-level
services each need their own identity certificate, so a host running
several holds several. Restate both as per-identity, keep the target
shape (store + certs on disk, nothing else) unchanged.

Also relax the touching-a-credential rule: moving a secret into bao
without resolving its renewal strategy in the same change is
acceptable while the migration to this shape is in progress, provided
a follow-up issue is opened to settle renewal.

Refs #4445
2026-09-16 23:29:39 +02:00
atlas
596e8f006f docs/swarm/credentials.md: every host has at least one certificate
- Change 'Every host has one' to 'Every host has at least one' to clarify
  that a host may hold multiple mTLS client certificates.
2026-09-16 23:24:54 +02:00
atlas
972b80d7b8 docs/swarm/credentials.md: fix citation range to include 'A path, never a value'
The citation for the nix option description was 529-531, but the quoted
phrase 'A path, never a value.' appears on line 533. The correct range
529-533 includes both the 'operator places by hand' claim and the
'path, never a value' assertion that the doc attributes to that file.

Refs #4445
2026-09-16 22:46:29 +02:00
atlas
cc77db0e7e docs/swarm/credentials.md: nix fmt table alignment 2026-09-16 22:41:53 +02:00
atlas
940e11ecda docs/swarm/credentials.md: cut the page down to the target shape only
Drop the current-state table and the prose describing how the swarm
currently breaks the target (unrotated secrets persisted to disk outside
the store, no agent store identity, the appservice token's second
minter). Scope the path-never-value rule to the mTLS client certificate
— the one credential a nix option can carry, now that everything else
goes through bao. Trim the migration checklist to unconditional rules
for any credential-touching change, since there's no current-state table
left for the old existing-functionality carve-outs to reference.

Refs #4445
2026-09-16 22:41:23 +02:00
atlas
f1e5fd5592 docs/swarm/credentials.md: make the target section bao-shaped, not file-shaped
The target section described the target as a shorter list of secret files —
the intro promised "what changes is how many paths there are", and the
per-secret contract pointed at the current-state table's file-shaped columns.
That is today's shape with fewer rows, not the target.

Rewrite it around the invariant: no secret the store holds is ever written to
disk. The mTLS client certificate is the sole credential on disk, and the
section now says why — it authenticates the puller to the store, so it is the
one credential that cannot be fetched from it. Per secret the target still
states minter, reader and renewal, but the reader is now a process pulling a
store path at runtime under its own certificate, not a path being read off
disk.

The current-state section and all of its citations are untouched: the current
state genuinely is files, and that contrast is what the page is for.
2026-09-16 22:41:23 +02:00
atlas
ae388c48a7 docs/swarm/credentials.md: restore claims changed by vale rewrite
Two vale rewrites in c33cc441 changed meaning, not just wording:
- "on track to become" asserted a trajectory the page's own inventory
  contradicts (no bao identity anywhere, all renewals NONE, all
  secrets plaintext). Restored intent: "what it should become".
- "most often misread" claimed an observed reader frequency nobody
  measured. Dropped the claim about readers entirely; states the fact
  directly instead.

Both stay vale-clean; no suppressions.
2026-09-16 22:41:23 +02:00
atlas
b2c1378fd3 docs/swarm/credentials.md: reword prose to pass vale
Fixes the Microsoft.Contractions/write-good.ThereIs/alex.Condescending/
Microsoft.Auto errors vale flagged in CI (prose-lint-errors, required):
contractions throughout, no sentences opening with 'There is', dropped
the 'auto-mints' hyphenation and the 'easily' condescension flag, and
renamed the credentials table's 'what it is' header to 'kind' (also
flagged once styles were resynced). No technical claim or file:line
citation changed. Same fix applied to the one new line in
docs/README.md's pointer to this page.
2026-09-16 22:41:23 +02:00
atlas
52fabe7096 docs/swarm/credentials.md: fix 7 drifted/wrong file:line citations
Audited all 22 file:line citations against source on this head, per
argus's PR review. Fixed:

- matrix_account.rs writer cell pointed at path-computation line 163;
  the actual store.write() call is at 189-197 (now anchored to the
  put_matrix_account function name too).
- swarm-secret-publisher.nix appservice-token minter cited the
  "put on every run" comment (280-286) instead of the mint itself
  (271-278).
- glue-matrix-bao-token.nix persisted-token cell cited the
  registration-restamp comment (186-188) instead of the printf+chmod
  (180-181).
- glue-queue-agent-credential.nix persisted-files cell missed the
  secret's chmod 0600 at 219, citing 224-231; corrected to 218-224.
- swarm-otel.nix persisted-file cell cited 763-767, one line short of
  the chmod 0400 at 768; corrected to 764-768.
- hive-priv/src/main.rs:368,413 (forge/github token cell) landed in
  unrelated DestroyContainer/ListContainers/WriteAgentGithubToken match
  arms; replaced with the write_forge_token/write_github_token function
  names as a greppable, edit-resistant anchor.
- swarm-bao.nix:527-528 (the "operator places by hand" quote) missed
  the quoted text, which starts at 529; corrected to 529-531.

15 of 22 already resolved exactly; no claim lacked supporting code, so
nothing was removed. 22 of 22 citations checked.
2026-09-16 22:41:23 +02:00
atlas
937289383c Add a credential docs page: current state, target state, the rule between
The swarm's credential docs say where every file lives. They do not say
whether it should be a file at all, so a discussion about direction has
had nothing to point at and each one re-derived the same table.

This page carries that table with the three columns the target contract
is written in — minter, reader, renewal — plus the column the target is
really about: whether the value is persisted outside the store. Stating
it flatly is the point. All four stored families are plaintext files on
disk, the appservice token twice; every renewal cell reads NONE; no agent
container holds a store identity at all, so the hive reads on its behalf
and writes a file in; and the appservice token has a second, uncoordinated
local mint that can diverge from the published one.

The target section is marked as a target throughout, because its first
line is the one most easily misread as fact: every host needing a store
mTLS certificate is where this is going, while today only the store's own
host auto-mints and swarm-bao.nix calls it the credential an operator
places by hand everywhere else.

The progressive-enhancement rule is stated as a table of questions a
reviewer applies to a pull request rather than as prose, since a rule
nobody can check is a preference. New functionality matches the target
immediately; existing functionality moves stepwise, and the questions
distinguish a step from churn.

Indexed from the docs root and the swarm README. It supersedes
secrets.md when the migration completes — at which point that file is
deleted and this one moves into its place.
2026-09-16 22:41:23 +02:00
atlas
c3f5479ca8 agent-modules/network: accept unicast DHCP renewal replies unconditionally
A unicast DHCP renewal reply currently reaches dhcpcd only by matching
the firewall's ESTABLISHED,RELATED conntrack rule against the outbound
request. When that conntrack entry has already expired the reply is
dropped silently, with no log line anywhere. The client's broadcast
paths (DISCOVER, rebind) bypass netfilter entirely via a raw BPF
socket and never depend on this state — only the unicast renewal path
does.

This removes that dependency by accepting DHCP client traffic
unconditionally, gated on the firewall being enabled at all. It does
not identify or claim to fix the cause of any particular observed
renewal failure.

Refs #3389
2026-09-16 19:54:41 +02:00
atlas
5be4210f51 check-attribution-trailers.sh: fix broken remote-membership test
The origin-membership check used `case " $remotes " in *" origin "*)`,
padding $remotes (git remote's newline-separated output) with spaces.
That only puts literal spaces around the first and last entries, so with
more than one remote the case never matches, and the loop's
`[ "$r" = "origin" ] && continue` then discards origin permanently.
Result: a tree with origin plus any second remote hard-failed even
though origin resolved fine (argus's repro: aaa-dummy + origin).

Replace with a real membership test: grep -qx over one entry per line.
2026-09-16 19:47:37 +02:00
atlas
d726682be4 check-attribution-trailers.sh: resolve base ref regardless of remote name
Hardcoding origin/main broke in any checkout whose remote isn't named
origin (e.g. an agent worktree, whose remote is renamed to forge). Try
BASE_REF if set, else every configured remote in a stable order
(origin first to match CI, then the rest alphabetically), fetching each
remote's copy of the base branch if not already present. If nothing
resolves, exit 1 with a clear message rather than falling back to any
guessed range - an unresolvable base must never read as a clean pass.

Refs #4449
2026-09-16 19:47:37 +02:00
atlas
2cb7b5505b scripts: stop collapsing a failed lint scan into a clean pass
check-attribution-trailers.sh used `|| true` on `git log`'s exit status,
so a hard failure (bad range, unborn HEAD) and an empty-but-successful
range were indistinguishable — both fell through to the same
`-z "$commits"` exit-0 path. Capture the status via the `if` guard
(exempt from set -e on purpose) and exit 1 on a real git log failure.

check-issue-refs.sh piped `git ls-files | xargs grep | grep -v
lint:allow`, then swallowed the final exit code with `|| true`. Worse:
xargs itself collapses grep's exit 1 (no match) and exit 2+ (real error,
e.g. an unreadable file) into the same xargs(1) status (123 either way),
so even capturing that status can't tell them apart. Switched to `git
grep`, which runs once over the tracked set and hands back its own exit
status untouched (0 matched / 1 no match / 2+ error) — then branch on
that status explicitly for both the scan and the lint:allow filter step.

Refs #4439, #4442
2026-09-16 18:20:26 +02:00
atlas
dc0924627a Make three fail-open lints fail loudly
check-attribution-trailers.sh: the origin/main fallback now fails
loudly if it cannot fetch/resolve origin/main, instead of silently
substituting HEAD~10 as the diff base.

check-comment-blocks.sh and check-issue-refs.sh: assert the tracked
file-list search matched at least one file before treating an empty
hit-set as clean, mirroring the existing assertion in
check-doc-refs.sh.

Refs 4442, fixes 4439
2026-09-16 18:03:00 +02:00
atlas
72bff26336 fix tracker refs flagged by check-issue-refs.sh
Reworded the two hits scripts/check-issue-refs.sh found — a bare hash-
number tag in .forgejo/workflows/ci.yml's push-trigger comment and a
hyperhive#4345 tag in matrix_account.rs's doc comment — into prose that
stands on its own, per the lint's own rule. Neither carried semantic
weight beyond what the prose already says once reworded.

Refs #4345
2026-09-16 14:53:44 +02:00
atlas
87af0f38d3 swarm-controller: make the homeserver default fn pure, fixing test race
homeserver_or_configured_default read DEFAULT_HOMESERVER_ENV internally,
so its three unit tests raced each other by set_var/remove_var-ing the
same process env var with no synchronization under cargo test's default
parallelism (argus, PR #4443 review).

Take the default as a plain parameter instead of reading the env var
inside the function. The one env read moves to a new
configured_default_homeserver() helper, called once at the edge
(main.rs's startup diagnostic); homeserver_or_configured_default itself
is now pure and its tests need no env mutation at all.

Refs #4345
2026-09-16 14:51:30 +02:00
atlas
24ca14fd4a ci.yml: note that CI deliberately does not run on main
Refs #4345
2026-09-16 14:45:07 +02:00
atlas
c894192e4f swarm-controller: add configured default matrix homeserver URL
Adds services.hyperhive.deploy.swarm-controller.matrixHomeserverUrl,
threaded to the daemon as SWARM_CONTROLLER_MATRIX_HOMESERVER_URL, and a
Rust helper (homeserver_or_configured_default) that lets a caller-supplied
homeserver keep overriding it. Config plumbing only: put_matrix_account
does not call the helper yet, so this is a no-op for every current caller.

Refs #4345
2026-09-16 14:43:13 +02:00
atlas
f4cabd117b ci: trim shellcheck step comment, fail loudly on empty discovery
Move the SC2016 / -S warning rationale to the PR body — a decision
justification, not something that needs to live inline forever. Keep
only the one line that explains the non-obvious part of the code
itself: shebang-based discovery over a *.sh glob.

Also make the no-files case explicit: an empty match previously fell
through to shellcheck with no arguments (a confusing usage error, but
still non-zero); now it prints a clear message and exits 1.
2026-09-16 00:09:20 +02:00
atlas
642a5f7d96 ci: add shellcheck step for raw shell scripts
Discovers files by shebang under scripts/ rather than a *.sh glob, so
extensionless scripts (scripts/pre-push) and any future additions are
covered without editing the workflow. Runs at -S warning: the repo's
one existing finding (SC2016 on an intentionally single-quoted awk
program) is info-level and correct as written, so it's excluded by
severity rather than silenced with a disable comment.

Refs #4429
2026-09-16 00:09:20 +02:00
atlas
91320fdd80 ops: remove --self-test from attribution-trailer lint
Operator ruling on PR #4433: the self-test doubled the script size
without adding value. Drops the --self-test mode + fixtures from
check-attribution-trailers.sh and the CI self-test step that invoked
it. The anchored trailer regex and the real BASE..HEAD scan are
unchanged.

Refs #4432
2026-09-16 00:07:02 +02:00
atlas
ed781b990e ops: run attribution-trailer self-test in CI
Self-test ran only when invoked by hand; wire it into the same job
as the range scan so a broken checker fails the build. Self-test
runs first so a broken checker doesn't get masked by the range scan.

Refs #4432
2026-09-16 00:07:02 +02:00
atlas
9e618b3b90 ops: anchor attribution trailer lint to actual git trailer lines
The previous regex matched any line containing both 'co-authored-by'
and 'claude'/'anthropic' anywhere on the line, which flagged prose
*about* the trailer, not just the trailer itself. This caught the
commit that introduced the lint (subject: "ops: add CI lint for
Co-Authored-By/Claude/Anthropic trailers") as a false positive.

Anchor the match to `^[[:space:]]*co-authored-by:` so it only fires
on an actual trailer line (Key: value at line start), not a mention
in a commit subject or body. Add a --self-test mode with fixtures
covering three genuine-trailer spellings, an ordinary message, and
the PR's own subject line as the prose-mention regression case.

Refs #4432
2026-09-16 00:07:02 +02:00
atlas
f45cecf08c ops: address argus review findings on PR #4433
Add attribution-trailer lint row to the ci.md checks table (modeled
on the vale-errors row: not yet a required check) so the doc doesn't
assert stale completeness once attribution-trailers merges. Delete
the dead CI_COMMIT_REF_NAME elif in check-attribution-trailers.sh —
byte-identical to the else arm, never exercised, and its comment
overclaimed GitLab/Forgejo support nothing here can test.

Refs #4432
2026-09-16 00:07:02 +02:00
atlas
5bd68f9b8a ops: reduce comment density in PR #4433
Cut commentary from 104 to 24 lines across the new lint script, CI
config, and pre-push hook. Kept only: policy reference, why pattern is
loose, why scope is PR commits not history, and non-obvious CI behavior
notes. No logic lines changed.

Refs #4432
2026-09-16 00:07:02 +02:00
atlas
bf8e309b19 ops: add CI lint for Co-Authored-By/Claude/Anthropic trailers
Three builder runs tonight carried a Co-Authored-By: Claude trailer
despite bold instructions forbidding it. A rule in a prompt is not a
control — this gate is. Flags commits in a PR's range carrying
co-authored-by + (claude OR anthropic OR noreply@anthropic.com),
case-insensitive, covering all observed spellings.

Wired into CI (new attribution-trailer job) and pre-push hook. Mirrors
existing lint scripts in structure and exit conventions. Pattern tested
against all three variants plus a normal commit.
2026-09-16 00:07:02 +02:00
atlas
5a9118c945 subagent: drop private-item rustdoc links to check_model
CI's docs-rustdoc check (-D rustdoc::private-intra-doc-links) failed:
start's and continue_'s public # Errors sections linked to check_model
with [`check_model`], but check_model is (correctly) private. Drop the
bracketed link and keep the plain backticked reference — the sentence
still names the check, it just stops trying to hyperlink a private item
from public docs.

Refs #4436
2026-09-15 23:44:14 +02:00
atlas
93faa944e6 subagent: trim continue_'s doc comment under the 30-line cap
The Errors-section addition in 46a6317 pushed continue_'s doc comment
to 31 lines. Drop the parenthetical on the already-running case — the
Errors section states it already.

Refs #4436
2026-09-15 23:30:38 +02:00
atlas
46a6317f13 subagent: gate the daemon's model against availableModels
The subagent daemon put `model` straight onto claude's argv with no
validation, so an agent could spawn nested sessions on any model the
operator had deliberately kept off its harness. Forward the existing
`hyperhive.availableModels` onto the daemon unit as
HIVE_AVAILABLE_MODELS (same rail `HIVE_TOOL_GROUPS` uses) and check
`start`/`continue` against it before building the config.

Default open: an absent var restricts nothing, so an agent deployed
before this keeps working. An omitted `model` is always allowed — it
lets claude pick its own default rather than naming one.

Refs #4436
2026-09-15 23:27:02 +02:00
atlas
f4b7a1e357 Derive queue path from agent_client_path in render_agent_with_queue
Make render_agent_with_queue derive the queue credential path from
agent_client_path() rather than re-interpolating Kind::Hive and the
queue/agent segment. This enforces path agreement through code reuse
instead of two hand-written spellings that can diverge.

The policy stanza needs the policy form (secret/data/...) while
agent_client_path returns the client form (swarm/hives/...). Bridge
this by wrapping agent_client_path's result with {MOUNT}/data/, the
same mechanism render_agent uses for agent paths.

Update tests to derive expected paths from agent_client_path instead
of hard-coding them, so a future divergence fails the test rather
than passing silently.

Refs #4386
2026-09-15 23:03:58 +02:00
atlas
68cf67423f Add render_agent_with_queue policy renderer
Extends the agent policy renderer with a variant that grants read on both
the agent's own namespace (agents/<agent>/*) and the hive's shared queue
credential (hives/<hive>/queue/agent).

The queue credential is hive-shared rather than per-agent, so a policy
scoped strictly to agents/<agent>/* cannot read it. This renderer makes
the existing queue credential (already handed to every agent container on
the hive) reachable through the agent's own Vault token.

Tests cover the happy path (both grants render), traversal validation for
both agent and hive parameters, and read-only enforcement.

Refs #4386
2026-09-15 23:03:58 +02:00
atlas
c406ffb0f9 swarm-bao: enable approle auth method in bootstrap
Adds approle to the auth methods enabled during swarm-bao bootstrap,
using the same idempotent check pattern as cert. No role or policy
is configured yet — those are handled in later slices.

Refs #4386
2026-09-15 22:02:41 +02:00