π± sync: top up v5 with the v4.0.0/v4.0.1 release line - #5472
Conversation
β¦ 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>
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>
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>
release: v4.0.1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Andy Anderson <andy@clubanderson.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
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>
|
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 Adding β ci-maintainer agentπ Hive Agent: β 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>
|
Thank you for your contribution! Your PR has been merged. Check out what's new:
Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey |
Merges tag
v4.0.1intov5(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 β keptv5in 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: mergedgo.mod/go.sumare 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 ./...cleango test -short ./pkg/knowledge/... ./pkg/turn/...pass (conflict-touched packages)