π sync: top up v5 with latest v4 (436 commits) - #5017
Conversation
β¦gin (#4596) (#4606) Hive auto-restarts an agent whose pane shows a login prompt while a valid credential exists in the shared config, on the theory that the agent has simply not picked the token up yet. Three guards decide WHEN that restart may fire (login streak, kick grace, cooldown). None of them decided HOW MANY TIMES, so when something the restart cannot reach holds the agent at the prompt, it retried at the 60s cooldown forever. Restarts are not free β they destroy in-flight work, which is the exact failure the kick grace was added for β so an unbounded retry of a repair that demonstrably is not working is harmful on its own. #4596 is precisely that shape. Claude's authentication spans TWO files and hive has only ever probed one: * $HOME/.claude/.credentials.json β the OAuth token. claude.HasValidToken reads this, and it is what configHasTokens() gates the restart on. * $HOME/.claude.json β the session state: which account is signed in (oauthAccount) and whether onboarding completed. A valid token beside a session file that has lost its oauthAccount makes the CLI re-run onboarding and show "Select login method". Every file-level check hive performs says "authenticated" while the operator is looking at a login menu, so configHasTokens() stays true, the pane keeps showing a prompt, and the restart loop never terminates β each relaunch rewriting the same contended file. This change: * Caps consecutive token-triggered restarts that fail to clear the prompt at 3, then stops and records a diagnosis. The counter resets the moment the prompt clears, so an agent that recovers is never permanently barred from a later legitimate nudge. * Adds diagnoseStuckLogin, which names BOTH files, says which one is the problem, and says restarting cannot fix it. Where both files look correct it says the cause is not on-disk state instead of guessing; non-claude backends get an honest generic message rather than a claude-shaped one. * Adds inspectClaudeSession, a read-only classifier for .claude.json (absent / unreadable / no-signed-in-identity / signed-in). An unparseable file reports unknown, never "identity missing" β this exists to replace guesses, not to add one. Deliberately DIAGNOSTIC ONLY. Nothing here repairs .claude.json or relaxes its permissions: #4596 measured that making the shared file group-writable converts "one agent cannot log in" into "no agent stays logged in", because every agent then rewrites it. Nothing changes AgentHome either β whether interactive-auth backends should get per-agent homes is a fleet-wide behavioural decision (it would relocate credentials for every existing deployment and cost one login per agent) and is left to the maintainers. This makes the failure legible and stops hive amplifying it. The give-up path deliberately does not `continue`: it disables only the token restart, leaving the TLS-error and hung-CLI detectors below live. The restart decision moved into AgentProcess.decideTokenRestart so the rule is unit-testable β the inline version sat behind a real tmux pane capture and could only be exercised in production. Accounting lives with the decision so "fired" and "counted an attempt" cannot drift apart. Tests verify the cap with a FIXED probe count rather than one derived from tokenRestartMaxAttempts: an earlier draft derived its loop bound from that constant and passed happily with the cap set to 1<<30. Both halves are falsified β the suite fails with the constant raised, and fails again with the give-up branch removed and the constant left sane. Signed-off-by: Danathar <doug.baggett@gmail.com>
β¦ke-podman fixture (#4584) pkg/sandbox sat at 67.6% (floor 67): Run was 20% covered (only the missing-binary branch) and Available 0%. Add a fake-podman script fixture so Run's success, non-zero-exit, invalid-spec, and credential-env-stripping paths run deterministically on any host β no real podman, no image pulls, no rootless uidmap prerequisites. Also cover Available and the remaining PodmanArgs branches (workspace-required, --name, --read-only, custom mount/workdir). Package coverage: 67.6% -> 98.5%. The [sandbox]=67 floor override in coverage-hourly.yml and v2-tests.yml can now be removed (ratchet rule), but the App token lacks workflows permission, so a maintainer should drop it in a follow-up. Signed-off-by: hive-quality <hive-quality@users.noreply.github.com> Co-authored-by: hive-quality <hive-quality@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
π fix(deploy): keep ttyd's --url-arg when it is credentialed (#4593)
* fix(contributor): auto-review Codex approvals Signed-off-by: Danathar <doug.baggett@gmail.com> * fix(contributor): add a reviewer-key escape hatch and guard whitespace workspaces Review feedback on #4589, both points from the quality agent. 1. approvals_reviewer had no off switch. `-c approvals_reviewer=auto_review` is a Codex CONFIG KEY, and hive cannot prove every Codex release accepts it. The reviewer asked what happens if one rejects an unknown -c key at startup: every codex contributor task fails at launch. It was worse than "no preflight". The value was read with ${HIVE_CODEX_APPROVALS_REVIEWER:-auto_review}, and `:-` treats an explicitly EMPTY value as unset β so setting the variable to "" still produced auto_review. There was no way to drop the key at all; the only escape was HIVE_CODEX_DANGEROUSLY_BYPASS_APPROVALS_AND_SANDBOX=1, i.e. the unsandboxed posture this PR exists to remove. Switched to ${VAR-auto_review} (no colon) and skip the flag when the result is empty. An operator hitting an incompatible Codex can now drop just the reviewer key and keep --ask-for-approval/--sandbox. 2. --add-dir silently corrupted argv for a workspace path with spaces. backend_perm_flag returns a whitespace-separated string that agent-launch.sh word-splits (`read -r -a PERM_ARGS <<< "$PERM_FLAG"`), so the contract cannot carry a path containing whitespace: `--add-dir /work space` arrives as three argv words and grants Codex the wrong directory. The grant is now omitted with a warning on stderr rather than emitted corrupted. The sandbox posture still applies β only the flag that cannot be expressed correctly is dropped. The default output is byte-identical to before, so the relay test's pinned argv is unchanged (163/163 still pass). Tests pin both behaviours: an empty reviewer drops the -c key while keeping the sandbox and the workspace grant, and a whitespace workspace omits --add-dir while keeping the posture. Verified they fail against the pre-fix config. Docs record the whitespace constraint and point at the empty-string escape hatch as the supported response to a Codex version that rejects the key β explicitly in preference to the dangerous bypass. Note: bin/contributor-agent.test.sh still aborts later on this host at the pre-existing non-hermetic "missing codex binary" check (a real codex on PATH yields NOT_AUTHED, tracked in #4592). Unrelated and unchanged; the full suite passes with codex off PATH. Signed-off-by: Danathar <doug.baggett@gmail.com> --------- Signed-off-by: Danathar <doug.baggett@gmail.com>
β¦hing live /data state (#4605) TestNewGitHubProxy and TestNewGitHubProxyWithWritableDir wrote the CA to the live /data/proxy-ca.pem default, and the latter deleted it afterwards β running unit tests on a hive host would destroy the production proxy CA. Redirect CACertPath/caKeyPath to t.TempDir() with the save/restore pattern already used by sibling tests. TestLoadPromptTemplate_FromPoliciesDir used agent name 'scanner', which the live /data/policies copy shadows since loadPromptTemplate consults /data paths before cfg.Policies.LocalDir. Use a name that cannot exist on a host. TestCollector_Summary_Initially expected a nil summary, but NewCollector unconditionally loads /data/token-summary.json, which exists on live hosts. Construct the collector with a temp persistPath so the no-snapshot premise actually holds. Test-only changes; no production code touched. 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>
β¦ ratchet (#4570) (#4578) * π± test: lift the three lowest packages over 90% and turn the coverage ratchet (#4570) The gate that filed #4570 is green again β the `hub: TESTS FAILING` it reported was a semantic conflict between #4562 and #4564 and was fixed by #4571 β but the issue it filed is about the floor, and the floor had gone slack in two different ways. Real coverage, on the three packages sitting furthest below the 90% default. All three are security-path code, and the untested statements were the security-relevant ones: pkg/sandbox 67.6% -> 98.5% pkg/ioscan 71.0% -> 97.1% pkg/pushbroker 72.8% -> 100.0% - sandbox: PodmanLauncher.Run was 20% covered because every test that touched it needed a container runtime. Pointing Binary at a recorded stub makes the whole body β argv construction, env sanitization, stream capture, exit-code reporting β deterministic on every runner, including the assertion that a credential env var never reaches podman's argv. Also covers the spec-validation rejections and the allowlist-is-not-an-escape-hatch rule in SanitizedEnv. - ioscan: confusableASCII was 12% covered β 22 of the 25 homoglyphs an attacker can substitute for their ASCII twin were never exercised, so a gap in that table would have been silent. The whole table is pinned now, plus an end-to-end check that a Cyrillic-spoofed directive folds back and still blocks. Adds the canary registry's disk round trip and best-effort-save behavior, the classifier's semantic verdict/redaction marker, its retry exhaustion, and the OpenAI-compatible client's error paths. - pushbroker: covers every rejection stage of Broker.Run through a scripted git runner (unreadable HEAD, nothing committed, mint failure, empty token, rejected push), both base-ref fallbacks, and GitHubAppMinter β including that the minted token is scoped to the single repository and lands on the least-privileged default tier. Floors, in both copies of PKG_THRESHOLD. The three above move with the gain, per the map's own rule that an improving package has its floor raised in the same PR. Three more had drifted well above floors recorded at d89121f and were no longer catching anything: intent 65 -> 87 (holds 87.7), tracing 76 -> 85 (86.0), dashboard 78 -> 82 (83.1). scheduler (90.4) and escalation (93.2) now clear the 90% default, so their entries are gone. No floor was lowered, and nothing was raised to paper over a drop. Every value was re-measured on v4 at cd8aee5; each new floor keeps about a point of headroom so ordinary churn does not turn the gate red. Verified with -race and -count=3. Signed-off-by: Danathar <doug.baggett@gmail.com> * test(ioscan): read the whole request body in the OpenAI happy-path test Review feedback on #4578: the test captured the outbound request with a single r.Body.Read into a ContentLength-sized buffer. One Read is not guaranteed to fill the buffer, so a request delivered across more than one segment would leave trailing NUL bytes in gotBody and fail the temperature/max_tokens Contains assertions intermittently β a flake that would read as a real determinism regression in the classifier. Use io.ReadAll and surface a read error instead of discarding it. Coverage is unchanged at 97.1% (ratchet floor 96). Signed-off-by: Danathar <doug.baggett@gmail.com> --------- Signed-off-by: Danathar <doug.baggett@gmail.com>
β¦4609) * [quality] fix(tests): hermetic pkg/config tests on live hive hosts Five pkg/config tests fail when run on a live hive host because they read the host's real state instead of test fixtures: - TestValidate_MissingOrg: HIVE_REPO is set on hive hosts and applyBootstrapEnv backfills project.org from it, so the expected validation error never fires. Clear it with t.Setenv. - TestValidate_NoAgents: Load() merges per-agent overlays from the default /data/agent-configs, which is populated on a live host, so 'no agents' validates fine. Point data.agents_dir at an empty temp dir. - TestSaveSkipsDashboardOverlayOutsideK8s: IsKubernetesPod() probes the hardcoded serviceaccount token path, which exists on hive hosts, so the overlay is written even with KUBERNETES_SERVICE_HOST unset. Turn the probe path into a test seam (saTokenFile var) and redirect it, plus RuntimeConfigFile, to a temp dir. - TestPausePersist_ConcurrentSoak / _TwoUnsynchronizedSavers: reloads merge the live /data/agent-configs overlays (which carry paused: false) over the persisted paused flags, dropping every pause. Use an empty temp agents_dir and redirect RuntimeConfigFile/DashboardOverlayFile. Same hermeticity pattern as #4595 (scheduler/tokens/proxy); this covers the config package. No production behavior change: saTokenFile is a var only as a test seam, identical to the existing CACertPath precedent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: quality <quality@hive.kubestellar.io> * test: make gh app token script tests portable Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Andy Anderson <andy@clubanderson.com> --------- Signed-off-by: quality <quality@hive.kubestellar.io> Signed-off-by: Andy Anderson <andy@clubanderson.com> Co-authored-by: quality <quality@hive.kubestellar.io> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Andy Anderson <andy@clubanderson.com>
β¦coverage gate (#4612) Adds hermetic unit tests for the lowest-covered logic paths not already addressed by in-flight PRs #4578 (sandbox/ioscan/pushbroker) and #4595 (scheduler/tokens/proxy hermeticity): - pkg/review 80.8% -> 90.2%: dispatch state persistence round trip and error paths (LoadDispatchState/WriteDispatchState were 0%), upsertHuman, removePendingForHead, reviewCapableAgents filtering/allow-list, ValidateReport rejection matrix, Collect error and skip paths. - pkg/retro 82.4% -> 90.5%: Lane.Due, metaBool, severityToPriority, roundDuration, applyPRMetadata branches, prRefFromAttrs, splitPRRef/ repoPart/canonicalIssueRef edge cases, completedAt sources, openDuplicate open-vs-closed advisory matching. - pkg/tracing 86.0% -> 94.8%: formatReachTime, TimelineSpanName fallback, AgentKickAttributes (was 0%), SaveReachState write/rename error paths, reach overflow-log saturation branch. All tests are hermetic (t.TempDir, in-process stores, no network, no live-hive state) so they stay green on live hive hosts. Signed-off-by: quality <quality@hive.kubestellar.io> Co-authored-by: quality <quality@hive.kubestellar.io> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The controller accepts agy as a backend and rotation has an agy/Google fallback rung, but the image did not ship /usr/local/bin/agy. Agy was therefore always rejected as agy-binary-missing and DeepSeek exhaustion could strand an otherwise rotatable agent. Install the vendor's pinned public Linux artifact (v1.1.19) with the published SHA-512 for amd64 and arm64. The binary is self-contained. Document that operators must seed a pre-authenticated /home/james/.gemini session into persistent storage; a pod cannot initiate Google OAuth. Signed-off-by: hanthor <jreilly1821@gmail.com>
β¦4608) High-volume agents normally avoid subscription providers, but that policy stranded them when their current prepaid/metered provider was positively measured exhausted: every fallback was excluded, so the only terminal action was pause. Keep the guard for normal operation and subscription-source failures. Waive it only for a successful exhaustion probe on the CURRENT metered provider, allowing the agent to rotate to a measured-healthy Codex, Claude, or Agy fallback rather than lose service. Probe errors remain fail-open. Add a regression test for DeepSeek/litellm -> Codex at 300s cadence. Signed-off-by: hanthor <jreilly1821@gmail.com>
src/docs/README.md is the index readers navigate from, so a page missing from it is one nobody finds. A sweep of every top-level src/docs/*.md against the index found eight unlinked pages -- two more than #4617 reported (roadmap.md and landscape.md were missed). Each entry goes in the section a reader would actually look in rather than a dumping ground: the five feature/config pages under "Configuration and agents", the heartbeat bearer cutover under "Security (v4)" since it retires a hub-master-derived credential, and roadmap/landscape under "Architecture and design" as project-direction documents rather than operator how-tos. Entries use relative links, which is what recent additions to the index actually chose: five of the six most recent index commits that ADDED an entry used a relative link, and the one absolute case edited an existing line in place rather than picking the format. Both formats appear for same-directory files historically, so the rule is not "in-directory files are relative" -- it is simply the live convention for new entries. roadmap.md and landscape.md both self-date in their own headers ("Directional, not a promise"; "This document will rot"), so their index entries carry a currency hint pointing the reader at that date rather than presenting them as settled fact. Also adds an advisory docs-index reminder workflow modelled on changelog-reminder.yml (#4429, #4440): it comments once when a PR adds a top-level src/docs page the index does not link, and never blocks. It reports only pages that PR adds, never the pre-existing backlog, and excludes subdirectories such as adr/ that carry their own README index. Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦authenticates the fleet (#4619) The shared HOME=/data/home meant Claude Code's wholesale atomic rewrite of $HOME/.claude.json (tmp file + rename, needing only DIRECTORY write permission on the 2775 dev:node shared home) let ANY agent replace the session file β an unauthenticated agent's rewrite stripped oauthAccount from under the authenticated one and the whole fleet fell back to the login menu. File permissions can never close a directory-entry race, which is why #4606 shipped diagnosis + a bounded restart, not a fix. Now each per-UID interactive agent runs with HOME=/data/home/agents/<name>: - AgentHome() routes per-UID non-inference agents to the per-agent home (single source of truth; buildAgentEnv, the auth probe, caveman install and #4606's stuck-login diagnosis all follow automatically). - setupInteractiveHome() provisions it at launch (mkdirAllNoFollow, tighten-to-0700-chown-uid with the tightenInferenceHome unprivileged fallback) and bridges shared state back to /data/home by Lstat-guarded symlinks: .claude (holds .credentials.json β the shared OAuth token, so one login still authenticates the fleet), .copilot, .config, .codex, .bob, .gemini, .cache, .local, plus the entrypoint-written .gitconfig/ .bashrc/.profile. Real files/dirs at a bridge name are never clobbered. - The contended ~/.claude.json becomes truly per-agent, ADOPTED at launch from a signed-in source (legacy shared file first, then any signed-in sibling) via #4606's inspectClaudeSession classifier β never fabricated (no signed-in source anywhere -> the login menu is the honest state), and a signed-in per-agent file is never overwritten. This also makes #4606's capped token-triggered restart curative. - Orphaned /data/home/.claude.json.tmp.* debris (ten accumulated in one afternoon on the reporting hive) is swept, bounded and best-effort. - .bash_history contention disappears as a side effect (not bridged); .npm deliberately un-bridged (per-agent caches end the cross-UID EACCES collisions the shared cache suffered). - entrypoint.sh pre-creates /data/home/agents (0755 dev:node). - HIVE_SHARED_AGENT_HOME=1 escape hatch restores the legacy layout. Migration: none. Credentials stay put; the first launch adopts the legacy session; single-agent hives behave as before. Fixes #4596 Signed-off-by: Andrew Anderson <andy@clubanderson.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add hermetic tests for tmux session probes, pane capture/readiness, blocking prompt detection, trust prompt dismissal, and Copilot session refresh looping. Signed-off-by: Andy Anderson <andy@clubanderson.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Redirect pkg/agent tests away from live metrics, ACMM fallback, token, and tmux state so they stay deterministic on hosts with active hive data. Signed-off-by: Andy Anderson <andy@clubanderson.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
β¦eshooting (#4626) * π docs: retire stale v2 disclaimers in architecture and legacy troubleshooting src/docs/architecture.md opened with a blockquote making two claims, both stale. It said the page "describes the `v2` branch" β v2 was retired in August 2026 and this file documents the current v4 line under src/. It also said automatic goal decomposition, plan review and stall-triggered re-planning "are not implemented", which is no longer true: all three ship today as the planning-intelligence lane. Rather than swap v2 β v4 and leave the capability claim standing, the blockquote now states what is actually true β the three capabilities exist but are gated at ACMM L5+ (PlanningMinACMMLevel, src/pkg/planning/issue.go), since the architect that decomposes epics has no cadence below L5 β and points at planning-intelligence.md and ADR-0006, which architecture.md itself does not cover. docs/troubleshooting.md (root, legacy v1/systemd) labelled its forward link "v2 troubleshooting" while the sentence containing it already said "branch `v4`; code under `src/`". Only the visible label was stale; the target path was correct. The legacy banner itself is correct and is left intact. Fixes #4624 Fixes #4625 Signed-off-by: Andy Anderson <andy@clubanderson.com> * π docs: hedge the decomposition claim to match the wiring Follow-up verification of the planning package showed the first draft of this note overstated automatic goal decomposition. The request side is automatic and L5-gated, but the fulfillment side is not wired into the daemon: the only non-test caller of planning.Decompose / DecomposeFromOutput is the bd decompose CLI (src/cmd/bd/decompose.go:62), agentparse.ParseTaskList has exactly one consumer (src/pkg/planning/decompose.go:179), ClearDecomposePending is reachable only through that path, and the architect's standing policy never mentions decomposition. So a stock L5 hive kicks the architect but nothing ingests its output back into child beads; the epic stays decompose_pending. Correcting a note that understated the feature must not overstate it in the other direction, so the blockquote now names that specific step. Plan review and the stall-replan lane are separated out, since both genuinely do run automatically. Signed-off-by: Andy Anderson <andy@clubanderson.com> --------- Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦al (#4632) Nine "v2 HEAD" references across six docs named a branch that is no longer the line of development. Two of them were hiding a second, independent error behind the stale branch name. ioscan.md claimed "`ioscan` is not present in v2 HEAD. There is no `ioscan` package, no `ioscan:` config block ... and no canary/fail-mode implementation wired into the kick path." Every clause is false on v4: src/pkg/ioscan is ~1400 non-test lines, src/hive.yaml.example carries an ioscan: block, and canaries plus fail_mode are wired from src/cmd/hive/main.go. The page is rewritten to describe the shipped feature rather than deny it. api-reference.md described itself as "generated from route registrations". No generator exists anywhere in the tree, so the word "generated" was a second error β the same one found in env-vars.md. The page now says it is compiled by hand and must be updated alongside route changes. The knowledge-curator.md claims were verified and still hold: RunExtraction has no non-test callers and nothing wires NewCurator, so only the branch name is retired there. Likewise the federation and inference-backends statements are accurate for the current tree. Signed-off-by: Andy Anderson <andy@clubanderson.com>
* fix(github): slow-start pacing after secondary rate limits GitHub's secondary limit throttles burst/concurrency. When it trips, go-github blocks correctly until the reset β but at the reset boundary every queued caller (pr-request watcher retries, enumeration, automerge sweep, stats) fires SIMULTANEOUSLY, GitHub sees the burst, and the limit re-trips for another hour. Observed live on kubestellar/console (2026-08-23): three consecutive hourly re-trips (07:49 -> 08:49 -> 09:49) with the spoke's entire GitHub output dark throughout. slowStartTransport breaks the loop at the one place all callers converge β the shared proxy-trusting transport. A REAL secondary 403 (Retry-After header; permission 403s carry none) opens a caution window extending 10 minutes past the reset, during which requests are globally serialized with a ~2s jittered gap. The post-reset wave becomes a trickle the secondary limiter tolerates; full concurrency resumes when the window closes. Tests: paced spacing after a Retry-After 403; plain 403s and normal traffic unpaced. Signed-off-by: Andrew Anderson <andy@clubanderson.com> * fix(github): share slow-start STATE, wrap the CURRENT transport per client The sync.Once wrapper pinned the process to the FIRST inner transport it saw: after the proxy-trust layer rebuilt its transport (new CA), every later client silently rode the stale one with dead TLS roots β caught by the proxytrust pool/leak regressions. Pacing state stays global (the herd is cross-caller); the wrapper is now cheap and constructed per client around whatever inner is current. Pool test updated to assert the proxy-trust guarantee through the wrapper's inner. Signed-off-by: Andrew Anderson <andy@clubanderson.com> * test(github): SharesTransport asserts the shared INNER through the wrapper Per-client slow-start wrappers are intentional; the #3875 socket-pool guarantee lives on the shared inner transport, and the pacing ledger must also be one shared instance β assert both explicitly. Signed-off-by: Andrew Anderson <andy@clubanderson.com> --------- Signed-off-by: Andrew Anderson <andy@clubanderson.com>
The v2 branch is retired and mainline is v4, but eight files still used "v2" to name the current running software, a path that no longer exists, or a directory to grep. Each was verified against the code before being touched, so capability claims keep their substance and only the branch name is retired. - troubleshooting.md: dashboard-only mode and the tmux agent manager are both current v4 behavior (src/cmd/hive/main.go, src/pkg/agent/manager.go). - env-vars.md: three operator audit commands grepped a `v2` directory that no longer exists, so they returned empty and looked successful. Now `src`. - agent-configuration.md: `v2/policies/` is `src/policies/`. - knowledge-system.md: section already cited `src/pkg/` while calling the architecture "v2". - notifications.md, beads-cli.md, tls-setup.md: name-only retirement. Native TLS is still genuinely absent (no ListenAndServeTLS, no tls yaml keys). - deployment-scripts.md: the doc was accurate and the script is the defect β src/deploy/bootstrap-lxc.sh still hardcodes `git clone --branch v2`. Kept the description honest and recorded the defect for operators. Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦ gates (#4654) `docs/advisory-staleness.md` was linked from nowhere: the only `git grep` hits for it are Go comments discussing the concept, not links to the page. It was the sole orphan in root `docs/`, so operators could not discover it. It is current v4 documentation sitting in the legacy tree. It cites `src/pkg/hub/advisory_staleness.go` and documents the hub's live stale-advisory pill and warning alert, and unlike its neighbours it carries no "Legacy v1/systemd documentation" banner. Move it to `src/docs/`, which is the documented source of truth and which the advisory `docs-index-reminder` workflow (#4618) covers β root `docs/` is not covered, so leaving the page there means the next orphan goes unnoticed the same way. `git mv` keeps the history. Nothing referenced the old path: no sync manifest, no workflow, no script. Root `docs/` pages are reachable by direct URL from search engines and old bookmarks, so a bare delete would 404 anyone holding a link. Leave the house retirement stub β a short banner plus pointer, matching `docs/architecture.md`. The page had also drifted while nobody could find it. It was last touched at #4167; #4528 then added a whole suppression arm the page never learned: - `allAgentsQuietByDesign` β with no error reported, an ageing digest is suppressed when every agent is paused or off-schedule. Documented as a new gate, including its deliberate conservatism (no agents, or a legacy agent entry, does not qualify) and that a reported post error still flags. - `suppressed-agents-quiet` was missing from the diagnostics classification list, and the per-hive `hidden_stale` field was undocumented. Cross-link with `src/docs/advisory.md` rather than merging: that page covers what the digest shows and how findings are retired, and its "digest stopped updating" section is the operator-facing counterpart to these gates. The two "staleness" concepts are different (finding auto-close vs digest-post age), so they stay separate pages that point at each other. Signed-off-by: Andy Anderson <andy@clubanderson.com>
src/docs/design/ held four documents and no README. Only two of them (knowledge-system.md, master-key-rotation.md) were linked from anywhere a reader navigates; pr-reach-telemetry.md and master-delivery-wrapped.md were orphans. Their only inbound "references" are string literals in a component-mapping test table (src/pkg/reach/mapping_test.go, src/pkg/hub/reach_api_test.go) and a Go comment (src/pkg/hub/wrapkey_test.go) β none of which is navigation. The structural reason both went unnoticed: the docs-index-reminder workflow (#4618) is scoped to ^src/docs/[^/]+\.md$, top level only, so that subdirectories carrying their own README index β the adr/ pattern β are not false-positived. design/ had no such README, so it got neither the delegation pattern's coverage nor the guard's, and fixing the one file named in the issue would have left the next one to recur. So this adds src/docs/design/README.md following the shape of src/docs/adr/README.md, and links it from src/docs/README.md exactly as adr/ is linked, completing the delegation chain. Each entry states its status, checked against the code rather than the page's own header, so an accepted plan is not read as current behaviour: - master-key-rotation.md β generations foundation and the session-cookie domain landed; remaining per-domain adoptions pending. - master-delivery-wrapped.md β header still reads DESIGN ONLY, but the sealing primitive and spoke wrap-key lifecycle landed as wrapkey.go / wrapkey_store.go. Flagged both ways. - pr-reach-telemetry.md β phases 1 and 2a shipped, 2b/2c pending. Its v2/pkg/... examples are recorded as DELIBERATELY historical: pre-rename merges report those paths from the forge API forever and the mapper still handles both eras. Not "corrected" to src/.... - knowledge-system.md β wiki layers and programmatic extraction are live; the planned scheduler.RunCurator(schedule) wiring is not implemented, so knowledge.curator.schedule is defaulted and validated but read by nothing. The workflow is left alone deliberately; see the PR body. Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦bient HIVE_HUB_URL (#4658) hubDomainSuffix() prefers HIVE_HUB_PUBLIC_URL / HIVE_PUBLIC_URL / HIVE_HUB_BASE_URL / HIVE_DASHBOARD_URL / HIVE_HUB_URL over the cluster domain, so on live hive hosts (which export HIVE_HUB_URL) every test hive in url_reachability_test.go stopped counting as hub-fronted and six alert tests failed with 'got []'. Clear the five overrides via t.Setenv in newURLHealthTestHub so the tests exercise only the domain they set. Same env-bleed class as the scheduler/tokens/proxy fixes in #4595. Fixes #4657 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>
β¦4639) MergeAllowed, EvaluateForAppSelfMerge, BuildEvidence, LinkedIssueNumbers, and LinkedIssueInBeads were at 0% coverage despite gating what enters merge-eligible.json. Pin the authorization x alignment matrix, the App-self-merge tier-gate preservation (callAllowed=false must be exactly Evaluate; Tier1 stays issue-gated), and evidence extraction against a real beads.Store. pkg/intent: 88.x% -> 94.4%. 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>
TestSlowStart_PacesAfterSecondaryLimit asserted >=gap spacing between server-observed request arrival times, but the transport only guarantees each request starts no earlier than its claimed slot: scheduler and connection latency can push an early request toward the next one's slot, compressing observed inter-arrival gaps below gap even when pacing worked. Observed 4/4 failures on a loaded hive host (gaps 78us-44ms vs 60ms). Assert the burst's total elapsed lower bound instead: 4 cautious requests claim slots >=gap apart, so the burst cannot finish in under 3*gap. Load can only increase elapsed time, while an unpaced stampede still completes near-instantly and fails. Also widen the test caution window to a minute so delayed goroutines cannot escape it (pacing cost stays 3 gaps). Fixes #4653 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>
β¦ hosts (#4634) TestAgentEnvPairs_BaseEntryCount and TestAgentEnvPairs_BDDirFromBeadsDir assert exact env-pair counts, but agentEnvPairs conditionally forwards HIVE_ID, HIVE_SHA, and HIVE_ADVISORY_ISSUE from the ambient process env. On a live hive host those are exported into agent sessions, so both tests fail on pristine v4 (15/16 pairs vs 13/14 expected). Blank the three vars via t.Setenv in the count-sensitive tests through a clearAmbientHiveEnv helper; t.Setenv restores originals on cleanup. Same hermeticity class as #4585, in pkg/agent. 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>
β¦ hosts (#4627) TestAgentAuthState_TruePositivePreserved and TestAgentAuthState_CopilotCredentials fail on any live hive host with a logged-in copilot fleet, because two probe paths could not be redirected: - AgentAuthState stat'ed the const CopilotUserTokenPath directly; the dashboard device-flow token at /data/copilot-user-token exists on live hosts and short-circuits the probe to "authenticated". Introduce copilotUserTokenProbePath, a var seeded from the const, purely as a test seam (the sharedCopilotConfigPath convention), and redirect it in emptySharedPaths. - uid>0 probes resolve HOME to <sharedAgentHome>/agents/<name>, and the test probes agent "scanner" β a real deployed agent whose per-agent home holds real copilot credentials since #4619. Redirect sharedAgentHome via the existing withSharedAgentHome seam. Same class as #4605/#4622 (live-host test hermeticity, #4585). No production behavior change: the var is initialized to the same const. 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>
β¦24s β 0.01s (#4628) (#4659) TestConcurrentPauseResume_NoPanic pins the pause/resume mutex accounting under concurrency, but its agents were plain (non-sandbox) claude configs, so every Resume of a StatePaused agent took the full relaunch path: ensureTmuxSession + launchInTmux β a real tmux session create plus a real CLI launch per cycle, ~250 launches per run, none cleaned up. On any host where HIVE_WORK_DIR is writable (every live hive host) that one test took 220-420s and pushed `go test ./pkg/agent` past go test's 600s default timeout, aborting coverage profile writes along the way. Route the test through the sandbox pause/resume branch instead (sandbox-enabled agents + SetSandboxConfig): every cycle is now a pure in-memory state transition, which is exactly the accounting the test exists to hammer. Measured 223.57s β 0.01s with -race on the same box. Concurrent stress on the real (non-sandbox) relaunch path is not lost β relaunch_race_guard_test.go covers that dance with proper stubs and cleanup. Also fix the tmux server mismatch in launch_coverage_test.go: the three adopt-running-CLI tests pre-created their marker sessions with bare `exec.Command("tmux", ...)` β the default-socket server β while the manager inspects the package test server (-L defaultTmuxSocket). The pre-created session was invisible to the manager, so each test silently did a full launch instead of exercising the adopt branch it documents. Coverage of manager.go's CLI-already-running block from these tests was 0 before this change and is full after it (verified with -coverprofile both ways). Audit of the rest of the package (issue bullet 2): every other tmux exec in tests goes through testTmuxCommand or the manager's socket-aware helpers, TestMain already isolates both the socket name (per-pid -L) and TMUX_TMPDIR, and su-exec paths are stubbed β no other bare-socket execs remain. Fixes #4628 Claude-Session: https://claude.ai/code/session_01KSiYdFgsi6Lt5zwmn7MrQm Signed-off-by: Douglas Baggett <doug.baggett@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
β¦aude.json (#4636) The caveman installer ran as the hive user with HOME pointed at the agent's per-agent home. Caveman spawns the backend's own CLI (claude plugin list / plugin install), and Claude Code rewrites $HOME/.claude.json wholesale on every invocation β run as a uid that cannot read the agent-owned 0600 session file, the CLI treats the home as a fresh install and replaces the signed-in session with a blank skeleton. The agent drops to the login menu on its next start, and every re-login is destroyed again at the next launch. This is the #4596 wholesale-rewrite clobber reintroduced from inside the manager, through the per-agent layout #4619 built to end it. Observed on a live rootless hive: the stuck agent's home accumulated .claude.json.tmp.<pid>.<hash> debris owned by the hive uid, each clobber timestamped exactly at an "installing caveman" log line, and three operator re-seeds of the session file were each destroyed within seconds of the next agent launch. Fix: wrap the install in su-exec as the agent user whenever the agent has an allocated UID β matching every other command that touches the per-agent HOME (tmuxCmd, setupCodexHome). The npm cache moves with it: a cache written back when the install ran as the hive user is owned by the wrong uid and npm EACCESes on its shards (#2284's failure class), so a foreign-owned cache is removed, with a per-UID fallback path when removal fails. Also correct the stuck-login diagnosis for the unreadable-session case. Under per-agent homes, "unreadable" means unreadable BY THE HIVE PROCESS β the normal state of a healthy signed-in agent whose CLI owns the file at 0600. The old wording claimed the agent's CLI could not load the identity, which sent the investigation above down a permissions rabbit hole on a file the agent could read fine. Signed-off-by: Douglas Baggett <doug.baggett@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
β¦ath (#4993) Found by the backend-list parity guard in #4987 on its first run (#4988). `amazonq` was offered to operators in the dashboard's backend/method picker and named in the CLI Pin Value tooltip, while being absent from BOTH authoritative registries β config/backends.conf's KNOWN_BACKENDS and config.CLIBackends. validateBackendName (src/pkg/agent/manager.go) therefore rejected it, so an operator who picked the backend the UI recommended got a launch failure. That is precisely the accept-then-fail class that function exists to prevent: its own comment says it dispatches on the same canonical lists as ValidateBackend and backendBinary "so a backend accepted by any write path is one the launch path can start". The picker is a write path that was never brought into that agreement. REMOVE, NOT FINISH β and this is not a product judgement. git history shows amazonq once HAD a full dispatch path (backend_binary "q", backend_perm_flag "--trust-all-tools") and was deliberately dropped from backends.conf in #1045, whose commit message states the resulting supported set: "6 CLIs: Claude, Copilot, Goose, Codex, Agy, Bob". gemini went in the same commit. So this completes a removal that was already decided; it does not decide against Amazon Q support. Removed from seven places across both dashboard trees and the shell/JS launch paths: - src/pkg/dashboard/static/index.html β the JS KNOWN_BACKENDS array that backendOptionList() renders the dropdowns from, and the CLI Pin Value tooltip that names the options in prose. - dashboard/index.html, dashboard/server.js β the standalone dashboard tree carries the same two lists, both claiming to mirror backends.conf. - bin/agent-launch.sh, bin/contributor-agent.sh, bin/contributor-relay.sh β three dead "no --model flag" lists. Unreachable already, since the backend can never be selected, but they are what made the name look half-wired rather than removed. - bin/contributor-relay.test.js β the test that pinned the dead entry. GUARD FOR THE THIRD LIST. #4988 notes the dashboard's JS array is a third, independent list with no guard at all, and calls a follow-up "worth it if this class of drift matters". It plainly does β that gap is the whole issue β so the guard lands here rather than being deferred: - TestBackendPickerOffersOnlyDispatchableBackends parses the JS array out of the embedded page and asserts every name is in CLIBackends, InferenceBackends, or a documented exception. - TestBackendPickerInferenceListMatchesGo pins the page's second array, since a name drifting between the two mislabels a picker entry as a CLI rather than a gateway even when both lists look individually plausible. - TestCLIPinTooltipNamesNoUndispatchableBackend covers the prose tooltip, which the array guard structurally cannot reach β and which is the other place #4988 found amazonq. DIRECTION IS DELIBERATE: picker β registries, not the reverse. A name the picker offers but nothing can launch is a broken promise to the operator. A supported backend the picker omits (`pi`, `agy`) is only under-advertisement, and whether agy belongs in the HUB's picker turns on whether it can authenticate in a pod β its sign-in is an interactive Google OAuth flow with no API-key mode β which is a question this guard must not answer by implication. Left for maintainers. `openrouter` stays, as a declared exception with its reason recorded: it is a Model Gateway preset that agent routing resolves as a configured gateway name, and the page deliberately offers it before a matching gateway exists so an operator can pick it and then fund it. The exception list carries the same staleness check as cliBackendExceptions β an exception that becomes a real registered backend, or leaves the picker, fails the test rather than rotting. Mutation-checked: restoring amazonq to the array, restoring it to the tooltip only, and dropping watsonx from the JS inference list each fail the right test with a message naming the specific backend and list. Verified the shell/JS edits are inert: bash -n on both scripts, and the relay suite is 162/163 before and after (the one failure, "a relaunch cds somewhere resolvable before starting the CLI", is pre-existing on a clean v4 and unrelated). Closes #4988 Signed-off-by: Danathar <doug.baggett@gmail.com>
* π feat(identity): observe-only delegation chain Signed-off-by: Andy Anderson <andy@clubanderson.com> * test(identity): cover minter, publish, and validation paths to clear the coverage floor Signed-off-by: Andy Anderson <andy@clubanderson.com> --------- Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦fetime (#5000) * π fix: scope uncollectible-upgrade de-dup per server and bound its lifetime undeliverableUpgradeNoted β the memory that keeps the hub from writing an identical "upgrade not armed" timeline entry on every 2-minute poll β was a package-level global. Two hubs in one process shared and clobbered it, so one server's note suppressed the other's entirely: an operator watching the second hub saw no refusal at all, which is the exact silence that timeline entry exists to break. Two hubs carrying the same hive ID is the normal case in tests, not an exotic one. The tell was in the tests: every one of them opened by hand-scrubbing the global for hive IDs its own fresh server had never seen (12 call sites), so each new test had to remember the incantation and a test that forgot it failed only in combination with whichever earlier test used the same ID. Entries also leaked forever. The only removal path ran when a hive was successfully ARMED, but an uncollectible hive is by definition never armed, so nothing could ever retire its entry β unbounded growth across hive churn, contradicting the bounded-growth claim in the comment that justified the map, and precisely for the unassigned-placeholder population this file handles. Moves the map onto HubServer under its own mutex (not s.mu: the note path does not hold s.mu and goes on to call recordTimeline), makes forgetUncollectibleUpgrade a method so it is scoped to its server, and hooks hive removal β removeRegistryEntry and handleRegistryDelete β so entries have a bounded lifetime. The map allocates lazily, since bare &HubServer{} literals do not pre-allocate it. Verified by neuter-test: reverting to a shared package global turns the per-server and removal-lifecycle tests RED while the de-duplication controls stay green, so the tests detect the old design rather than merely describing the new one. Race detector clean. Fixes #4995 Signed-off-by: Andy Anderson <andy@clubanderson.com> * π fix: convert #4997's re-arm tests to the per-server forget method Semantic merge conflict with #4997, which landed first: its new rearm_collectible_test.go calls forgetUncollectibleUpgrade as a bare function, while this branch makes it a HubServer method. Git rebases cleanly because the two touch different lines; the package simply stops compiling. Caught by building the rebased branch rather than trusting the clean rebase. Signed-off-by: Andy Anderson <andy@clubanderson.com> --------- Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦t as shell source (#5001) The #4938 deny list puts (, ), * and , into backend_perm_flag's output. The argv consumer (agent-launch.sh, read -r -a) handles that correctly and is tested β but four consumers interpolate the flag string into text a shell re-parses: the Justfile contribute-hive local mode, the contributor agent's interactive tmux launch, hive.sh's supervisor send-keys, and the AGENT_LAUNCH_CMD env files that supervisor.sh materializes into a launcher via unquoted heredoc. All four died at the first paren: -bash: syntax error near unexpected token `(' before the CLI ever started. No single spelling is both argv-safe and shell-line-safe (%q escapes survive read -r, raw parens break the parser), so add backend_perm_flag_shell β per-word %q β and move the four shell-line consumers to it. The raw contract on backend_perm_flag is unchanged. Contract tests pin all three sides: the _shell output parses as shell source, reduces to the identical argv, and the raw spelling still does NOT parse (if it ever does, the variants have converged and one should be deleted). Refs #4938 Signed-off-by: Douglas Baggett <doug.baggett@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add Danathar to the Adopters List table, following the schema documented in the file's "How to Add Yourself" section and matching the description and maturity level of the existing entries. Signed-off-by: Douglas Baggett <doug.baggett@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
T0 of the #4907 TUI epic: the design record every later sub-issue reads before picking up work. All five sections the issue asks for β scope, the verbatim architecture decisions, the layout sketch, the keybinding table, the testing convention β plus two corrections that the sub-issue bodies do not carry and that change what several of them can do. PATHS. The epic specifies `v2/internal/tui/...` and `v2/docs/tui.md`. There is no `v2/` directory and no module at the repo root; the module is `src/`, which is where go build and every CI shard run from, so a tree at the repo root would never be compiled or tested. The prefix is not a typo but a stale spelling: #3996 renamed the `v2/` source tree to `src/`, which src/pkg/reach/mapping.go:36-37 records verbatim and still compensates for, because a PR merged before the rename reports `v2/...` paths from the forge API forever. The page carries the full mapping table so T2 through T27 do not each rediscover it. CONTRACT. The epic names `dashboard/openapi.json` as the source of truth for every client task, with an escape hatch when an operation is missing. That hatch is going to fire constantly, so the size of the gap is measured once here rather than surprising each task: the spec publishes 32 routes, all GET, against 298 the dashboard actually registers (137 GET, 83 POST, 64 PUT, 14 DELETE). It documents NO write operation of any kind, so all four Phase 2 action tasks β T14 pause/resume, T17 model, T19 ACMM, T20 kick β have no contract to build against, and each endpoint they need is already exercised by hivectl today. Two read tasks are hit too, including T2 (#4917), which is named `/api/health` β an endpoint the spec does not contain. The page cites the live endpoint for each so those tasks can file the spec-gap issue AND still ship, rather than stalling on a reading that would block six of them at once and leave the TUI read-only. Also recorded: the Delivery decision resolves to `hivectl tui` (T1, #4919) because `hive` is the spoke daemon with no subcommand dispatch while hivectl is the cobra CLI that already defaults --server to the :3001 proxy and --token-env to HIVE_DASHBOARD_TOKEN β and the one deviation that forces, since the epic names HIVE_DASHBOARD_URL and hivectl spells the same thing --server. Two deliberate deviations from the issue text: - Location is `src/docs/design/tui.md`, not `v2/docs/tui.md`, per the path correction above. src/docs/design/ is this repo's home for "longer-form design records ... the reference record for a decision", which is exactly what T0 asks for, and its status taxonomy gives the page the "design only" header it needs while nothing is shipped. - The issue says not to touch any docs index because T26 owns that. This adds ONE line to src/docs/design/README.md anyway. That file is the directory's own index, not the top-level one T26 names, and its "Adding a document here" section makes the entry part of the procedure for adding a page β it states pages here are reached through this index rather than through src/docs/README.md. Following the letter of the issue would land a page nothing links to, which is the exact failure the docs-index-reminder workflow exists to catch. Easy to drop if a maintainer disagrees; it is one bullet. Verified: doc-only, `go build ./...` still passes, and every relative link resolves. Each numeric claim was measured against origin/v4 at 45a13d5 rather than estimated. Part of #4907. Closes #4915 Signed-off-by: Danathar <doug.baggett@gmail.com> Co-authored-by: Andy Anderson <andy@clubanderson.com>
T2 of the #4907 TUI epic: the one place that knows how a request to the dashboard is addressed and authenticated. Client, New(), Health(), and the shared getJSON() helper the pane-client tasks (T4/T6/T8/T10) build on. THE AUTH HEADER, AND WHY IT IS NOT GUESSED. The issue says to send the token "the same way the web dashboard does per dashboard/openapi.json security scheme (check the spec; do not guess the header)". Checked: the spec has no security scheme. It has no `components` block at all β the whole document is `openapi`, `info`, `servers`, `tags`, `paths`, and nothing else β so there is no `securitySchemes`, no top-level `security`, and no per-operation `security` to read. `/api/health` is not in it either. Rather than guess, the header is transcribed from the two things that actually verify it, which agree: - src/proxy/server.js requireAuth() matches `/^Bearer\s+(.+)$/i` against req.headers.authorization and timing-safe compares to HIVE_DASHBOARD_TOKEN. - src/pkg/hivectl/client.go sets `Authorization: Bearer <token>` from the same environment variable. So: `Authorization: Bearer $HIVE_DASHBOARD_TOKEN`, and omitted entirely when no token is set β a bare `Bearer ` fails that regex and would turn a public endpoint into a 401, which is why an empty token is not sent as an empty header. Both are pinned by tests. NO DUPLICATE SPEC-GAP ISSUE. The issue's note says to file one if the spec is missing what this task needs, and completing that filing completes the task. #4912 is already open and already reports exactly this β including a recommendation to add "the HIVE_DASHBOARD_TOKEN security scheme". A second issue would be noise, and stopping at a filing would leave T4/T6/T8/T10 blocked behind a client that can be written correctly today from the verifier. Corrections to #4912's route inventory are posted there as a comment rather than duplicated here. WHERE /api/health ACTUALLY LIVES. It is a Go dashboard route (src/pkg/dashboard/server.go) on the isPublicPath list, reachable at the epic's :3001 base URL because src/proxy/server.js proxies /api/* to the Go API on :3002. That public status is load-bearing for the TUI: a tokenless health check is what separates "the dashboard is unreachable" from "the dashboard is there and my token is wrong", which are different things to tell an operator. Confirmed independently by src/pkg/hub/heartbeat.go, which probes http://localhost:3001/api/health, and by the compose healthcheck. Also carried over deliberately: the transport lesson from src/pkg/hivectl/client.go. A nil Transport silently shares http.DefaultTransport, whose idle pool every parallel httptest teardown calls CloseIdleConnections on β which showed up in this repo before as a flaky "connection broken" from an unrelated test. This client clones its own, and a test asserts it. pkg/hivectl's client was evaluated for reuse instead of a new one. It speaks the same API with the same token, but its Do() returns `any` because it feeds the CLI's table/json/yaml printer, so a TUI decoding into typed structs would re-marshal that map on every poll; and its constructor returns an error, which this task's New() contract cannot. The reasoning is recorded in the package doc so the next reader does not have to redo it. Tests: httptest fixtures assert path, method, Authorization, and Accept; the tokenless path; APIError across 401/404/502/500-empty-body; the error-body cap; getJSON decode, malformed-body, transport-error and context-cancellation paths; and the two environment variables with their default. Every load-bearing assertion is mutation-checked β swapping Bearer for Token, sending an empty Bearer, dropping the trailing-slash trim, sharing DefaultTransport, and unbounding the error body each fail their test. Verification: go build ./..., go vet ./..., golangci-lint with the repo config (0 issues), gosec on pkg/tui (0 issues), every test binary in the module compiles, and pkg/tui/client is at 96.9% against the gate's 90% floor. No dependency changes β this is stdlib only, so go.mod and go.sum are untouched and the vulnerability surface is identical to v4. govulncheck could not be run locally (v1.1.4 crashes in the x/tools SSA builder on the Go 1.27 toolchain here); CI runs it on Go 1.25. Out of scope, per the issue: any endpoint other than health, SSE, retries and backoff. Part of #4907. Closes #4917 Signed-off-by: Danathar <doug.baggett@gmail.com> Co-authored-by: Andy Anderson <andy@clubanderson.com>
β¦on (#4975) A hive configured with governor.work_source.type: linear aborted every eval cycle when ghClient.EnumerateActionable failed (e.g. the GitHub App lacks the Issues permission and every repo returns 403 "Resource not accessible by integration"). The work-source overlay runs only after that call, so the Linear backlog was never enumerated and the queue sat at 0 until an Issues permission a Linear-sourced hive should not need was granted. Extract the decision into actionableAfterGitHubEnumerate: on the default GitHub work source the cycle still aborts and keeps prior state; on a non-GitHub work source the failure is logged as a warning naming the work source and the cycle continues with whatever partial result GitHub returned (an empty result when nil), letting the overlay populate issues. 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>
Documents who responds to a vulnerability report (the Maintainer Committee, rostered in OWNERS), the end-to-end handling flow, the 60-day publicly-known-vulnerability fix commitment recorded on the OpenSSF Best Practices badge, how security-response membership is added/removed via the existing maintainer lifecycle, an honest note that the current cross-org roster is incidental rather than a policy target, the escalation path (Maintainer Committee -> KubeStellar Steering Committee / CNCF CoC channels), and the project's stated known limits (no on-call, no third-party audit, no CVSS policy). Adds a 'Who Responds' pointer in SECURITY.md and indexes the new page in src/docs/README.md. Does not change or duplicate the existing SECURITY.md reporting process. Signed-off-by: Andy Anderson <andy@clubanderson.com>
* π docs: add CNCF General Technical Review Answers the CNCF GTR questionnaire (Day 0 Planning, Day 1 Installation and Deployment, Day 2 Day-to-Day Operations) against this repository, citing file:line, doc, or workflow evidence for every substantive claim. Questions with no supporting evidence in the repo are marked [NEEDS OPERATOR INPUT] with a one-sentence description of the gap rather than answered speculatively. A summary block at the top of the document gives the answered/needs-input counts and groups the outstanding items by section for operator follow-up. Adds an index entry in src/docs/README.md alongside the existing CNCF TAG-Security self-assessment. Signed-off-by: Andy Anderson <andy@clubanderson.com> * π docs: resolve all operator-input markers in General Technical Review Fills in the 13 NEEDS OPERATOR INPUT markers with the operator's answers: vision/goals, use-case scope, target personas, adopter org types, end-user research (citing real adopter-filed issues), sovereignty posture, compliance, certificate rotation boundary, SLO/SLI/load-testing/limits (all none), third-party attribution (known gap, PR in flight), and security-response policy (known gap, issue being filed). Updates the summary block to 73 answered / 0 pending and replaces the per-section NEEDS OPERATOR INPUT index with a Known gaps list naming the five real standing gaps: no SLOs, no load testing, no NOTICE file, no security-response policy, no formal compliance, no third-party audit. Signed-off-by: Andy Anderson <andy@clubanderson.com> --------- Signed-off-by: Andy Anderson <andy@clubanderson.com>
Adds a repo-root NOTICE file listing Go module dependencies compiled into hive/hive-hub/hive-contributor, addressing a CNCF General Technical Review gap: go.sum pins dependency versions but nothing assembled an attribution document from it. - src/scripts/generate-notice.sh: walks the resolved module graph with google/go-licenses, pinned to an exact tagged version and installed via `go install`, matching the existing govulncheck/gosec pattern in go-security-analysis.yml. See the script header for why go-licenses was chosen over a hand-rolled go-list walk. - go-security-analysis.yml: new `notice-drift` job regenerates NOTICE on every change to src/go.mod, src/go.sum, or the script (plus weekly) and fails the build if the committed file has drifted. - release.yml: attaches NOTICE to the GitHub Release alongside the existing per-image SBOMs, same asset-upload call. - src/docs/releases.md: documents generation, enforcement, and scope β explicitly what NOTICE does NOT cover (base-image OS packages, from-source Node/tmux layers β those stay on the SBOMs). The committed NOTICE cannot yet be the real, go-licenses-generated file: this change was authored in an environment where `go` cannot be run, so no `go` tooling (including go-licenses itself) could produce the actual resolved module graph or verified license text. Rather than fabricate license identifiers, NOTICE is a placeholder derived statically from src/go.mod's require blocks only, with every license field marked UNVERIFIED and the file's own header explaining exactly that. The first notice-drift CI run is expected to fail once, showing the real diff; a maintainer applies that regenerated output (or runs generate-notice.sh locally with go installed) and commits it. CHANGELOG entry under Added. Signed-off-by: Andy Anderson <andy@clubanderson.com>
Console is maintained by a hive today β issue triage, fixes, review, and merge run continuously against that repo. It is the first entry at the Production maturity level; every other adopter is Pre-production. Also fixes a malformed link in the same table: the Open Horizon Services row was missing the closing bracket on its markdown link, so it rendered as literal text rather than a link. ADOPTERS.md is read directly during CNCF maturity review β the Incubation criteria ask for a publicly documented adopter list with adoption levels β so a broken link there is more visible than in ordinary docs. Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦at shipped (#5009) Two answers in the General Technical Review described work as "being dispatched". Both landed, so the answers now describe what exists. Security response (#5005): src/docs/security-response.md documents who responds, how a report is handled end to end, the 60-day fix commitment, membership changes through the existing maintainer lifecycle, and escalation. The GTR now cites it instead of recording a gap. Third-party attribution (#5006): the mechanism exists β generator, notice-drift CI guard, release attachment β but the answer deliberately does NOT claim the work is finished. The committed NOTICE is a statically-derived placeholder whose license fields all read UNVERIFIED, because it was assembled without running Go tooling and no license was inferred. A reviewer who follows the link finds exactly that, so the document says it up front and points at #5007 for the one remaining maintainer step. The Known gaps list is updated in the same spirit. "No security-response policy" becomes "no on-call or diversity target", which is what is actually missing now that the process is written β the document is explicit that it defines neither, and that response depends on whichever of three part-time maintainers sees a report first. Overstating either item would be the expensive kind of error here: a reviewer who catches one inflated claim discounts the answers that are solid. Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦the binary (#4974) On a hive whose config path is read-only β the ordinary shape of a bind-mounted hive.yaml under Docker/podman β entrypoint.sh falls back to reading /data/hive.yaml.runtime directly and signals that by exporting HIVE_CONFIG. That export was inert. main.go reads HIVE_CONFIG only to choose the DEFAULT of its -config flag, and the image's own CMD passes that flag explicitly: CMD ["--config", "/etc/hive/hive.yaml"] An explicit flag outranks a default, so the binary loaded the stale, unwritable file the entrypoint had just decided not to use. The damage outlives the boot. Config.Save() writes /data/hive.yaml.runtime unconditionally, so the first save of any kind β a dashboard edit, a heartbeat-delivered config β wrote the stale config back over the operator's real persisted state. Reported as an ACMM level set from the dashboard reverting L4 -> L3 on every restart, reproduced twice, with /data fully intact (#4973). main.go cannot fix this alone: an explicit --config coming from the image's own CMD is indistinguishable from one an operator typed. The entrypoint is the component that knows the config path is stale, so it now says so in the argv it launches with, appending `--config "$HIVE_CONFIG"`. Appended rather than substituted because flag.Parse keeps the LAST occurrence of a repeated flag, so this wins without the script having to parse and rewrite the CMD. When HIVE_CONFIG is unset β the ordinary boot, where the copy succeeded and the config path IS current β nothing is appended and the CMD applies unchanged. Verified end to end against the real script: with a read-only config path holding acmm_level 3 and a runtime config holding 4, the binary resolved /etc/hive/hive.yaml (level 3) before and /data/hive.yaml.runtime (level 4) after. Also adds a WARN when the loaded config path disagrees with HIVE_CONFIG. That state should now be unreachable, but it was previously invisible from every vantage point: /api/config/provenance reads HIVE_CONFIG directly, so it reported the file the entrypoint chose while the process ran on the one it did not β which is exactly what made this hard to diagnose from outside the container. Tests extract the real launch block from entrypoint.sh by marker rather than paraphrasing it, and cover: the read-only branch firing at all, the redirect landing in argv, an unset HIVE_CONFIG leaving the CMD alone, the launch still passing "$@", and the Dockerfile/entrypoint coupling that broke β an image that ships an explicit -config must be paired with an entrypoint that outranks it. Signed-off-by: Danathar <doug.baggett@gmail.com>
β¦e hives (#4979) The Linear agent install and the OpenRouter funding flow built their redirect_uri from hub.dashboard_url if set, else from the request's X-Forwarded-Proto/X-Forwarded-Host/Host. On a hub-less hive whose dashboard is private but whose /linear/callback is published on another hostname β behind an ingress that rewrites Host (Traefik with a fixed upstream Host, a Cloudflare Tunnel "HTTP Host Header") β the install leg and the callback leg derived different origins and Linear rejected the code exchange with "redirect_uri is invalid". The only knob was hub.dashboard_url, a hub-namespaced field that is wrong for a hive with no hub and discoverable only by reading source. Add dashboard.public_url: the externally reachable origin of this dashboard. Precedence is dashboard.public_url β hub.dashboard_url (kept for hub-hosted spokes) β forwarded/request host, implemented once in oauthPublicOrigin and used by both callback builders. The value is validated at load time as an absolute http(s):// origin with no path, query, fragment or credentials (trailing slash trimmed). The install endpoint now also returns the redirect_uri it used beside authorize_url so an operator can see the value Linear must match. Tests: precedence/trimming table, validation table plus a Load-level error test, and a handler test where X-Forwarded-Host differs between the install and callback requests and both legs must still agree on redirect_uri β that test fails on the parent commit. 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> Co-authored-by: Andy Anderson <andy@clubanderson.com>
β¦ead of GitHub (#4981) The dashboard's ACMM "Open Issue" / "Open All" buttons always created a GitHub issue via the go-github client, even on a hive whose backlog lives in Linear (governor.work_source.type: linear). Operators of such hives got GitHub issues nobody triages. Config: governor.acmm.issue_tracker: github | work_source (default github; unset hives see zero behavior change; any other value fails validation). work_source means "file where the backlog lives": a Linear work source creates the issue via GraphQL issueCreate on the team whose teams[].repo matches the criterion's repo (else the first team); GitHub / GitHub Projects / Jira / unset stay on GitHub. API: POST /api/acmm/issue accepts an optional `tracker` field ("github" | "work_source") that overrides the config for one request; unknown values are a 400. The response now carries `tracker` ("github" | "linear") and, for Linear, `identifier` and `team`. GET /api/acmm/evaluation reports the effective default as `issue_tracker` plus `work_source_type`, stamped from live config on every response so a config edit is visible without waiting for the evaluation cache. Linear: LinearSource gains CreateIssue (team-key lookup + issueCreate) and TeamForRepo, sharing the existing endpoint/auth path (bare API key) through a new doGraphQL seam that doQuery now delegates to. The issue title and body are rendered once (acmmIssueContent) and are identical on both trackers; the audit entry records tracker either way. UI: when the work source is Linear, the level dialog shows a "File in: GitHub / Linear" selector pre-selected from config; issue links render the Linear identifier (ENG-42) instead of #42. Tests: config default + validation; request-vs-config resolution and the 400; Linear creation against an httptest GraphQL server (team routing, verbatim title/body, bare Authorization header); GitHub path unchanged. 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>
β¦ at all (#5010) The notice-drift job introduced in #5006 failed on its first real run against v4 β but not from the placeholder drift it was designed to detect. It failed before generating anything: Failed to find license for github.com/kubestellar/hive/pkg/forge: cannot find a known open source license for ".../hive/src/pkg/forge" ... and locates up until ".../hive/src" Every error names a github.com/kubestellar/hive/... path β the project's own packages, not a dependency. The cause is this repository's layout: LICENSE lives at the REPO root while the Go module is src/, so go-licenses' upward search stops at src/ and never sees it. It then exits non-zero and no NOTICE is produced. Both invocations now pass --ignore github.com/kubestellar/hive. That is the correct answer rather than a workaround: a THIRD-PARTY attribution file should not list the project's own code, so excluding it is what the file means. The alternative β a duplicate src/LICENSE purely to satisfy the tool's search path β would add a second copy of the license to keep in sync for no benefit, and this repo has already been bitten today by a LICENSE that drifted from canonical. Until this lands the job fails on every PR in the repo, including contributors' work that has nothing to do with it, which trains people to read red as noise. Signed-off-by: Andy Anderson <andy@clubanderson.com>
Signed-off-by: Danathar <doug.baggett@gmail.com>
Merge commit (never squash) preserving both lineages: v5's RFC line (#4000 approvals desk, #4001 hooks, #4002 turn model, reviewer role #5002) and v4 production. Conflict rule applied: v5 wins in the RFC packages, v4 wins everywhere else. Two exceptions were adjudicated on evidence rather than the rule: - pkg/turn: v4 grew an INDEPENDENT prototype (#4933) that collided with v5's full RFC #4002 implementation. v5 won, and v4's prototype-only runner_test.go + store.go were removed β they auto-merged cleanly but test an API v5 does not have (effectID redeclared; caught by go vet, not by go build). - level-5/6 packs: both sides APPENDED different agents at the same position. Kept the union (v4 telemetry+operations, v5 reviewer) and set the pack-count assertion to 12/13 β a value neither side had. pkg/hooks needed no adjudication: v4's copy is a byte-identical port from v5 (#4837). 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 |
v4's release-line guard (#4405) arrived in this merge declaring `release_lines: [v2, v4]`, and met v5's workflow triggers that already list v5. Neither parent fails alone β v5 has no guard, and on v4 no workflow names v5 β so the merge is what surfaces it. That is exactly the condition the guard exists to catch, so it is fixed here rather than waived. Per the manifest's own instructions, cutting a line takes three edits: - release_lines: [v2, v4] -> [v2, v4, v5] - every pinned workflow gains v5: go-security-analysis, podman-{contract, rootful-lane,rootless-lane,arm64-lane}, quadlet-gate (v2-ci, v2-tests and suid-contract already had it) - env_lists: docker.yml LONG_LIVED already carried v5 scorecard.yml is recorded as an EXCLUSION (`-v5`), not widened: scorecard-action hard-fails on any non-default branch ("refs/heads/v5 not supported with push event"), which is why 90dea44 dropped v5 from its trigger. Writing it down keeps that a justified line rather than a silent gap. test-release-lines-guard.sh built its "plausible future release line" fixture out of v5, which became a no-op once v5 was real β the demonstration passed while asserting nothing. Moved to v6 so the negative control still proves the guard catches a stale allow-list. Signed-off-by: Andy Anderson <andy@clubanderson.com>
Semantic conflict from the v4 top-up: v4 tightened CSP to `script-src-attr 'none'` and added TestEmbeddedStaticHasNoInlineEventHandlers (#4680), while v5's RFC #4000 approvals panel was written with inline `on*=` handlers. Both sides merged cleanly β the panel's HTML was v5-only and the guard was v4-only, so git had nothing to flag β but the merged result would have shipped an Approvals panel whose every control is dead under the new policy: the buttons render, the clicks do nothing, and nothing reports an error. Converted all 7 to the data-action dispatch idiom already used by the sibling sections, rather than weakening the test or relaxing the CSP: - section header -> data-action="toggleSection" data-arg0="approvals-section" (matches beads-section / platform-section verbatim) - Approve/Reject selected -> data-arg0 true|false with data-arg-types="j" - filter chips -> data-arg-types="j", preserving the null sentinel that distinguishes "all" from a named rule - row checkbox -> data-change-action="approvalsToggle" (the change listener's default branch already routes to hiveDispatchAction) - per-row Approve/Reject -> data-arg-types="s,j" with the id escaped through escapeHtml, matching the repo-pr-pill precedent Verified by neuter test: re-adding a single inline onclick puts the guard back to red (1 handler reported), so the test still detects what it claims to and this is not a green-by-construction fix. Signed-off-by: Andy Anderson <andy@clubanderson.com>
|
Thanks for your pull request. Before we can look at it, you'll need to add a 'DCO signoff' to your commits. π Please follow instructions in the contributing guide to update your commits with the DCO Full details of the Developer Certificate of Origin can be found at developercertificate.org. The list of commits missing DCO signoff:
DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
|
Thank you for your contribution! Your PR has been merged. Check out what's new:
Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey |
Top-up merge of
v4(production) intov5(the RFC implementation line: #4000 approvals desk, #4001 hooks, #4002 turn model, plus the reviewer role #5002).A merge commit, not a squash β sync PRs preserve both histories, matching #4058 and the earlier v2βv4 top-ups. Merge commit
4369eb95has two parents:847774a9(v5) andd3e2b5a5(v4).Incoming: 436 commits from v4, 923 files. v5 was 15 ahead.
Major incoming changes from v4
pkg/delegation/(new)pkg/hub(144 files)upgradeCollectibleheartbeat gating (#4997) at three sites; per-server uncollectible-upgrade de-dup (#5000); pull-only upgrade reasons; stale-upgrade reconciliationpkg/dashboard(125 files)pkg/agent(68 files)PauseByCause,SetPauseTransitionObserver, Copilot/Claude durable-token watchdog audit reasons, per-UIDHOMEisolationsrc/scripts/publish-image-tags.shwith immutable short-SHA tags and generation-checked moving tags; release-line guard; podman rootless/rootful/arm64 lanes; quadlet gatepkg/config,pkg/hub,pkg/agent,pkg/proxy,pkg/scheduler,pkg/review,pkg/retro,pkg/tracingtelemetryandoperationsagents at L5/L6Conflicts and resolutions
14 conflicted paths. The standing rule β v5 wins in the RFC packages, v4 wins everywhere else β resolved most of them, but three cases were adjudicated on evidence instead, and are called out below.
src/pkg/turn/envelope.go,journal.go,runner.gosrc/pkg/turn/runner_test.go,store.gosrc/pkg/config/config.goToolApprovalConfig,ToolApprovalRule). v4's side differed only in gofmt alignment of the surrounding struct, which v5's side already carries.src/pkg/config/packs/level-5.yaml,level-6.yamlsrc/pkg/config/acmm_packs_test.gosrc/cmd/hive/hookwire.goagent_paused/upgrade_pauseemitters that v5's code comment described as not-yet-existing, and moved the pause action toPauseByCauseso the depth-1 loop guard receives structured causation. Verifiedagent.PauseByCause,agent.SetPauseTransitionObserver, andhub.SetUpgradePauseObserverall exist in the merged tree.src/cmd/hive/hookwire_test.goTestAgentPausedEmitterCarriesHookCausationAndStopsPauseLoop, the positive control for the loop-safety invariant the emitter introduces. Dropping it would have left v4's new emitter untested.src/cmd/hive/main.goApprovals:hook adapter, and v4'sgithub.PrepareRequestDirs(logger)andinstallAgentPauseEmitter(agentMgr). All four intents verified present by grep after resolution.src/pkg/dashboard/deps.goApprovalDesk/ApprovalInbox; v4 addsHookFire. Disjoint struct fields.src/pkg/agent/audit.goAuditToolApproval; v4 addsAuditCopilotTokenMissing/AuditClaudeTokenMissing. Disjoint constants..github/workflows/docker.ymlrelease_channelsemission. v4'sf992bfa6deliberately removed that mechanism to fix release-channel publication starvation, moving publication intopublish-image-tags.sh. Keeping v5's side would have reintroduced the starvation bug. v5's safety intent is preserved structurally β see below.src/docs/hooks.mdpkg/turn β the semantic conflict, and the one that mattered
v4 is not a newer copy of v5's turn model. v4 independently landed
feat(turn): prototype durable re-entrant turns(#4933, 1052 lines added fresh), while v5 carries the full RFC #4002 implementation. Taking v4's side would have silently deleted the RFC line this branch exists to build β the diff in that direction removes 2206 lines includingjournaled_exec.go,operations.go,types.go, andreplay_test.go. Resolved to v5.The dangerous part was not in the conflict markers. v4's prototype
runner_test.goandstore.godo not exist on v5, so git added them with no conflict at all, andgo build ./...passed βgo buildignores_test.go.go vetcaught it:Both files belong to the discarded prototype (
store.godefinesturn.FileStore, referenced only by that test β theFileStorehits elsewhere in the tree are the unrelatedknowledge.FileStore). Removed.pkg/turnnow matches v5's file set exactly.Agent packs β union, and a count neither side had
level-5.yamlandlevel-6.yamlconflicted because both sides appended different agents at the same list position: v5 addedreviewer(#5002), v4 addedtelemetryandoperations. This is an append collision, not a disagreement β either side alone silently drops the other's agents. Kept the union (v4's two first, thenreviewer, preservingsort_orderintent).That makes
acmm_packs_test.gothe trap: v5 asserted5: 10, 6: 11(+1) and v4 asserted5: 11, 6: 12(+2). With all three agents present the correct value is5: 12, 6: 13β a value neither branch contained. Either side's number would have been a green-looking test asserting the wrong tree. Confirmed empirically:go test ./pkg/config/ -run 'ACMM|Pack'passes at 12/13.pkg/hooks β no adjudication needed
Worth recording since it was flagged high-risk: v4's
pkg/hooksis a byte-identical port from v5 (#4837port state-triggered hooks from v5) βdispatcher.gohashes tob0dd545eon both branches. The "v5 wins" rule never had to fire there.docs/hooks.md
The causation prose took v4's wording, which is now the accurate one (the emitter it describes exists after this merge). But v4's two "β οΈ NOT YET FUNCTIONAL / does not work yet" callouts for
enqueue-approvalare stale on v5: the backing queue landed here with #4057 astoolapprove.Inbox, wired vianewHookApprovalAdapterβWithApprovalQueueincmd/hive/main.go. Carrying them in would have shipped documentation telling operators a working control does not work. Dropped the callouts and rewrote Status: to state that it is functional on v5 and gated bytool_approval.enabled(default off, sink nil, records an unwired-sink error).docker.yml β checking that v5's intent survived
v5's block carried a deliberate comment that
stable/candidate/edgemust never be retagged by a v5 build. Taking v4's side removes that comment, so the intent was verified structurally instead:publish-image-tags.shgates channel refs on[[ $branch == "$release_branch" ]], anddocker.ymlpassesrelease_branchas the literalv4. A v5 build therefore cannot move a production channel. v5 still gets its ownv5-latesttag βLONG_LIVEDis"v2 v4 v5 mk dd".Anchor verification (merged tree)
Standing security anchors β all present and non-empty:
MintSSOTokenverifyHeartbeatBearerderivePerHiveKeySecretFilePathAllowedmintHubUserCookieValueV2Behavioral anchors:
CONTRIBUTOR_MODE_MARKERconstant, no env indirectionbin/gh-wrapper.sh:32βCONTRIBUTOR_MODE_MARKER="/etc/hive/contributor-mode", a literal_extract_authorlast-value semanticscontinue, never an earlybreakβ last--author/-Awinsbin/gh-wrapper.sh:49βBOT_LOGIN_FILE="/var/run/hive-metrics/agent-tokens/gh-bot-login", a constant;HIVE_GH_WRAPPER_BOT_LOGIN_FILEhonored only under theREAL_GHtest harnessagent-env-scrub.sh+ BASH_ENV wiring (#4048)GITHUB_TOKEN/GH_TOKEN/β¦; exported atbin/agent-launch.sh:229; interactive-shell install atsrc/Dockerfile:422cd-prefixed launches, 3 spawn sites (#4046)bin/contributor-relay.sh:1776,bin/contributor-agent.sh:582, plus dashboard site covered bysrc/pkg/dashboard/contribute_pane_cwd_test.goupgradeCollectiblegates (#4997)hub/saas.go:6073,6167,6255,hub/server.go:3834,hub/stale_upgrade.go:416s.uncollectibleUpgradeNotedmap guarded bys.uncollectibleUpgradeMuinhub/pullonly_upgrade.goβ a HubServer field, not a globalsrc/pkg/delegation/+ observe-only invariant testobserve_only_test.gocarriesTestObserveOnlyInvariant_NoEnforcementConsultsChainandTestObserveOnlyInvariant_MinterHasNoDecisionAPIBuild / test evidence
go build ./...β cleango vet ./pkg/turn/... ./pkg/toolapprove/... ./pkg/hooks/... ./pkg/review/... ./cmd/hive/... ./pkg/config/... ./pkg/hub/... ./pkg/agent/...β clean (this is what caught thepkg/turnsemantic conflict)go test ./pkg/review/...β ok (the flagged high-risk package: v5's reviewer-role grounding and v4's coverage-floor tests coexist)go test ./pkg/turn/...β okgo test ./pkg/toolapprove/...β okgo test ./pkg/hooks/...β okgo test ./pkg/config/...β ok (17.7s)go test ./cmd/hive/...β ok (11.9s)go test ./pkg/hub/...β ok (121s)go test ./pkg/delegation/...β okgo test ./pkg/dashboard/ -run 'PinsPaneWorkingDirectory|Cwd'β okbin/gh-wrapper.test.sh,bin/test_gh_wrapper_gates.sh,bin/test_agent_env_scrub.shβ all passNote on the two
pkg/configtests expected to fail pre-existing (TestExportEmitsShellExportOnBothPaths/warm_cache,TestSharedCacheIsCreatedPrivate): both PASS on the merged tree β v4's incoming hermetic-test wave fixed them. No failure was ignored on this branch.gofmt: all resolved files are clean.pkg/turn/operations.goremains unformatted, which is pre-existing onorigin/v5and untouched by this merge.Follow-up commit:
e4b6f398β release-line guardCI surfaced one genuine merge-induced failure,
release-lines-in-sync, fixed ine4b6f398rather than waived.v4's release-line carry-forward guard (#4405) arrived in this merge declaring
release_lines: [v2, v4], and met v5's workflow triggers which already listv5. Neither parent fails on its own β v5 has no guard (.github/workflows/release-line-guard.ymland.github/release-lines.ymlare v4-only), and on v4 no workflow namesv5β so the merge is precisely what surfaces it. That is the condition the guard exists to catch, so waiving it would have defeated its purpose.Fixed per the manifest's own three-edit instruction:
release_lines: [v2, v4]β[v2, v4, v5]v5to the pinned workflows missing it:go-security-analysis.yml,podman-contract.yml,podman-rootful-lane.yml,podman-rootless-lane.yml,podman-arm64-lane.yml,quadlet-gate.yml. (v2-ci.yml,v2-tests.yml,suid-contract.ymlalready had it.)env_lists:docker.ymlLONG_LIVEDalready carriedv5, so no edit.scorecard.ymlis recorded as an exclusion ([main, -v2, -v5]), deliberately not widened:scorecard-actionhard-fails on any non-default branch (refs/heads/v5 not supported with push event), which is why90dea447droppedv5from its trigger in the first place. The manifest's design is that an exclusion must be written down and justified rather than left as a silent gap.One more thing the guard's own self-test needed:
test-release-lines-guard.shbuilt its "plausible future release line" fixture by addingv5to the real manifest. Oncev5became a declared line that fixture was a no-op β the demonstration would have kept passing while asserting nothing, the exact green-forever failure mode the guard was written to prevent. Moved the fixture tov6so the negative control still proves the guard catches a stale allow-list.Verified locally:
src/scripts/check-release-lines.shβ PASS,src/scripts/test-release-lines-guard.shβ PASS.Also folded in:
go test ./pkg/agent/β ok (586s). The tmux tests pass; the socket-path errors noted as expected were avoided by using a short clone path (/tmp/hv/s2).CI
release-lines-in-syncis green aftere4b6f398, and the workflows that fix newly cover v5 (podman rootless/rootful/arm64 lanes, quadlet gate, gosec, govulncheck) all pass on this branch.One check is red and is pre-existing on v4, not merge-induced:
NOTICE matches the module graphfails identically on the six most recentorigin/v4commits, includingd3e2b5a5β this merge's own v4 parent. It is not a required status check onv5(the only required context isgate, which passes). Verified before ignoring rather than assumed.Merge verification:
pkg/turnandpkg/toolapproveare byte-identical toorigin/v5, and 196 sampled v4 production files acrosspkg/hub,pkg/proxy,pkg/scheduler,pkg/tokens, andpkg/rotationare byte-identical toorigin/v4β so no v4 fix was regressed by a stale v5 copy, and no RFC surface was lost. The reviewer role's files (grounding_test.go,withhold_test.go,policies/reviewer-advisory.md) and all threeOnDemandgovernor gates survived intact.Follow-up commit:
0ae23929β CSP inline handlers (a second semantic conflict)CI's
test (rest 2/3)shard caught a second semantic conflict, of the same shape as thepkg/turnone and equally invisible to git.TestEmbeddedStaticHasNoInlineEventHandlers(#4680) reported 7 inlineon*=handlers instatic/index.html. v4 tightened CSP toscript-src-attr 'none'and added that guard; v5's RFC #4000 approvals panel was written with inline handlers. The panel's HTML is v5-only and the guard is v4-only, so the two sides never touched the same lines and git merged them without a murmur β but the merged result would have shipped an Approvals panel whose every control is dead under the new policy: buttons render, clicks do nothing, and nothing reports an error. Exactly the failure mode #4680 exists to prevent.Converted all 7 to the
data-actiondispatch idiom rather than weakening the test or relaxing the CSP:data-action="toggleSection" data-arg0="approvals-section"β verbatim the idiom used by the siblingbeads-section/platform-sectionheadersdata-arg-types="j", preserving thenullsentinel that distinguishes "all" from a named ruledata-change-action="approvalsToggle"β thechangelistener'sdefault:branch already routes tohiveDispatchActiondata-arg-types="s,j", id escaped throughescapeHtml, matching therepo-pr-pillprecedentNeuter-test (the test is not green by construction): re-adding a single inline
onclickputs the guard back to red, reporting exactly 1 handler; restoring the fix returns it to green. So the guard still detects what it claims to.go test ./pkg/dashboard/β ok (108s), full package.The
testjobtestis an aggregator that rolls up the shards (list-packages,test-hub,test-agent,test-rest) and fails if any shard failed. Its log showstest-rest: failureas the only non-success input β so it was the same CSP failure surfacing, not a distinct problem, and it clears with0ae23929.NOTICE matches the module graphβ pre-existing, and not a stale NOTICEThis one is not a dependency-graph change from the merge, and regenerating NOTICE cannot fix it. The job's own log shows the generator aborting before it writes anything:
github.com/fumiama/go-docxis pinned at the same version on both parents (v0.0.0-20250506085032-0c30fd09304binorigin/v4:src/go.modandorigin/v5:src/go.mod), so the merge neither introduced it nor changed its version. The identicalFORBIDDEN β¦ go-docxabort appears on cleanorigin/v4atd3e2b5a5β this merge's own v4 parent β and on the six most recent v4 commits. It is a pre-existing licensing issue on the release line, out of scope for a sync PR, and it is not a required status check onv5(the only required context isgate, which passes).src/pkg/review/β the flagged high-risk packageIt did not conflict, and that was checked rather than assumed. The two sides touched disjoint files: v4 added
artifact_roundtrip_test.goandquality_gap_test.go(its coverage-floor work), while v5's #5002 modifiedprompts.goand addedgrounding_test.go+withhold_test.go. No shared file, hence no conflict.Because a clean merge is not evidence, the package was built and run on the merged tree:
go build ./pkg/review/β OKgo vet ./pkg/review/β OKgo test ./pkg/review/β ok, 40 tests pass, with both lineages' tests executing: v5'sTestPromptDirectsReviewerToReadRepo,TestPromptCarriesMergeBaseCommit,TestPromptGroundingDegradesWithoutMergeBase,TestFalsePositiveDisciplineIsInThePrompt,TestNonApproveVerdictsNeverAuthorizeMerge,TestAbsentVerdictDoesNotAuthorizeMerge,TestPartialPerspectiveCoverageIsNotApprovalalongside v4'sTestCollectAndWriteRoundTrip,TestWriteArtifactUnwritableDir,TestLoadArtifactErrors,TestSeverityRankOrdering, and the rest.The reviewer role's wiring also survived intact:
policies/reviewer-advisory.mdkick template, thereviewercadence entry, and all threeOnDemandgates inpkg/governor/governor.go(which auto-merged) βgo test ./pkg/governor/ok.