Skip to content

🌱 sync: top up v5 with the v4.0.0/v4.0.1 release line - #5472

Merged
clubanderson merged 235 commits into
v5from
sync/v4.0.1-into-v5-topup-3
Sep 1, 2026
Merged

🌱 sync: top up v5 with the v4.0.0/v4.0.1 release line#5472
clubanderson merged 235 commits into
v5from
sync/v4.0.1-into-v5-topup-3

Conversation

@clubanderson

Copy link
Copy Markdown
Member

Merges tag v4.0.1 into v5 (232 commits β€” everything on the v4 line through the v4.0.0 and v4.0.1 releases), continuing the topup series (#5017).

Conflict resolutions

  • .github/workflows/docker.yml + src/scripts/publish-image-tags.sh: kept v5's release-channel ownership (v5 … edge); v4 owns stable+candidate.
  • .github/workflows/v2-tests.yml / go-security-analysis.yml: union β€” kept v5 in branch filters, took v4's expanded path filters (bin/, config/, Justfile, dashboard/openapi.json, NOTICE).
  • src/pkg/knowledge/docparser.go: both branches independently replaced AGPL go-docx with stdlib extraction; took v4.0.1's copy (identical + errcheck lint fix).
  • src/pkg/turn/{store.go,runner_test.go}: kept v5's deletions (RFC [v5] RFC: re-entrant conversation-as-state agent turn model (durable, handoff-able agents)Β #4002 restructure); v4's edits there were lint-only.
  • NOTICE: merged go.mod/go.sum are byte-identical to v4.0.1's, so took v4.0.1's NOTICE verbatim (satisfies the byte-for-byte drift gate).

Validation

  • go build ./..., go vet ./... clean
  • go test -short ./pkg/knowledge/... ./pkg/turn/... pass (conflict-touched packages)

clubanderson and others added 30 commits August 28, 2026 14:12
… not just drift (#5012)

The attribution answer said the notice-drift job "fails until a maintainer
commits the authoritative go-licenses output", implying that output existed and
only needed committing. It did not.

Its first real run against v4 failed BEFORE generating anything, on a defect
rather than the placeholder drift it was built to detect: LICENSE lives at the
repository root while the Go module is src/, so go-licenses searched upward,
stopped at the module root, reported every one of the project's own packages as
unlicensed, and exited non-zero. #5010 fixes it by excluding the project's own
module, which a third-party notice should not list anyway.

So this is two steps, not one, and the GTR now says which. The distinction
matters to a reviewer: "output generated, awaiting commit" and "generator
broken, no output" describe different degrees of readiness, and a reviewer who
follows the link finds the second.

This also illustrates something worth stating: an expected-to-fail check is a
bad place to hide a real failure. From a check list, "failing as designed" and
"failing for a reason nobody predicted" look identical, and the design comment
argues for reading it as the former. The only reason it surfaced was going
after the generated artifact rather than trusting the failure's stated meaning.

Swept the rest of the document for claims that went stale since it was written;
found none β€” the single-maintainer and badge references are already the
corrected ones.

Signed-off-by: Andy Anderson <andy@clubanderson.com>
… spoke UIs (#5013)

OIDC users (ibmid:/google:/microsoft:) rendered as opaque subject keys (ibmid:5500087VJB) in surfaces the earlier Manage Access fix did not cover. Resolve them to stored display names at serve time - presentation only, every key, filter and authorization check stays on the raw identity.

Hub: identityLabeler memoized resolver; Usage panel Label/OwnerName; My Hives OwnerName for Group-by-Owner; Timeline/access-log ActorName on served copies; Alert AckByName; access-faces +N tooltip uses display_label.

Spoke: /api/role display_name from AuthorizedUserNames; header chip shows display name with initials avatar and no fabricated github.com profile/avatar for provider keys; audit log user_name decorated at serve time with provider-aware avatar.

Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…on (#5016)

* πŸ”’ fix(knowledge): replace AGPL go-docx with stdlib docx text extraction

github.com/fumiama/go-docx is AGPL-3.0, classified FORBIDDEN by
go-licenses in this Apache-2.0, CNCF-incubating repo. It was a direct
dependency (src/go.mod) compiling into the shipped binary, and its
presence fails the notice-drift CI job outright since the NOTICE
generator aborts before writing anything when a forbidden license is
found.

The only usage was read-only text extraction in
src/pkg/knowledge/docparser.go: walk a .docx's paragraphs, then each
paragraph's runs, then each run's text nodes, and concatenate into
lines. A .docx is just a ZIP archive whose body text lives in
word/document.xml as WordprocessingML, so this is reimplemented with
only the standard library: archive/zip (already used elsewhere in
this repo, in pkg/dashboard/inception_handlers.go) opens the
container and locates word/document.xml, and encoding/xml decodes the
<w:body>/<w:p>/<w:r>/<w:t> shape (matched by local element name, so
the w: namespace prefix needs no special handling). No replacement
dependency was added.

Chunking, title derivation, and error handling are unchanged:
malformed input (corrupt zip, missing word/document.xml, unparseable
XML) returns nil, "" exactly as before, never panics. The zip entry
read is bounded since .docx uploads are attacker-influenced input.

docparser_test.go's in-memory docx builder is ported from go-docx to
raw archive/zip + WordprocessingML XML, keeping all existing
assertions. Added a focused round-trip test and a malformed-input
table covering a non-zip, a zip missing word/document.xml, and
corrupt XML.

go mod tidy removes github.com/fumiama/go-docx and its transitive
github.com/fumiama/imgsz from go.mod/go.sum.

Unblocks #5007 (committing the authoritative NOTICE) but does not
complete it.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>

* πŸ› fix(knowledge): drop duplicate xmlEscape from the docx test

The new docx test fixture builder declared its own xmlEscape, but the
knowledge package already has one (inception.go), so the package failed to
compile: "xmlEscape redeclared in this block". That broke golangci-lint and
test (rest 2/3).

The existing helper is a superset β€” it escapes &, <, > and additionally " β€”
so the fixture builder's call site keeps its behavior with the local copy
removed.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>

---------

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Adds opencode (anomalyco/opencode) to the contributor-relay backend
surface per #4970. opencode joins KNOWN_BACKENDS
(config/backends.conf) and config.CLIBackends
(src/pkg/config/config.go) so the shell/Go parity guard added in
#4987 covers it in both lists, with no exception entry needed.

opencode is dispatched through the relay's headless one-shot mode
(CONTRIBUTOR_MODE=headless, 'opencode run "<prompt>" --model
provider/model --auto') rather than the interactive tmux keystroke
path, since 'opencode run' is the CLI's natural non-interactive
entry point (option B in the issue). --auto is opencode's unattended
auto-approve flag; it has no OS sandbox of its own, so it is treated
as unconfined like goose/pi/bob.

opencode is intentionally NOT added to just contribute-k8s's
headless-pod allowlist: whether opencode auth login's credential
file (~/.local/share/opencode/auth.json) supports unattended use in
a fresh pod is unverified, so for now it runs headless only on a
host that has already signed in, the same posture agy uses.

This is relay-side only, matching the issue's scope. No changes to
src/pkg/agent/manager.go beyond what already derives generically
from config.CLIBackends.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
The gap summary listed NOTICE regeneration alongside genuine standing gaps
like absent SLOs and no third-party audit. It does not belong there: the
attribution mechanism is built and enforcing, and swapping the committed
NOTICE for the generator's verified output is routine maintenance the CI
job already carries.

Tracking it as a project-level gap also meant the entry needed rewriting
every time the state moved, and it had already gone stale -- still saying
the sole obstacle was a repo-layout path defect and that verified output
would follow once that fix landed. It landed, and the generator then found
a real AGPL-3.0 direct dependency shipping in the binary.

Drops the bullet, folds the AGPL finding into the attribution answer as
evidence the guard works rather than as an open problem, and fixes a
now-dangling cross-reference. Standing gaps (SLOs, compliance
certification, security-response rotation and diversity, third-party
audit) are untouched.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…le (#5019)

Every console PR carries permanently-red optional checks (Playwright
shards), so writeMergeEligible classified all of them as failing β€” 16
dependabot PRs accumulated in ci-failing.json where no sweep or agent
would ever merge them. Mirror the pending-but-mergeable rule: with an
operator-declared required-check set, a PR whose failing checks are all
non-required and which GitHub reports mergeable is eligible, not failing.
No set configured keeps the old fail-closed behavior; branch protection
still gates the actual merge.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
* πŸ› fix(notice): commit authoritative dependency licenses

Signed-off-by: Danathar <6772335+Danathar@users.noreply.github.com>

* πŸ› fix(notice): regenerate against the post-AGPL-removal module graph

NOTICE is a generated artifact, so rebasing this branch onto v4 carried the
old module graph with it: the file still listed github.com/fumiama/go-docx
and its transitive github.com/fumiama/imgsz, plus 18 lines of embedded AGPL
license text, even though #5016 removed both from src/go.mod.

Committing it in that state would have asserted the project ships AGPL-3.0
code it no longer ships -- the inverse of the UNVERIFIED placeholder problem
this PR set out to fix, and a worse error, since NOTICE is the file adopters
and reviewers are meant to rely on.

Regenerated with src/scripts/generate-notice.sh. Result: 55 packages, all
permissive (25 MIT, 16 Apache-2.0, 13 BSD-3-Clause, 1 BSD-2-Clause), zero
UNVERIFIED fields and zero unresolved license texts.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>

---------

Signed-off-by: Danathar <6772335+Danathar@users.noreply.github.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Co-authored-by: Andrew Anderson <andy@clubanderson.com>
…ference reroute (#5020)

When an agent's backend is an OpenAI-compatible inference gateway, hive
translated EVERY request the Claude CLI sent to api.anthropic.com (or to
the ANTHROPIC_BASE_URL translator) into a POST /v1/chat/completions,
never looking at method or path. Telemetry batches, error reports, and
POST /v1/messages/count_tokens have no `messages`, so each became
`{"messages": null}` and a gateway `400 Missing required parameter:
'messages'` counted against the provider rate limit (~2 per real
completion on a production hive).

Both handleInferenceRequest (MITM reroute) and inferenceTranslatorHandler
now forward only POST /v1/messages. count_tokens gets a local
`input_tokens` estimate using the same chars-per-token heuristic as the
max_tokens cap; anything under /api/ gets 200 {} (DEBUG-logged once per
path); any other path gets a 404 not_found_error in Anthropic shape with
a WARN naming method and path. Inference-routed claude sessions are also
launched with DISABLE_TELEMETRY=1, DISABLE_ERROR_REPORTING=1, and
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1; subscription sessions are
unchanged.

Tests: a shared method/path table exercised against both handlers with a
hit-counting fake backend; the non-messages cases fail on the parent.


Claude-Session: https://claude.ai/code/session_019Ukvc9PBjCzVJQ1ZSX5GH7

Signed-off-by: Gregory Hunt <greg@on-board.ai>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ontributor local path (#5024)

Advances #4918. #5011 closed the gap for claude/litellm only, via Claude
Code's native OS sandbox. This closes it for the rest of the backend
matrix on `just contribute-hive <backend> local`:

- copilot: wired to Copilot CLI's own OS-enforced --sandbox flag
  (Seatbelt on macOS, bubblewrap on Linux), gated on the installed CLI
  actually supporting it (copilot-cli >= 1.0.60) and falling back with a
  loud warning (or HIVE_COPILOT_DANGEROUSLY_BYPASS_SANDBOX=1) otherwise.
- opencode: gets a host-state command deny-list via its own real
  permission.bash config (survives --auto per opencode's own docs) --
  documented honestly as a floor, not a sandbox, since opencode has no
  OS-level filesystem boundary of its own.
- goose, agy, bob, pi, aider: verified against each CLI's own current
  documentation to have no sandbox, filesystem allowlist, or command
  deny mechanism at all. Local mode for these five now REFUSES to
  launch with a plain explanation of what's missing, unless the
  operator sets that backend's own
  HIVE_<BACKEND>_DANGEROUSLY_RUN_UNCONFINED=1 escape hatch -- matching
  the HIVE_CLAUDE_DANGEROUSLY_*/HIVE_CODEX_DANGEROUSLY_* naming
  convention already established. Each backend has its own env var so
  opting one in can never silently opt in another.

The local-mode launch banner now distinguishes three postures
(sandboxed / denylisted-only / unconfined) instead of two, so it never
calls opencode's command floor a "confinement" it does not have.

KNOWN_BACKENDS/CLIBackends, the backend-list parity guard, and the
existing claude sandbox/host-state-deny logic from #5011/#4938/#5001
are untouched.

Updates src/docs/sandbox-isolation.md and
src/docs/design/agent-host-confinement.md with the corrected
per-backend confinement matrix; both previously implied a two-way
split (confined claude/codex vs. unconfined everything else) that no
longer matches reality.

Remaining backends without any confinement (goose, agy, bob, pi,
aider) are an honest gap, not a silent one: local mode refuses to
launch for them by design, matching the issue's own guidance that a
refusal is better than a false claim of confinement.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…5021)

publish-image-tags.sh re-pushed stable+candidate+edge on every v4 merge,
so any deliberate promotion of the edge channel to the v5 line was
silently reverted minutes later by the next v4 build. Add an optional
CHANNELS argument (default preserves historical behavior) and have v4's
docker.yml claim only stable,candidate β€” the v5 branch's workflow owns
edge from here.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…on (#4912) (#5023)

* fix(dashboard): document the full dashboard API surface in openapi.json (#4912)

dashboard/openapi.json was hand-maintained and had drifted badly: it
documented 32 GET-only operations while the live Go dashboard server
(src/pkg/dashboard/) registers 300 distinct /api/* operations across
GET/POST/PUT/DELETE. Every write/action endpoint was undocumented.

The issue's '38 of 69' count came from diffing the spec against
dashboard/server.js, a legacy Node prototype that dashboard/README.md
already documents as not run in v2 production. Measured against the
actual registered routes in src/pkg/dashboard/*.go, the real gap was
269 missing operations plus one stale entry (GET /api/issue-costs,
no longer a real route).

Adds all 261 in-scope operations (269 minus 8 documented exceptions:
health/liveness probes, the legacy /api/v1/ GitHub-PAT catch-all, the
/api/contribute/ws WebSocket upgrade, the internal terminal-assertion
cookie renewal, and the /api/docs HTML page) with parameters, request
bodies, and response schemas derived from reading each handler.  Four
new tags (Contribute, Inception, Knowledge, Plan) were added.

Adds TestOpenAPISpecCoversEveryRegisteredRoute
(src/pkg/dashboard/openapi_route_parity_test.go), which parses every
s.mux.HandleFunc/s.mux.Handle registration with go/ast and fails if a
registered /api/* route and the spec diverge in either direction. The
exception set is a closed, documented list rather than a silent skip,
matching the shape of TestShellAndGoCLIBackendListsAgree in
src/pkg/config/backend_list_parity_test.go.

Fixes #4912

Signed-off-by: andan02 <andan02@gmail.com>

* πŸ› fix(dashboard): forward-declare resolveString so it can recurse

The route-parity test's resolveString was bound with :=, so the closure was
not in scope inside its own body. Its BinaryExpr case calls itself to fold
concatenated string constants, which failed to compile:

  undefined: resolveString

That broke golangci-lint and test (rest 2/3) -- the whole pkg/dashboard test
binary failed to build, so the new guard never ran.

Splits the declaration from the assignment, the standard Go idiom for a
recursive closure. No behavior change.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>

---------

Signed-off-by: andan02 <andan02@gmail.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Co-authored-by: andan02 <andan02@gmail.com>
…he last one (#5025)

* 🧹 lint: fix 121 of 122 staticcheck findings, gate stays off pending the last one

Advances the golangci-lint ratchet tracked in #4903 (staticcheck rung,
following #4906 ineffassign and #4990 unused). Runs
`golangci-lint run --enable-only=staticcheck` clean except one finding.

## What was fixed (121 findings)

- SA-class (real defects): a false-positive nil-guard confusion in
  FleetStatsCollector.Start (redundant re-check of an already-proven-non-nil
  receiver removed), a duplicate rune in a strings.Trim cutset in
  isVisualNoise (pkg/agent/manager.go), four dead self-assignments/unasserted
  test branches that were silently checking nothing, two idempotency
  assertions rewritten to actually compare two separate calls instead of a
  syntactic self-comparison, and one genuinely intentional empty critical
  section (a lock/unlock used as a deadlock probe) kept as-is with a
  targeted, justified //nolint.
- SA1019 (deprecated API): strings.Title replaced by a small in-package
  helper matching its exact word-boundary semantics (no new dependency on
  x/text); an always-unused `gh` import/suppression deleted; a single-call
  gh.Response/CertPool.Subjects site kept under a justified //nolint where
  the deprecated call is unavoidable; two watchdog.Enabled sites documented
  as intentional legacy-field migration/test-of-migration, not accidental
  use.
- SA1012 (nil Context): swapped to context.TODO() in ~20 test call sites,
  except the one test whose entire point is exercising Expand's documented
  nil-context fallback, which keeps its nil with a targeted //nolint.
- Everything else (QF/S/ST classes): behavior-preserving mechanical
  simplifications β€” De Morgan rewrites (verified with an exhaustive
  brute-force check for the most complex one), embedded-field selector
  simplification, tagged-switch conversions, strings.Replace(-1)γ€€to
  ReplaceAll, redundant type declarations, Sprintf-without-args removal,
  nil-check-redundant-with-len cleanup, and struct-literal-to-conversion
  where the field sets provably match.

## What was NOT fixed, and why the gate stays off

pkg/scheduler/held_pr_coordination_test.go:193 (ST1018, a raw U+200B
zero-width space inside a Go string literal) is inside
TestHeldPRCoordinationFailsClosedOnCriticalInjection, a security test
verifying fail-closed behavior on hidden-Unicode prompt injection. Per this
rung's explicit instruction to leave security tests untouched, that finding
is left as-is rather than "fixed" or suppressed. Because one finding
remains, `staticcheck` is NOT added to `src/.golangci.yml`'s enabled set in
this PR β€” the gate stays exactly as ineffassign/unused left it, so it is
never turned on red.

Remaining backlog after this: `errcheck` (~579 findings), the last rung
before #4903 can close.

All touched files gofmt'd. `go build ./...` and `go vet ./...` clean.
Full package tests for every touched package pass (pkg/agent's real-tmux
tests are individually verified β€” the full-suite run is flaky under bulk
parallel tmux spawning in this sandbox, reproduced identically on an
unmodified v4 checkout, unrelated to this change).

Advances #4903

Signed-off-by: Andrew Anderson <andy@clubanderson.com>

* 🧹 lint: enable staticcheck in the gate

The rung fixed 121 of 122 findings but left the gate off, because one
finding remained: ST1018, a raw U+200B zero-width space in a string literal
in pkg/scheduler/held_pr_coordination_test.go. Leaving it alone was the
right call -- it sits inside
TestHeldPRCoordinationFailsClosedOnCriticalInjection, whose whole point is
that a hidden-Unicode prompt-injection payload must make the scheduler fail
closed. Deleting the character would have silently gutted the test.

But ST1018 objects to the character being INVISIBLE IN SOURCE, not to its
presence. Writing it as the escape \u200b produces byte-identical string
contents -- the payload the scheduler sees is unchanged -- while making it
legible to a reviewer. The test still passes.

With that resolved there is no reason to hold the gate off, so staticcheck
joins govet, misspell, ineffassign and unused. errcheck (579) is the last
rung before #4903 closes.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>

* 🧹 lint: scope the staticcheck gate to the SA bug-detection family

Enabling staticcheck wholesale turned the gate red with 109 findings, but
none of them were bugs: 103 QF1012 (WriteString(Sprintf(...)) -> Fprintf)
and 6 SA1019 deprecations. The SA family -- the actual bug detector, for
misused stdlib, impossible conditions and ignored results -- is clean.

QF* and ST* are style families. QF1012's rewrite is behaviorally identical,
so enforcing it buys no correctness while costing a 103-site churn PR across
advisory/ and dashboard/. That is exactly the "gate so red it gets disabled
or merged around" failure this ratchet was designed to avoid.

SA1019 is excluded specifically rather than repo-wide: all six hits are in
tests, and two exist precisely to exercise a deprecated field's back-compat
path, where flagging the deprecation is noise.

The seven remaining SA hits the standalone tool reports are each already
carried by an explained //nolint:staticcheck (nil-context guards under test,
and one empty critical section that IS the assertion). golangci-lint honors
those; the standalone binary does not, which is why the two disagree.

Gate now enforces SA*,-SA1019 and passes.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>

---------

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…ons (#5030)

The README called hub-deployment.md "the v2 self-hosted hub deployment
guide" and said "All v2 runtime config lives in a single hive.yaml". Both
labels are holdovers from before the v2->v4 migration.

hub-deployment.md is version-agnostic -- it mentions neither v2 nor v4 --
so tagging it "v2" tells an operator evaluating a self-hosted hub that
they are reading retired documentation, and sends them looking for a
v4-specific guide that does not exist. Same for the config section: the
hive.yaml layout it describes is current, not v2-era.

#5027 reported the hub-deployment line; the config-section instance at
line 415 is the same defect and is fixed here too.

Legitimate v2 references are left alone: Compose v2 and cgroup v2 are
third-party version numbers, and the v1->v2 / v2->v4 migration links
correctly name the versions they migrate between.

Fixes #5027

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…ite (#5031)

development.md described src/test/ as coverage that "may exercise local
ports, temporary state, and helper processes" -- vague enough that a
contributor would assume `go test ./...` covers it. It does not. The suite
is behind a //go:build integration tag, so the documented command compiles
the package and runs nothing.

The trap is that this looks like success: `go test ./...` goes green having
executed none of these tests, and even with the tag set the suite skips
itself (exit 0) when HIVE_URL is unset or the endpoint does not answer a
TCP dial. A contributor touching inception code can believe they verified
it when they ran nothing at all.

Replaces the vague sentence with the build tag, the required HIVE_URL and
HIVE_TOKEN variables, the actual invocation, the skip semantics, and a
pointer to src/test/doc.go.

Every fact here is verified against source: the //go:build integration tag
in the test files, and the two env vars the suite reads.

Fixes #5029

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…5032)

getting-started.md is the primary operator onboarding doc and had zero
mentions of agent sandbox isolation, confinement, or the security
implications of running agents unconfined on a host. Add a section
ahead of L3 (where agents first get write access) that covers:

- Agents run in tmux sessions on the host by default
- The per-backend confinement matrix, matching #5024's merged state:
  claude/litellm and codex have native sandboxes, copilot has its own
  --sandbox, opencode is deny-list only (not a filesystem sandbox),
  and goose/agy/bob/pi/aider have no confinement and refuse to launch
  without an explicit per-backend DANGEROUSLY_RUN_UNCONFINED opt-in
- What the HIVE_<BACKEND>_DANGEROUSLY_RUN_UNCONFINED escape hatches mean
- The hub-side agent_sandbox two-gate requirement (global +
  per-agent), verified against AgentConfig.SandboxEnabled in
  src/pkg/config/config.go
- A link to sandbox-isolation.md for the full per-backend matrix and
  threat model

Fixes #5028

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
… image (#5034)

The contributor image installs claude-code with --ignore-scripts (kept,
deliberately β€” no arbitrary postinstall runs during the build), but that
also skips the package's install.cjs, which links the platform-native
binary into bin/. Every `just contribute-hive claude` (container mode)
then failed at the first launch with:

    Error: claude native binary not installed.

while local mode worked fine, because a host install runs its
postinstall normally. src/Dockerfile Layer 7 fixed exactly this for the
spoke image; this ports the same fix β€” run the one named vendor script
explicitly after the install, resolved via `npm root -g` because this
image installs Node from NodeSource debs whose global root differs from
the node base image β€” and `claude --version` makes the BUILD fail
loudly if the link is ever missing again, instead of every contributor
task failing at runtime.

Reproduced and fix verified in a throwaway build on the image's exact
base (debian:bookworm-slim + NodeSource node 24, claude-code 2.1.226):
before the postinstall the same error, after it `claude --version`
succeeds. Regression-pinned by
TestContributorDockerfileLinksClaudeNativeBinary, which fails on the
unfixed Dockerfile.

Signed-off-by: Douglas Baggett <doug.baggett@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…T3) (#5035)

T3 of the TUI epic: the Pane interface, four stub panes that render
their title over a "waiting for data" placeholder, and the app frame β€”
header bar, 2Γ—2 bordered grid, footer keybinding strip β€” per the layout
sketch in src/docs/design/tui.md Β§3. tab/shift+tab cycle focus; the
focused pane wears a thick border (not only a color, so the highlight
survives termenv's Ascii profile where CI strips colors).

The pane/app split is deliberate: a pane renders content into the box
it is given, the app owns borders, focus and geometry, so pane golden
tests never break because the frame moved. Key routing and non-key
broadcast ship now as the seam T5/T7/T9/T11 slot into.

Full-frame golden pinned at 100x30 as panes/testdata/grid.golden β€” the
exact path the acceptance criteria name, via a fixed-name variant of
golden.RequireEqual that honours the same -update flag.

Closes #5004. Part of #4907.

Signed-off-by: Douglas Baggett <doug.baggett@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Danathar <doug.baggett@gmail.com>
Fixes #5045
Fixes #5047

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Signed-off-by: Danathar <Danathar@users.noreply.github.com>
Co-authored-by: Danathar <Danathar@users.noreply.github.com>
Signed-off-by: Danathar <Danathar@users.noreply.github.com>
Co-authored-by: Danathar <Danathar@users.noreply.github.com>
…on agent default, session PR link, credential status (#5033)

Follow-up to #4891. Four gaps stood between a Linear-sourced hive and
running the L3 pack cleanly:

- Double hand-out. A delegated issue arrives through the session webhook
  (kicked immediately) and, with assigned_only, through the governor's next
  sweep. Kicks wait for an idle prompt rather than interrupting, so the
  effect was a re-hand the moment the session's run ended. The session
  tracker is now the in-flight ledger (Tracker.ActiveSessionForIssue); the
  scheduler consults an injected InflightLookup, withholds held items from
  ${ISSUE_LIST} and IssueRefs, and appends an "In Flight" note at the same
  seam as the tracker section (${IN_FLIGHT} places it explicitly).
- session_agent had no L3 default: the resolver fell back only to a sole
  configured agent, and the pack has six. It now falls back to the sole
  enabled agent whose ACMM mode allows tracker writes (CanCreateIssues) β€”
  quality at L3; two writers is still ambiguous and still an error.
- No PR in the session: the pr-request watcher gains a PR-opened hook; the
  responder narrates the PR as an action activity and attaches it to the
  session's external links via agentSessionUpdate.
- Dashboard: the Linear Agent card already existed; it now reports which
  credential ISSUES_ONLY+ agents hold for Linear writes (oauth / api_key /
  none).

Docs, policies variable table, CHANGELOG, and verification items 9–10.


Claude-Session: https://claude.ai/code/session_013SEx7DdWVED4txUw3ykY5D

Signed-off-by: Gregory Hunt <greg@on-board.ai>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…mmit (#5051)

The Tagged Release workflow's release commit was pushed straight to the
protected v4 branch. v4 branch protection requires the 'gate' status
check (docker.yml), which only ever attaches to a commit through
docker.yml's own push/pull_request triggers β€” a commit created in-job
and pushed directly has no check on it yet, so GitHub rejected the push
outright (GH006) on every attempt, identically, permanently blocking
release v4.0.1 (#5026).

Required-status-check evaluation is keyed on the commit SHA, not on
which ref the check ran against, and GitHub accepts a check that
already succeeded on that SHA before the push. release.yml now pushes
the release commit to a throwaway release-gate/v<version> branch first
(docker.yml's push trigger is branches: ["**"] minus bot branches, so
gate runs there too), waits for gate to conclude success/skipped/
neutral on that exact SHA, then pushes the same commit to v4 β€” which
protection now accepts. The scratch branch name stays outside
docker.yml's LONG_LIVED set so this never pushes a GHCR image or moves
a channel tag, and it is deleted immediately after regardless of
outcome.

Branch protection is unchanged: no bypass, no weakened check, no
enforce_admins change, no force push.

Fixes #5026

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…rld-readable /tmp/contributor-task.json (#5066)

task_assign serialized the entire hub message β€” including msg.github_token β€”
to TASK_FILE with default 0644 perms, so any local user on a contributor
host could read the live task-scoped installation token. The token's only
legitimate on-disk home is the 0600 GH_TOKEN_CACHE written by injectGhToken.

Strip github_token before serializing, write the task file 0600 (with a
chmod to cover overwriting a pre-existing 0644 file), and add a regression
test asserting the persisted file carries neither the key nor the value.

Fixes #5065

Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
Co-authored-by: sec-check <sec-check@hive.kubestellar.io>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…promotion test (#5063)

TestIntegration_SelectTask_PromotionRequiresPR (#5037) wrote task_complete
over the WS connection, slept a fixed 30ms, then read the contributor
profile back off disk. The profile write (saveContributorProfile) happens
on the connection's own read-loop goroutine asynchronously relative to the
test goroutine, so under load the fixed sleep can lose the race against
that write. The test then drives selectTask with a stale TasksWithPR and
reuses an issue number whose completion just booked it into cooldown,
so selectTask correctly returns task_unavailable/no_matching_work for it.

This is a test-side flake, not a product bug: selectTask's behavior is
correct given what it was asked. Fixed by polling the persisted profile
for the expected TasksCompleted/TasksWithPR count (bounded, 2s timeout,
5ms poll interval) instead of guessing a fixed delay.

Verified: reproduced deterministically by injecting a 40ms delay before
the save call (5/5 failures); confirmed root cause; with the fix in
place the same injected delay passes 5/5. Clean at -count=100 and
-race -count=30 with no injected delay.

Fixes #5037

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…5052)

Adds pkg/hub/hub_generations_handler_test.go: handler-level tests for the
master-key rotation endpoint's previously uncovered branches β€” malformed-body
400, success response shape (incl. secret hygiene on the 200 body), forced
rotation within cooldown, the stranding 409 with no RetryAfter, the untrusted
generation-state 500, and the persist-failure 500 as a clean retryable no-op.

Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
Co-authored-by: sec-check <sec-check@hive.kubestellar.io>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add client.Agent and Client.Agents(ctx) to the TUI's dashboard API
client, decoding GET /api/agents.

The design doc (src/docs/design/tui.md Β§2.1) flagged /api/agents as
missing from dashboard/openapi.json at the time this task was filed.
That gap is closed: #5023 widened the spec from 32 documented
operations to 255 paths, and /api/agents is now published with both
get and post. The published schema for the get operation matches the
handler (pkg/dashboard/api_agents.go, handleAgentsList /
agentListEntry) field-for-field, so the Agent struct here mirrors both
sources with no disagreement between them: name, id, displayName
(omitempty), enabled, managed, backend, model. No fields are invented;
in particular neither source has an activity timestamp or a
running/paused/idle status string, so this struct doesn't either.

Tests cover: full-fixture decode of testdata/agents.json (including
the omitempty displayName case), an empty list, a malformed-JSON body,
and a non-200 status via the existing APIError contract.

Fixes #5053

Signed-off-by: Andy Anderson <andy@clubanderson.com>
… 1/5) (#5068)

Advances #4903. errcheck reported 585 non-test findings on v4; this
step fixes the long tail of ~114 findings across ~24 packages outside
pkg/hub (224), pkg/proxy (108), and pkg/dashboard (103) -- those three
are steps 2-4.

Each finding was judged individually rather than blanket-silenced:

- Genuinely ignorable (silenced with _ = and a comment): closing an
  already-fully-read HTTP response body (48 sites), a read-only file
  descriptor, best-effort cleanup in a branch that is already
  returning the real underlying error, os.Setenv on a fixed valid
  key/value (cannot fail on Unix), flag.FlagSet.Parse under
  ExitOnError (never returns on failure), and CLI/HTTP writes with no
  one left to report a broken pipe or disconnected client to.

- Real bugs, fixed rather than silenced:
  - pkg/beads: Store.Archive and appendArchiveEntry deferred Close()
    on a write path with the error discarded, so a failed flush could
    report a successful archive while the in-memory bead was already
    deleted. Both now check Close and propagate/report failure.
  - pkg/knowledge: GraphStore's four bbolt View() calls and
    DocumentSource.Delete's RemoveTriple calls silently dropped
    query/cleanup errors; now logged. FileStore.persistAccessCounts's
    final os.Rename error was dropped; now logged.
  - pkg/hivectl/commands: printTable's tabwriter.Flush() error (the
    only call that can surface a broken output pipe, since tabwriter
    buffers every write until Flush) was dropped by a bare defer; now
    captured and returned.
  - cmd/hive/main.go: self-upgrade marker cleanup (os.Remove) errors
    were dropped, risking confusing state across the next boot; now
    logged. parseColorInt silently returned black on a malformed hex
    color; now falls back to the same default as an empty string.

errcheck stays out of src/.golangci.yml until steps 2-4 land -- this
PR fixes findings only, the gate must never go red.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…f parsers (#5044)

readFileTrimmed (pkg/mint/tokenreview.go) was at 20%: the rotation
re-read, whitespace trim, and missing-file branches were untested even
though this loader is what keeps the mint authenticating itself after
the kubelet rotates the projected ServiceAccount token. Now 100%.

NewInClusterTokenReviewAuthenticator was at 50%: only the missing-env
refusal was covered. The CA-bundle branch is now tested hermetically on
both plain hosts (absent bundle must refuse, naming the CA) and real
pods (present bundle must construct without network I/O). Now 87.5%.

splitFilePathRef / isFilePathRef (pkg/advisory/advisory.go) were only
reached through the happy VerifyFindingPaths flow; edge branches
(non-numeric suffix, trailing/leading colon, gh-/#-refs) are now pinned
directly. Both now 100%.

Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
Co-authored-by: sec-check <sec-check@hive.kubestellar.io>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…in-cluster hosts (#5036)

TestProvenanceReportsSeedWritableOutsideKubernetes assumed the test host
has neither KUBERNETES_SERVICE_HOST nor a serviceaccount token, so it
failed deterministically on any hive that itself runs in a pod (both
signals present). Add config.SetSATokenFileForTest β€” mirroring the
existing SetSecretFileRootsForTest seam β€” and have the test clear both
probes explicitly, matching what config_save_test.go already does
in-package.

Signed-off-by: hive-quality <sec-check@hive.kubestellar.io>
Co-authored-by: hive-quality <sec-check@hive.kubestellar.io>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubestellar-ci-bot and others added 19 commits September 1, 2026 05:39
docs/development.md documented `go test ./...` with no mention of the flags
the required `test` check actually uses. Every shard in v2-tests.yml runs
`-short -race -count=1`, so a contributor following the docs ran a materially
weaker suite than the gate: no race detector, and no awareness that a test
placed behind a `testing.Short()` guard is never executed by CI at all.

Documents each flag, why `-race` matters most here (the writeMu/WriteControl
reasoning in contribute_ws.go exists because of real mutex re-entrancy
deadlocks), and separates the two flags that appear in CI but are not part of
the PR gate: `-timeout 600s` belongs to the hourly coverage cron, not the
shards, and `-coverprofile` feeds a report rather than the merge gate.

src/docs/contributor-relay.md described a 4-hour backstop with nothing about
the GitHub token lifecycle, leaving the 55m-token/4h-task gap unexplained.
The gap is covered: the hub re-mints at 50 minutes on the heartbeat, so a task
running to the backstop uses several tokens in succession. Documents minting,
refresh, the two conditions refresh depends on (live socket, active task), and
removal on every task-exit path but deliberately not on a decline.

Also documents two honest limits rather than assuming they are handled: a
failed re-mint is logged hub-side and never surfaced to the relay, and
`token_expires_at` is recorded by the relay but never read, so expiry is
observed as a push that starts failing rather than as any warning.

Signed-off-by: Andy Anderson <andan02@gmail.com>
…y-docs

πŸ“ docs: document the real CI test flags and the relay token lifecycle
The release workflow verifies gate through a workflow_dispatch check-run on its scratch branch, but those check-runs have no pull-request association. The protected release PR therefore omits gate from its required-context rollup and rejects every merge with HTTP 405, even when a newer gate check-run is green.

After the exact-SHA gate wait succeeds, publish gate:success as a SHA-scoped commit status before opening the PR. Fail closed if that POST fails, remove the ineffective post-PR re-dispatch race, preserve gh pr create diagnostics, and document the corrected protection flow.

Extend the release harness to prove status/PR/merge ordering and both new failure paths.

Fixes #5356

Signed-off-by: Danathar <doug.baggett@gmail.com>
Package tests inspect shipped files outside src/, but v2-tests only ran for src changes (plus the recently added OpenAPI exception). A PR changing Justfile, config/backends.conf, or the bin scripts could therefore bypass the very parity and behavioral assertions intended to police it.

Extend the pull-request filter to those guarded roots and add a trigger-contract test over every current external package-test input. The test evaluates the effective changed-file property, including ordered negative patterns, and carries the pre-fix filter as a failure-direction control so an always-true matcher cannot make the guard vacuously green.

Signed-off-by: Danathar <6772335+Danathar@users.noreply.github.com>
Hive read the short-lived Claude access token out of .credentials.json
once, at manager construction, and injected that snapshot into every
claude agent as CLAUDE_CODE_OAUTH_TOKEN. That variable is a static bearer
override: with it set, Claude Code uses the value verbatim, never opens
the credentials file, and can never refresh. Measured in-container β€” the
variable set to a bad value beside a perfectly good credentials file
answers "401 OAuth access token is invalid"; unset, the same command
works.

Claude access tokens live 8h (measured mint-to-expiresAt), so every claude
agent was pinned to whatever token was on disk when the container started.
Once it aged out the fleet 401'd, and the only thing that re-read it was
ReloadClaudeToken(), called solely from the dashboard's OAuth-login
handler β€” so restarting an agent re-injected the same dead snapshot and
the operator had to re-login, roughly daily.

The variable is now injected only when the agent has no credential file
it can read, which is the job it was added for (c5648bc). Since
per-agent homes (#4619) every agent's ~/.claude symlinks to the shared
/data/home/.claude, so the CLI reads the credential itself and redeems
its refresh grant on start β€” the one thing the override made impossible.

Alongside it, claude.HasUsableToken (a live access token, or an expired
one whose refresh grant is still good) replaces HasValidToken at the five
sites asking "can this credential still put an agent to work?".
HasValidToken reports an expired token as no token, which made a routine
expiry indistinguishable from a logout and β€” the one that mattered β€”
disabled the token-triggered restart heal (#4596/#4606) via
configHasTokens(). The watchdog now reports
LoginPromptWithUsableCredential rather than paging a human.

Hive still performs no refresh of its own: a refresh rotates the grant
and instantly revokes the token every other live session holds (observed
as "401 OAuth access token has been revoked" on a sibling agent), the
race #5171 declined to introduce. OAuthTokens also gains
refreshTokenExpiresAt, which it had been silently dropping on any
rewrite.

The logged-out path is untouched: with no refresh grant, or one past its
own expiry, every alert, badge and prescription behaves as before.

Fixes #5454

Signed-off-by: Douglas Baggett <doug.baggett@gmail.com>
… on in-cluster hosts (#5428)

TestEntrypointHardensRuntimeConfigItRecreates fails deterministically (3/3
subtests) on any in-cluster runner or live hive host:

1. runBootPreludeRoot inherited the pod's KUBERNETES_SERVICE_HOST via
   os.Environ(), so IS_KUBERNETES was true even for the 'Docker mode' cases β€”
   the env-var half of the leak whose file half the serviceaccount-token path
   rewrite already closes. The harness now drops the inherited variable so the
   case's env map alone decides the branch.

2. The prelude's 'owned by the runtime user' fast paths compare against the
   literal uid 1001 (dev in the shipped image). Under go test the seeded files
   are owned by the current uid and a non-root test cannot chown, so
   hive_harden_runtime_config took its cannot-chown branch by design (#5360)
   and the 0600 assertions failed for reasons unrelated to the hardening under
   test. The harness now rewrites the uid literal onto the sandbox uid, the
   same mapping it already applies to /data and config paths.

No production code changes; entrypoint.sh is untouched.

Signed-off-by: hive-quality[bot] <hive-quality[bot]@users.noreply.github.com>
Co-authored-by: hive-quality[bot] <hive-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…urls handler contract (#5433)

Two functions at 0% coverage get dedicated tests:

- pkg/config ProjectConfig.CheckoutRootFor (config.go): table tests for
  bare/org-qualified/deep repo slugs, the empty no-op sentinel, whitespace
  trimming, and the path-traversal guard ('.', '..', separators) that keeps
  a config-supplied repo name from escaping checkouts_dir.

- pkg/dashboard handleAgentTerminalURLs (terminal_urls.go): HTTP contract
  tests β€” 503 on nil AgentMgr, and the documented 'no pane is not an error'
  behavior (200 with empty urls/authUrls lists, never null, never an error)
  for both a not-running agent and an unknown agent.

Test-only change; no production code touched.

Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
Co-authored-by: sec-check <sec-check@hive.kubestellar.io>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…kResult.Queued, and bare stub.View (#5459)

Per-function coverage showed four 0%% seams in pkg/tui that no open PR
claims:

- attach.go: tmuxNotFoundError.Error/Unwrap and
  tmuxSessionMissingError.Unwrap β€” the typed-error contract the attach
  preflight footer and errors.Is chains depend on. New
  attach_errors_test.go pins the exact messages (with/without tmux
  detail) and that both Unwrap methods expose the underlying cause.
- client/actions.go: KickResult.Queued β€” the #5325 async-kick seam
  distinguishing 'queued' from 'in-flight'. New kick_result_test.go
  pins both wire values, the empty case, and case-sensitivity.
- panes/pane.go: stub.View β€” every shipped pane overrides View, so the
  shared fallback is reachable only through the next pane that embeds
  stub; stub_view_test.go pins it directly (box fill + degenerate size).

app.Run stays uncovered: it wraps tea.NewProgram().Run() on the live
terminal and needs a seam, not a test-only workaround.

Signed-off-by: hive-quality <quality@hive.local>
Co-authored-by: hive-quality <quality@hive.local>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Danathar <doug.baggett@gmail.com>
Signed-off-by: Danathar <doug.baggett@gmail.com>
Signed-off-by: Danathar <doug.baggett@gmail.com>
Signed-off-by: Danathar <6772335+Danathar@users.noreply.github.com>
Signed-off-by: Douglas Baggett <doug.baggett@gmail.com>
)

The gateway-endpoint fallback from 231ca4b (#5393) is correct, but it
shipped inline in the ~3400-line main() with no test. Nothing goes red if
a refactor of main() drops it, and the symptom it prevents β€” "502 no
inference route for agent" β€” only surfaces at runtime on a live hive.

Lift the endpoint/model decision tree for backend=="litellm" out of
main() into resolveLiteLLMInferenceRoute(cfg, backend, requestedModel)
(endpoint, model, ok). Strictly behaviour-preserving: same order (local
proxy > legacy governor.litellm block > explicit gateway), same
default-model inheritance, same warn-and-install-no-route on failure.
main() keeps ownership of the key, CA bundle and logging.

The extraction makes the honest failure explicit: ok=false rather than an
empty endpoint string, so a dead route can never be installed silently.

Table-driven tests cover legacy endpoint used directly, local proxy
overriding both other sources, the #5393 gateway fallback including
default-model inheritance and case-insensitive name match, a gateway with
no endpoint, an unrelated gateway that must not be borrowed, and nothing
configured at all.

Verified the tests discriminate the fix, not just the extraction: with
the gateway-fallback block removed (pre-231ca4b behaviour) the three
gateway-fallback cases fail with ok=false and every other case still
passes; restoring the block turns them green.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…ome/.copilot/config.json (#5440)

configTestHelper and TestConfigHasTokens_WithActualFile wrote the LIVE
shared Copilot credential file, clobbering real tokens for the duration
of every test run (or permanently, if the binary died mid-run) and
flaking on live hosts (TestFixSharedConfigPerms_FixesPerms: EPERM on a
foreign-owned file). Redirect sharedCopilotConfigPath to t.TempDir()
instead β€” the var exists for exactly this (manager.go).

Also make the three skip-on-live-host negative tests
(TestConfigHasTokens_NoFiles, TestCopilotConfigHasTokens_NoFile,
TestClearExpiredTokens_NoFile) hermetic via emptySharedPaths so they
assert everywhere instead of skipping.

Signed-off-by: hive-quality[bot] <quality@hive>
Co-authored-by: hive-quality[bot] <quality@hive>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…allback

πŸ§ͺ test(inference): extract and cover the litellm route resolution (#5460)
Automated release commit. Moves the CHANGELOG.md Unreleased section
into a dated v4.0.1 entry. See src/docs/releases.md.

Signed-off-by: hive-release-bot <actions@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
@kubestellar-prow kubestellar-prow Bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Sep 1, 2026
@kubestellar-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign danathar for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubestellar-prow kubestellar-prow Bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Sep 1, 2026
docs-link-check.yml gains v5 in its branch lists (release-lines-in-sync guard) and the toolapprove idempotency test compares against a captured key instead of an identical expression (SA4000, staticcheck gate arrived with the merge).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
@kubestellar-hive

Copy link
Copy Markdown
Contributor

Release Line Guard failure β€” exact cause (runs 33515437334 push / 33515444520 pr, 2026-09-01T13:47Z):

The guard's repo-in-sync self-test fails because this branch's .github/release-lines.yml declares release lines v2,v4,v5 but docs-link-check.yml still lists only v4:

FAIL: docs-link-check.yml:38 branches: has [v4], expected [v4,v5] β€” missing: v5
FAIL: docs-link-check.yml:47 branches: has [v4], expected [v4,v5] β€” missing: v5

Adding v5 to both branch lists in .github/workflows/docs-link-check.yml (lines 38 and 47) should turn Release Line Guard green. All other guard self-test cases pass.

β€” ci-maintainer agent

🐝 Hive Agent: ci-maintainer | Instance: hosted-available-oke-11-placeholder-r05x | SHA: unknown

β€” hive: agent=ci-maintainer backend=copilot model=claude-fable-5

…guard

The v4.0.1 top-up brought TestOpenAPISpecCoversEveryRegisteredRoute, which requires every registered dashboard route to appear in dashboard/openapi.json. The v5-only approval desk routes (GET /api/approvals, POST /api/approvals/resolve, POST /api/approvals/bulk) are real owner-facing JSON operations, so they are documented rather than excepted.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
@clubanderson
clubanderson merged commit d92e32d into v5 Sep 1, 2026
58 of 59 checks passed
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Thank you for your contribution! Your PR has been merged.

Check out what's new:

Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has signed the DCO. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants