A hive reads its own declaration today and that scopes cleanly: DIRECT.GET
carries the key in the subject, so the grant can name it. A *watch* cannot be
scoped that way — a consumer's filter travels in the request payload, so
$JS.API.CONSUMER.CREATE.<stream> grants the whole stream. With every hive in
one bucket, letting a hive watch its own declaration would let it read every
other hive's.
One bucket per hive (hive-wanted-<hive>) makes the stream a hive may hold
exactly as wide as what it is allowed to see, which is what #4006's live-watch
needs. That watch is a separate change; this only moves the boundary.
mara's calls, both on #4006: one stream per hive rather than teaching the auth
responder a hive roster, and a wildcard for the controller — "its okay if
swarm controller can theoretically override hive". A bucket name is a single
subject token with no prefix matching, so no wildcard narrower than * covers N
per-hive buckets; the controller's grant is account-wide by consequence, and
documented as chosen rather than left to look accidental.
The reader arm of #4005's key-layout guard asserted the opposite of that
ruling, so it is replaced rather than deleted: the hive arm survives as
no_hive_may_write_another_role_s_agent_status (with a positive control), and
the_readers_grant_is_deliberately_account_wide pins the decision and names the
ruling, so the width reads as chosen to whoever finds it next.
Two pre-existing negative assertions were silently defanged by the rename --
they matched hive-wanted.beta and $KV.hive-wanted.alpha, strings nothing
produces any more, and kept passing. Both now match current names.
swarm-controller resolves the store per hive per call instead of caching one in
a OnceCell: there is no single handle that serves N buckets, and declarations
change on operator action rather than per tick.
CHECKPOINT, NOT A PROPOSAL. mara paused the plan ("i dont quite understand
the plan") before any behaviour-changing edit; this commit exists so the work
survives a container stop, not because it is ready.
wanted.rs: BUCKET -> BUCKET_PREFIX + bucket(hive) producing hive-wanted-<hive>;
open_or_create/open_read_only take the hive. lib.rs: Error::CreateBucket.bucket
becomes String, since a per-hive name is built at runtime.
Nothing else is touched, so no caller compiles against the new signatures yet
and no deployed behaviour changes. Remaining, if she approves: the two other
CreateBucket sites, swarm-controller's OnceCell (one store -> N), hive-c0re's
pull, policy.rs per-hive streams + the reader roster, swarm-nats.nix.
argus's #3989 review caught it: a <details><summary> line isn't
markdown-processed, so backticks in it render as literal characters
rather than styled code. Worth banking now, before it gets rediscovered
per-file across the other ~19 docs #3902 still has to touch.
mara: 'separate technical status from agent provided status string' —
the agents page's status column concatenated the freshness label with
the agent's own free-text status_text into one Badge pill, meant for a
short discrete label, not a full sentence. Long status strings blew
the row out and made the table look messy.
Now two columns: 'status' is just the freshness badge (fresh/stale/
never reported/not in swarm identity) plus relative time, unchanged in
meaning; 'message' is the agent's own status string as plain prose in
its own capped-width, wrapping cell (Table's new cellClass, ui-table-
prose), not a badge. A stopped agent (status_text always null per the
wire contract) shows an em dash there instead of a stale message.
Verified against a static render of the built CSS (real dark theme,
real Badge/table classes, the exact long strings from the screenshot
mara attached) — wraps cleanly within the column instead of
overflowing. Typecheck + build clean.
hyperhive#4008, mara: 'add a setting to not display verbose output...
like the grey colored debug stuff.'
Same shape as the existing expand-tool-output preference
(ExpandDetailsSetting.tsx): a new shared/src/prefs.ts key pair
(getHideDebugPref/setHideDebugPref), a new settings-menu-row component
owning its own state (HideDebugSetting.tsx, not a prop threaded through
the shared SettingsMenu component — per mara's earlier review on the
first one, more per-page options as props there is how that component
accumulates cruft), mounted next to ExpandDetailsSetting in Root.tsx.
Row.tsx skips (returns null for, not CSS display:none) any TermMsg
whose level is 'debug' when the pref is set — matches the muted 'debug'
row this issue is about (see docs/web-ui/terminal-rendering.md's Levels
table). Read live per-row, same as expand-details, so toggling applies
to newly streamed rows in an already-open tab without a reload; already
-rendered rows are unaffected either way, same non-retroactive
precedent the existing preference already sets.
hyperhive#3902, mara: option (a) - a content pass splitting mixed docs
into operator-facing content plus implementation detail. One file
first, to agree on the split pattern before doing the other ~19.
Went through 3 shapes on review before landing here: a sibling
-internals.md file (mara: clutters the navigation), then two
tree-precedent alternatives damocles raised (subdir+README like
web-ui/; or only split docs with a pre-existing boundary marker,
which would've covered 3-4 of the ~20 flagged docs and left the rest
untouched), then mara's own proposal - a collapsed <details> section
in the same file. Verified empirically (cmark-gfm --unsafe, the
website's own render pipeline) that markdown headings nested inside a
<details> block still parse as real headings with heading-id anchors
once separated from <summary> by a blank line, so anchor links into
the collapsed section keep working.
github.md keeps enabling/provisioning/security up top; its
'Implementation' section is now a <details> block holding what was
briefly a separate github-internals.md (deleted again) - how the
agent's gh/git-push actually authenticate, and the notification
poller's internals. Reverted the two cross-references + the
docs/README.md entry back to pointing at github.md now that the
content lives there again.
Added a short CLAUDE.md note recording the pattern per mara's ask,
including the one real caveat damocles flagged: <details> only
collapses in a rendered browser, a raw-text read (cat, the Read tool)
still sees everything, same as today.
Second round for the flat-alias deprecation. `pr-commits` and
`pr-assign-reviewer` are back-compat aliases with an exact scoped
equivalent, and they were missed by all three buckets of the original
accounting -- which was wrong in both terms: `reopen`/`dependency`/
`reaction` appear only in the doc-comment prose listing the scoped
surface and are not Verb variants at all, so the real count is 17
(7 + 2 + 7 + 1), not 18.
The test for "pure rename" is that an exact scoped form exists AND the
verb is PR-only, so a caller holding just a number can be sent somewhere
unambiguous. Verified rather than assumed: `pr commits` and
`pr assign-reviewer` exit 0, while `issue commits` and
`issue assign-reviewer` exit 2 -- the same code a deliberately bogus
verb returns, which is what makes those 2s mean "no such verb".
That is also why the seven kind-agnostic aliases are untouched here:
each exists as both `issue <verb>` and `pr <verb>`, so refusing them
would remove a capability rather than rename one.
Both run functions are still called from pr_cmd.rs, so nothing becomes
dead code. The enum's block comment enumerates the pure renames by name
and is extended to match -- left alone it would be a stale claim naming
seven of nine.
An agent whose freshness is "unknown" is a distinct case, not a
fallback: it reported into the KV bucket but isn't in the swarm-identity
roster. The label used to just reuse the enum name ("unknown"), which
doesn't tell an operator what to do about it. Give it a label that
names the actual situation ("not in swarm identity") plus a tooltip
spelling out the fix (register/migrate it), so an operator scanning the
roster during a migration can immediately tell which agents still need
work.
No backend change needed — GET /api/agents/status already emits a row
for every hive-reported agent outside the roster (AgentStatusReader,
unit-tested as an_agent_outside_the_roster_is_surfaced_as_unknown), and
the page already renders every row it gets back.
A KV entry publishes to $KV.<bucket>.<key> and NATS wildcards match whole
.-delimited tokens, so a /-joined {hive}/{agent} key is a single token: the
only expressible write grants are one exact subject per agent (needs a roster
in the auth responder, which Policy::hive_name argues against) or a bucket-wide
wildcard that lets any hive overwrite any other hive's agents.
Joining with a dot puts the hive in its own token, so hive_subjects can grant
$KV.agent-status.<hive>.* — every agent of one hive and nothing else, the same
containment hive-status already has.
The grant lands with the bucket-open pair (STREAM.INFO + STREAM.CREATE):
open_or_create resolves the bucket before it writes, so alone the publish
subject is unreachable and the sweep fails one step later instead.
Verified the client accepts a dotted key rather than assuming it: async-nats
0.50.0 VALID_KEY_RE is \A[-/_=.a-zA-Z0-9]+\z and is_valid_key rejects only
empty / leading / trailing dot; the subject is prefix + key verbatim.
hyperhive#3896. The backend for start/stop (Up/Offline wanted-state
declarations) already existed and was merged (#3905's writer, the
PUT /api/hives/{hive}/agents/{agent}/state route) — nothing here was
waiting on Paused/Destroyed, which I'd mistakenly conflated with this
issue in an earlier comment (that's #3803, a different feature).
swarm-controller: merges each row's declared wanted state into
GET /api/agents/status, same shape as the config_pr merge (one read
per distinct hive, not per agent, since a declaration is a hive's
whole agent map).
swarm-ui: AgentsPage gets a "wanted" column — clicking the current-
state badge toggles it (Badge's own chip-plus-control shape, same as
its own header comment's pause/resume example), backed by the PUT
route above. A row with no declaration yet reads its implied current
state off the agent's own last-reported running flag. Stop asks for
confirmation (native window.confirm — no confirm-dialog component
exists in swarm-ui yet); start doesn't.
The swarm-controller had no grant for the per-agent status bucket, so
resolving it failed and the dashboard answered 503.
Grants the reader the same five subjects the hive-status bucket already
has, for the same measured reasons -- including BOTH `CONSUMER.CREATE`
forms. `keys()` builds an ephemeral consumer whose create subject carries
no consumer name, and `>` matches one or more tokens, never zero, so the
`.>` form alone does not cover it; that omission surfaces as a client
timeout and an operator-visible 503, which is the shape reported here.
Deliberately grants NO `$KV.agent-status.*` subject, to anyone. That is
the write side, and writing is what picks a key layout -- whether agent
status is published by the agent or forwarded by its hive, and under
which key, is still open. A write grant here would answer that by
implication. Reading needs none of it: a KV read is a DIRECT.GET, and the
`.>` form is bucket-wide rather than per-key, so nothing added here
encodes a layout. A test asserts that, for the reader and for a hive,
with a control proving the same matcher fires on a bucket that does have
write grants.
The stream name is derived from the crate constant, like the wanted-state
bucket beside it, so writer and reader cannot disagree about it.
dep_meta's comment records a measured 404: leaving owner/repo as None
made every same-repo dependency fail, because forgejo resolves the
dependency's repo from the request BODY rather than the URL. Nothing
stopped a later tidy-up from simplifying them back to None.
index() promises "error instead of wrapping". A wrap would not fail
loudly -- it would hand the forge a negative index it looks up as some
other issue.
Rust's runtime sets SIGPIPE to SIG_IGN at startup, so writing to a pipe
whose reader has gone away returns EPIPE and println! panics. `hive-forge
<verb> | head` printed a panic and exited 101 where cat, grep and every
other pipeline member exit quietly.
101 is not cosmetic: to a caller running `set -o pipefail` it is a real
failure, so a shell script that pipes our output stops on a condition
that is not an error.
Restore SIG_DFL first thing in main, before any output. Measured against
the same command: piped to head, 141 (killed by the signal) with empty
stderr; unpiped, 0; the pre-change binary, 101 with a panic.
Closes#3972
Seven hidden aliases are pure renames — `pr status` does exactly what
`pr-status` did. They now refuse and name the replacement instead of
running, which is the deprecation step before removing them outright.
Deliberately not all of the hidden verbs. The other ten (`view`,
`comment`, `comments`, `close`, `reopen`, `assign`, `labels`, `timeline`,
`dependency`, `reaction`) exist as BOTH `issue <verb>` and `pr <verb>`,
so a caller holding only a number cannot be told which to run. Refusing
those removes a capability rather than renaming one, and there is no
message that would help; they keep working pending a decision on what
replaces them.
Measured: each of the seven exits 1 naming its replacement; `view` still
exits 0; `pr status` still returns its merge-readiness verdict (0 on a
ready PR, 1 on one with CI pending).
Refs #3974
Filtering the gateway's journal by severity returned nothing while nginx
was logging [error] continuously. Measured against nginx.service:
`-p warning` and `-p err` over 24h both said "No entries", while the
unfiltered query returned a continuous stream of [error] lines.
The failure is inverted rather than merely missing. A severity filter is
how you ask "is anything wrong", and here it answered "no" *because* the
errors were misfiled -- one step from recording "gateway clean, no
warnings in 24h".
Cause: the module default is `error_log stderr`, and systemd files that
whole stream at one priority regardless of what each line says. Routing
to syslog preserves per-line severity, so [error] lands as err and every
existing severity filter starts working without changing a consumer.
Uses the module's own `logError` option rather than appendConfig:
nixpkgs emits `error_log ${cfg.logError};` at exactly one site, in the
main context, so the option guarantees the placement. Transport only --
no level suffix, so nginx keeps its default `error` threshold and this
changes where lines are filed, not which lines exist.
Gated with `nginx -t` on the flake-locked nginx against the exact string
shipped here, with a deliberately invalid syslog target as the control
that the test can fail on this directive. gixy (what the module's own
validateConfigFile actually runs) reports no issues.
What that does NOT prove: that journald files the lines at the right
severity. `nginx -t` is a parse check; only a deploy shows the effect.
An option name that is absent reads exactly like one that is present —
the reader has no way to tell, and the failure surfaces as a nixos eval
error at deploy time. Two namespace reorgs the prose never followed:
`forge.*` moved under `swarm.`, and several flat paths under `c0re.`.
docs/networking/gateway.md:335 services.hyperhive.forge = { … }
docs/networking/gateway.md:367 forge.rootUrl → swarm.forge.rootUrl
docs/networking/gateway.md:389 frontend → c0re.frontend
docs/tools/hivectl.md:193 agentCpuQuota → c0re.agentCpuQuota
docs/tools/hivectl.md:317 forge.behindGateway → swarm.forge.behindGateway
docs/agent-lifecycle/persistence.md:404 agentCpuQuota → c0re.agentCpuQuota
docs/web-ui/dashboard.md:610 modelPrices → c0re.modelPrices
docs/web-ui/dashboard.md:789 forge.publicUrl → swarm.forge.publicUrl
Both `gateway.md` blocks are fenced nix an operator is meant to paste.
Two sites needed more than a rename:
dashboard.md:781 cited `gateway.enable` as a live opt-in. It was
removed; `gateway.md:269` already says the gateway is unconditional,
and `hive-c0re/environment.nix:208` sets HIVE_GATEWAY_ENABLED = "1"
with no condition. Rewritten to say so, keeping the flag-absent
fallback the dashboard still implements.
turn-loop/config.md:4 attributed the page's knobs to
`services.hyperhive.agents.<name>`. No such option exists — every
knob the page documents is agent-side `hyperhive.*`, set in the
agent's own agent.nix.
Measured against the module system rather than a grep: `nix build
.#docs` renders nixosOptionsDoc over the evaluated trees (279 option
paths). That arm alone is wrong — it omits `internal = true` options
and called `gateway.localNames` and `swarm.peerHives` dead, so a second
arm requires the literal path to appear in nix/ and rescues exactly
those. Four remaining unresolved citations are correct prose saying
`gateway.enable` / `network.{enable,isolateContainers,upstreamDns}`
were removed, and are left alone.
Closes#3948
The runs-list query's `ref` filter matches only a fully-qualified ref,
while the listing prints `prettyref` (`main`, `#3967`). So no value a
caller can read off the output was accepted by the filter, and the
rejection rendered as "no matching runs" — indistinguishable from a
branch that has never been built.
Qualify the value before it reaches the query: `refs/...` passes
through, `#N`/`N` becomes the pull head ref, anything else becomes a
branch. A slash cannot mark an already-qualified ref, since branch
names contain them.
Not shared with ci-rerun: its `ref` is a workflow_dispatch body field
taking a bare branch name, so the same normalisation would break it.
Closes#3970
docs/tools/swarmctl-cli.md is rendered by `swarmctl markdown-docs`, and
nix/checks.nix's swarmctl-docs-fresh check diffs the committed copy
against fresh binary output. Enabling prettier on markdown rewrote its
list bullets and footer indentation, which no regeneration can settle:
formatting it fails the freshness check, not formatting it fails
treefmt. .prettierignore already carried hivectl-cli.md for exactly
this reason; swarmctl's doc was added later and the entry was not.
Restore the file to its generated bytes, list it alongside hivectl's,
and state the invariant where the next CLI doc gets added.
`docs/scheduler/ci.md` told the reader to run `nix develop -c treefmt`.
treefmt is not on the devshell PATH — `nix develop -c which treefmt` exits
1 while `which cargo` resolves, so the check discriminates and the
documented command has never worked from that shell.
The same line enumerated the formatter set as "rustfmt + nixfmt + taplo",
which was already wrong before this branch: keep-sorted has been enabled
all along, and the commit before this adds prettier. Replaced the list
with a pointer to nix/treefmt.nix so it can't drift out of sync again.
Also drops "+ rustfmt" from the nix-flake-check row: rustfmt is one of
treefmt's formatters, not a separate thing that runs alongside it.
Pure `nix fmt` output from the commit before this one — no hand edits.
203 files: 52 md, 42 tsx, 32 js, 32 css, 21 ts, 13 html, 8 json, 3 mjs.
Reproduce with `nix develop -c nix fmt` on the parent commit; the result
should be byte-identical to this tree.
None of the 13 `.prettierignore` entries appears here — verified by
intersecting the changed-file list against the ignore file, with a
control proving the intersection finds a match when one exists.
`nix/treefmt.nix` gated nix, rust and toml and nothing else, so markdown,
css, js/ts/tsx, html and json had no formatting gate at all. Operator
asked for "the web stuff to be formatted as well" (#1997).
This commit is the one-line enable; the reformat it produces is the
commit after it, kept separate so this one stays reviewable.
Scope is treefmt-nix's `programs.prettier` default rather than a
hand-written `includes` list. The default is wider than the requested
md/css/js/ts/tsx/html/json — it also covers yaml, scss, vue, mdx, json5,
cjs/mjs/jsx — but measured against this tree that width costs exactly 3
extra files, all `frontend/packages/*/build.mjs`, which are web build
scripts. `.forgejo/workflows/{ci,coverage}.yml` are in the default set
and already conform, so they reformat to nothing today; from here on they
are gated too, which is what a repo formatter is for.
`.prettierignore` is honoured: prettier applies it even to the explicit
paths treefmt passes, so the `hivectl markdown-docs` generated file and
the 12 files awaiting manual list-item re-wrap stay untouched.
`age_seconds` is documented on the LooseEnd enum as saturating to zero on
any clock anomaly, but the derivation was in three places: hive-c0re had a
named `saturating_age` helper with tests, and the in-agent socket server
hand-rolled the same two lines twice, untested.
Move the helper to hive-sh4re::inbox, beside the enum whose contract it
implements and inside the one crate both producers already depend on. Its
three tests move with it (not dropped) and gain two arms: the whole-i64
range, where the saturating_sub is what stops the subtraction overflowing,
and a far-past control so those zeros are the clamp firing rather than the
function bottoming out on large inputs.
The two clamps are not redundant, which is what `to_loose_end`'s doc got
wrong: it credited "saturating" for the zero, but saturating_sub bottoms
out at i64::MIN, still negative. The try_from is what yields 0.
Also cover the two projections themselves, which is the part the shared
helper cannot: that a reminder ages from created_at rather than due_at,
and a todo from updated_at, with a future timestamp reading 0 through
both and a past-timestamp control on each.
`nix/templates/harness/` is cited from `send_allow.rs` and twice from
`mcp_config.rs`, in the present tense, as where the NixOS module writes
the send allow-list, writes the extra-MCP spec, and defines the MCP http
port default. There is no such directory — `nix/templates/` holds only
`agent.nix` and `ruth.nix`.
All three describe things that live in `nix/agent-modules/mcp.nix`:
send-allow.json is written at :230, extra-mcp.json at :228, and the
`hyperhive.mcp.httpPort` option is declared at :143.
These are the mechanical half of #3923, which explicitly carved the
repointing out from the question of whether the reference gate should
widen. Nothing here changes that question.
Per mara's review call on this PR: "the view should be filled by a single
backend call." AgentsPage.tsx was doing three fetches (/api/agents,
/api/config-prs, /api/agents/status) and joining them client-side by name.
Moves the config-PR join server-side instead: AgentStatusRow gains a
config_pr field, populated by get_agents_status's handler from
AppState::config_prs after agent_status::AgentStatusReader::view() returns
- not inside that module, which has no forge client and stays that way (see
the field's doc comment for why the handler is the right layer for this
merge, not the reader).
AgentsPage.tsx now does exactly one fetch and no client-side joining at all
- the wire row is the table row. Dropped the separate AgentStatusRow TS
interface (folded into AgentRow, which now mirrors the backend type
field-for-field) and the /api/agents + /api/config-prs fetches entirely;
neither is needed once /api/agents/status already returns every roster
agent with its config PR attached.
ConfigPrStatus gained Deserialize (previously Serialize-only) since
AgentStatusRow derives both and a struct's derive requires every field to
support it.
Continues #3341 item 3, unblocked now that #3568/#3569 (items 1/2)
are merged and GET /api/agents/status is live.
Third fetch alongside the existing roster + config-PR ones, joined
client-side by name same as the config-PR merge. Adds a hive column
and a status column (freshness badge + status_text + relative-time,
same rendering AgentsPage's sibling HivesPage already uses for the
hive-level status endpoint).
Its doc justified the pub with "so socket_server and socket_server can
populate AgentMeta" — the same module named twice, and both of those call
read_agent_status_live instead. The bare function has exactly one caller,
the wrapper eleven lines below it. container_view is not a pub module and
this is a binary crate, so the pub only ever granted sibling access that
nothing took.
Also splits the parsing half of read_meta_locked_revs into
parse_locked_revs, which needed a flake.lock on disk to exercise, and
tests it: alias-to-rev mapping, a follows input (an array of path
segments, which is why that arm is a continue), a node with no rev, and
the malformed shapes that must yield an empty map rather than panic.
Coordinator::transient_snapshot builds a fresh HashMap on every call, so
each /api/state render iterates it under a different hash seed and the
dashboard's transient rows reorder while an operator watches an operation
run. Nothing downstream sorts them.
build_port_conflicts, three functions up the same file, already sorts both
its levels. This is the asymmetry, not a new convention.
Sorted by (name, kind, secs) so ties cannot drift either, plus five tests
on a builder that had none. The clamp one is the reason to bother: `since`
in the future yields a negative duration, and cast_unsigned would render
that as an age near u64::MAX rather than zero.
The pinned nixpkgs cannot evaluate prettier: its closure carries
pnpm-9.15.9, which is marked insecure for seven CVEs, and the refusal
happens at evaluation. That breaks nix fmt and the formatting flake check
for every language at once, not just prettier's, which is what blocks
wiring prettier into treefmt.
Measured against both pins, prettier.outPath only:
569d5785 refused, suggesting permittedInsecurePackages
5dfba623 /nix/store/7ryzvaaks7m71lilhzalcbay57nqqm78-prettier-3.8.3
Prettier is 3.8.3 on both. What moved is the pnpm in its closure, not the
version.
Landed on its own so a two-month pin bump gets its own CI run and its own
bisect point, separate from the treefmt change that needs it.
Two more statements of the claim the previous commit removed: the
snapshot field called the clusters "sub-agents", and WEB_PORT_BASE's own
doc called the range sub-agent-only while the function that reads it
says "every agent including the manager" three lines below.
docs/process/gotchas.md already had this right.
The doc comment said the manager sits at a fixed 8000 and therefore
cannot collide with a sub-agent. No such special case exists:
ContainerView.port is agent_web_port(name) for every container, and that
function hashes every name — including the manager's — into one range.
The only 8000s in the tree are test-fixture arguments in meta.rs.
The code was always right; the comment would send an operator hunting a
detector bug instead of renaming an agent. It now points at the allocator
that owns the rule, and the manager-collides case is a test rather than a
sentence.
Nine tests on a module that had none, covering the cluster grouping, the
name sort, the port ordering, and that a stopped container still claims
its port.
Mutation testing found this arm untested: classify_tool_result has its
OWN `<= 120` deciding icon-vs-body, separate from the one in
summarize_tool_result that picks the summary text. The existing case
used 2 and 500 characters, so neither arm went near the boundary and
moving it to 119 changed nothing.
Verified the new test fails under exactly that mutation.
clippy::format_collect (pedantic, denied) fires on
`(0..40).map(|i| format!(...)).collect::<String>()`. The test only needs
40 non-empty lines, not numbered ones, so `"line\n".repeat(40)` says the
same thing without the lint.
`r.find(':')` returns a BYTE offset; it was being passed to
`r.chars().take(colon.min(9))` as a CHARACTER budget. For a multi-byte
room-id local part the two disagree, and the extra characters come out
of the server half:
fmt_room("!ÄÖÜ:server") == "!ÄÖÜ:se" // want "!ÄÖÜ"
The doc comment above it already said the `chars().take()` was there to
handle non-ASCII, so the intent was recorded and the implementation was
half of it. Split on the colon first, then take 9 characters of the
local part.
ASCII behaviour is unchanged and pinned by the existing case:
`!abcdefghijkl:server` -> `!abcdefgh` before and after.
Found by the tests in the previous commit — written red, then fixed.
check-doc-refs.sh's relative arm resolved every link against the linking
file's directory. That is right for markdown and wrong for a .rs doc
comment: rustdoc emits the href verbatim onto a page under
target/doc/<crate>/<module...>/, so the link resolves against THAT
directory instead.
The consequence was a false pass, not a miss -- a link resolving from the
source dir got certified while rendering broken in the published docs.
Reproduced against main's script: a `](../../docs/README.md)` planted in
hive-forge/src/client.rs resolves from hive-forge/src/ and the old rule
exits 0.
Refused rather than resolved rustdoc-aware. The repo has zero relative
links in .rs doc comments (measured on 54daf9ac), so nothing needs the
second reader, and the backticked repo-root form the root arm already
gates renders correctly under both. Inferring module nesting would be
machinery in service of a form nothing uses.
Closes#3928
clippy::items_after_test_module (pedantic, denied) — a #[cfg(test)] mod
has to be the last item in its file. Pure move: same line count, same
line multiset, no content change.
`cargo test` was green while this was broken; only clippy saw it.
Pointing at a doc instead of duplicating its prose next to the code is what
the comment-block lint pushes people toward, and nothing checked that the
pointers still resolve. The reorg that prompted the preceding commit broke
41 references and every one of them merged green.
Two arms: repo-root-relative `docs/` paths, and relative markdown links
resolved against the linking file. Scope is every tracked file rather than a
type list — the dead references were in css, html and an ignore file as well
as in markdown and nix.
Three controls, because a link checker whose extractor quietly matches
nothing passes forever while checking nothing: each arm must have found
candidates, and at least one pointer must have resolved. Any of those failing
exits non-zero and says the script is broken rather than the tree.
Escape hatch is the `lint:allow` marker the tracker-tag lint already uses.
Its only current users are in reference-docs.nix, where a path naming a file
the derivation synthesizes into $out is correct precisely because the repo
does not have it.
Also runs in the pre-push hook alongside the other two.
`docs/` was reorganised into topic subdirectories and the references to it
were not moved with it. Thirteen distinct paths and three relative links no
longer resolved, spread across nix, css, html, js, markdown and
.prettierignore — a stale pointer is not a markdown problem, it is wherever
someone wrote a path down.
Each mapping resolved to exactly one target. `docs/matrix.md` was the sole
ambiguous basename: .prettierignore lists `docs/tools/matrix.md` separately
and that entry still resolves, so the stale one is the integrations doc.
The three relative links were each one `../` too deep — from `<crate>/src/`
two levels reach the repo root. `hive-agent/src/login.rs` already had the
correct form, in the same crate, at the same depth.
.prettierignore is repointed rather than dropped, though nothing in the tree
runs prettier: no treefmt entry, no CI job, no package script. Whether that
config should exist at all is a separate question from whether it names
files that do.
`validate_container_name`'s doc comment read "check it maps to a
hive-managed container". The body checked characters. Nothing in it
ever tested hive-managed-ness, so at the privsep boundary it asserted
a security property that was not there -- the kind of comment a future
reader relies on when deciding a name is already confined.
The confinement is real, it just lives elsewhere: all five callers
immediately apply `container_system_name`, so a caller naming
`hive-forge` reaches `h-hive-forge`, not the forge. That guarantee now
sits on `container_system_name`, which is the function that provides
it.
The `SIBLING_CONTAINERS` early-return in that validator was dead in
every path, not merely unused today: `validate_name_chars` accepts
`[a-z0-9-]`, so all three sibling names pass the fallthrough
identically -- both arms, same result. Infra containers reach the
lifecycle verbs through `ControlInfraContainer` and the
`InfraContainer` enum, which carries no free-form name at all, so no
sibling name reaches these functions. No test pinned the branch.
With the dead branch gone the function was character-for-character
`validate_agent_name`, so it is deleted and its five call sites use
that directly. `SIBLING_CONTAINERS` stays imported --
`validate_container_system_name` still needs it, and there the same
early-return IS load-bearing: without it an unprefixed sibling name
hits the bail.
No behaviour change. Gated: fmt, clippy -D warnings, 21 tests, rustdoc
under CI's docs-rustdoc lints (the two new intra-doc links resolve),
push-lints.
The collector bind-mounts /var/log/journal into its container and points
its journald receiver at that directory. journald only writes there when
it stores persistently: with storage = "volatile" the journal lives in
/run/log/journal, and with "none" there is none at all. Nothing in the
tree sets or asserts services.journald.storage, so a host with either
value deploys a collector that cannot start — nixos-container refuses to
start when a bind source is missing, which is the same trap the
swarm-bao-certs and hostClientSecretDir comments document.
A bindMounts entry never creates its hostPath, and unlike every other
runtime-written bind source in the tree there is no unit to order the
container after: each of those declares before + requiredBy on its
producing unit, and this directory has no producer. journald was told to
store persistently or it does not exist. That leaves an assertion as the
only place to say so.
"auto" is deliberately accepted: it uses the directory when it exists,
and evaluation cannot see whether it does. Certainty is what separates
an assertion from a warning, and only "volatile" and "none" are certain.
Verified by reading config.assertions for all three values rather than
system.build.toplevel, which raises the first failing assertion in the
whole config and so reports a minimal stub's unrelated ones instead:
persistent -> mine [] others 3
volatile -> mine [the message, quoting "volatile"] others 3
none -> mine [the message, quoting "none"] others 3
The others count is identical across the three, so the storage value is
the only thing that differs between them, and a populated list is what
makes the empty one mean "this assertion passed".
Continues #3901 (dashboard round 1 was #3906, tabs.js/swarm.js).
Scanned the rest of packages/dashboard/src for the same 'moved to X'/
'now lives in Y' pattern: five more hits in schedules.js, common.js,
logs.js (two, one duplicating the other), core.js, and swarm.js.
All trimmed to state the current location as a fact rather than
narrating the move; one inline duplicate (logs.js's tab-default
comment restating the file-header pointer) dropped outright.
Follow-up to the same defect class as the `validate_container_name`
fix: prose on a privsep validator that does not match what the body
does. Found by enumerating all 33 fns in this file whose doc claims a
check or guarantee and reading body against doc. Three disagreed.
`validate_forge_admin_arg` overclaimed, which is the dangerous
direction. Its doc ended "Shell metacharacters are harmless since the
command is spawned directly (no shell), but we reject them
defensively" -- and no metacharacter is rejected anywhere: the body
takes null bytes, newlines and carriage returns only. The first clause
is true and is the actual safety argument, so it stays; the claim of a
defence that does not exist goes. The same doc also said "null bytes
and newlines" while the body rejects `\r` too.
`validate_bind_path` listed "no newlines, null bytes, or
double-quotes" and omitted the colon its body rejects -- the one that
matters most. The other three would corrupt the
`EXTRA_NSPAWN_FLAGS="..."` conf line; a colon corrupts nothing,
because `--bind=SRC:DST` is colon-separated, so a path carrying one
silently produces a different mount than the caller asked for. The
function's own bail! string already named colons.
Checked and left alone because they are accurate: `check_fd_agreement`
(both arms really do bail, so "in either direction" is earned),
`exec_forge_admin`, `validate_credential_name`.
Comments only, no behaviour change. Gated: fmt, clippy -D warnings, 21
tests, rustdoc under CI's docs-rustdoc lints, push-lints.
The doc comment on PROXY_HEADER_BLOCK attributed the variable to
`recommendedProxySettings = true`. It does not: nixpkgs' nginx module
emits `map $http_upgrade $connection_upgrade` into commonHttpConfig
outside that conditional, after four other independently-gated blocks.
The distinction is load-bearing rather than pedantic, because several
hyperhive vhosts set `recommendedProxySettings = false` per location
(hive-forge's auth_request endpoint, swarm-ui, swarm-victorialogs,
swarm-victoriametrics). Under the old comment, moving the per-agent
include near any of them reads as breaking the websocket headers, and
the fix someone would reach for is a second, redundant map in our own
http config.
Verified against the nixpkgs module rather than inferred: the
recommendedProxySettings block opens and closes ten lines before the
map, and `git grep connection_upgrade -- nix/` returns nothing, so
nothing on our side defines it either.
`docs/swarm/ca.md` said it under *Constraints on the material* and again
under *Distributing the root*; this module's header said it a third time,
directly above a line already pointing at that doc.
Measured: 6-gram intersection between the module's comments and the doc,
32 -> 6. The sentence now appears once in the tree.
Module header keeps what a reader editing this file cannot see from the
config: that one root makes cross-hive trust O(1), that the two
provisioning modes differ only in who writes the artifacts, and the ⚠️
that both root key and root cert are runtime files — the cert as a
consequence, and that one costs something real, since nothing whose trust
store is built at build time can name it. Everything behind those is a
`docs/x.md::Section` pointer with the heading checked to exist.
Dropped the O(n²)-per-peer-pinning story: the live reason is that one root
is O(1), and how it used to be configured is history.
In the doc, *Distributing the root* now links to *Constraints on the
material* rather than restating it. Intra-doc `#anchor` links are already
the convention there — 85 of them across `docs/`, 8 in `gateway.md` alone.
Comments + one doc paragraph; no behaviour change. Refs #3901.