π sync: top up v5 with latest v4 - #4058
Merged
Merged
Conversation
β¦4027) Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦GitHub API from unit tests, flaking the coverage gate (#4028) The 2026-08-17 22:02 Coverage Hourly run at bb2d554 reported 'hub: TESTS FAILING': TestListRepoBranches, TestListRepoBranchesError and TestFetchBranchSHAImageReady each died at exactly 2.00s in waitForCommitOrderResolvers ('timed out waiting for 1 commit order resolver(s)'). The next hourly run β with only docs commits in between β was green, so the base branch was not actually broken. Root cause: commitAtOrAheadOfTarget (heartbeat completion at server.go:1911/2289, the orphan sweep at stale_upgrade.go:183, and triggerAutoUpgrades at saas.go:5315) kicks a deduplicated BACKGROUND resolveCommitOrder goroutine whose default fetchCommitCompareStatus does a real HTTPS call to githubAPIBase with a 10s commitCompareTimeout budget. Any earlier test that crosses one of those paths with an unresolved SHA pair β without stubbing the fetcher β leaks that goroutine. One slow call outlives three consecutive 2s waits: the failure timestamps in the red run (+2s, +4s, +6s, then the 4th sha-poll test passing) match a single leaked resolver draining within its 10s budget. Fix at the seam rather than per test: TestMain now installs an instant offline fetcher (error return β errors are never cached, so semantics are exactly 'resolution pending', the fail-closed default; an unknown pair still never reads as complete). Tests that need definitive ancestry answers already stub via stubCommitCompare or seedCommitOrder and are unchanged, as are the unresolved-ancestry regression tests, which already stubbed an erroring fetcher. The real fetcher's HTTP behaviour stays covered: TestMain captures it in realFetchCommitCompareStatus and the new TestFetchCommitCompareStatusHTTP exercises ahead/non-200/empty-status/bad-json against a local httptest server β its first direct coverage; previously it was only reachable over the real network. Fixes #4022 Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦ocs for standalone OpenShift (#4035) A standalone (hub-less) adopter on a firewalled OpenShift cluster hit the question the manual-provisioning guide could not answer: how do I know if my cluster grants NET_ADMIN? Auditing the kustomize path showed the gap is real: the base deployment requests capabilities.add (NET_ADMIN + the su-exec set), and NO stock SCC β anyuid included, which is all overlays/openshift binds β permits adding capabilities, so on a stock OpenShift cluster the pod is rejected at admission. The managed fleet works because the hub provisions a dedicated hive-netadmin SCC out-of-band; nothing in-tree encoded that pattern for standalone installs. Manifests: - NEW overlays/openshift-netadmin: dedicated hive-netadmin SCC β restricted-v2 loosened by EXACTLY what the base deployment declares (the six added caps, RunAsAny uid for the root entrypoint, RunAsAny fsGroup for the pinned 1002, allowPrivilegeEscalation for the SUID su-exec helper; drop-ALL and runtime/default seccomp still required; NOT privileged, no host access) + use-RBAC (ClusterRole/RoleBinding hive-netadmin-scc) scoped to the hive SA, matching the fleet naming. Grant-only overlay (does not include the workload base) so the one-time cluster-admin action is decoupled from app deploys; clearly commented cluster-admin-only and OpenShift-only. Base stays cluster-agnostic. - deployment.yaml: comment at capabilities.add pointing at the overlay and the HIVE_PROXY_ADVISORY_OK degraded-mode alternative. - overlays/openshift + standalone kustomization/patch-advisory-mode/README: cross-reference the new overlay; correct the stale exit-1 claim (post-#3879 the missing-capability FATAL exits 77/EX_NOPERM); split the DENIES case into its two real signatures (admission rejection vs runtime exit 77). Docs (authoring source; site copy auto-syncs from here): - manual-provisioning.md: new 'Does your cluster grant NET_ADMIN?' section β scc-subject-review dry-run of the rendered overlay, which-SCCs-allow + who-can query (with the plain-k8s Pod Security equivalent), 30-second probe pod, and the exit-77 signature and how to read it; the NET_ADMIN decision now lists both remedies (cluster-admin applies the overlay for the full gate, or HIVE_PROXY_ADVISORY_OK=true with the honest trade-off: forced proxy egress becomes advisory). 'On OpenShift' now names the SCC grant as a required platform delta. - net-admin-requirement.md: OpenShift section (declaring the cap is not enough; apply the overlay) + Pod Security note for plain k8s. Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦ask prompts were typed into a bare shell Observed live on an agy contributor: the CLI exited, the relay never noticed, and the hub's task prompt was typed into the shell the pane had fallen back to β the terminal showed the prompt running as shell commands, its first characters eaten by readline. This is exactly the wedge the send gate was added to prevent (#2203, bug 2); the gate was intact, but everything upstream of it was blind. The probe was a substring match over the WHOLE process table: procs.includes(BACKEND) || procs.includes('claude') || procs.includes('copilot') || procs.includes('bob') || procs.includes('codex') || procs.includes('goose') || procs.includes('pi') Two independent defects, both reproduced on the affected machine: 1. The relay's OWN machinery carries the backend's name. For agy both `just contribute-hive agy local` (the launcher) and `tmux attach -t hive-agy-5b4f` (the session) contain "agy", so the probe was pinned alive no matter what happened to the CLI. It was structurally incapable of reporting a death for this backend. 2. The other CLI names were OR'd in unconditionally, whatever BACKEND was. Any contributor with Claude Code running β most of them β reported a live CLI for every backend, forever. With the probe stuck true, relaunchCLI() never ran, so cliReady β a LATCH, cleared only by a relaunch β stayed true, and the send gate waved the prompt through into bash. Fixes, either of which alone would have stopped it: * Liveness now asks the PANE what it is running (tmux pane_current_command), which nothing outside the pane can influence. A death requires CLI_GONE_CONFIRMATIONS consecutive shell readings, so a foreground tool call cannot be mistaken for one β the expensive error, since it restarts a working CLI. The pane TEXT is deliberately not consulted: a dead CLI leaves its last frame on screen, ready-chrome included, so requiring that chrome to be gone would re-introduce the same blindness. * The send gate re-confirms against the live pane instead of trusting the latch. A pane at a shell prompt queues the prompt, drops the stale latch and relaunches the CLI, so the queued prompt has somewhere to flush to. Also: a pane sitting at a shell is no longer allowed to reach the completion check. Before the death is confirmed, the dead CLI's last frame would classify as "agent idle" and report a task nobody performed as COMPLETED. bob is exempt β it exits at the end of every turn, so for bob a shell pane IS the completion signal. Crash-detection tests now tick twice per assignment, which is the honest consequence of requiring confirmation before declaring a death. Signed-off-by: Douglas Baggett <doug.baggett@gmail.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
β¦y repo count (#3898) Queue depth scales with how many repos a hive watches; the mode thresholds did not. A fixed surge=20 therefore encoded an implicit repo count: a 39-repo hive carrying a queue in the low hundreds sat in SURGE permanently, while a 3-repo hive with the same numbers idled through a backlog that was genuinely deep per repo. The DEFAULT thresholds are now per-repo bases multiplied by len(project.repos), so surge means "20+ items per repo" at any hive size. A one-repo hive gets exactly the numbers it got before. governor.threshold_scaling picks the curve: linear (default, equivalent to comparing per-repo pressure), sqrt (gentler, x ceil(sqrt(repos))), or none (absolute, the pre-change behavior). An unrecognized value is rejected at config load. An explicit governor.modes.<mode>.threshold is used verbatim and never scaled, so hand-tuned hives are unaffected. Because explicit values do not scale, mixing them with scaled defaults can leave busy above surge and make a mode unreachable; the governor warns with all three effective values rather than silently reordering a number the operator set. The warning is deduplicated on the ladder it describes, so an unchanged inversion does not spam the log on every reload. Threshold resolution moved to config.EffectiveThreshold, shared by the governor and the dashboard gauge. The gauge previously kept its own copy of the defaults β harmless while both were constant, but it would have explained the current mode with numbers that did not produce it. Known limitation, documented in docs/governor-thresholds.md: ACMM packs seed explicit thresholds, and nothing distinguishes a pack-seeded default from a hand-tuned value, so scaling does not engage on a hive that applied a level. Rebased by maintainer onto v4 after the v2/ -> src/ rename (#3996): paths and import prefixes rewritten mechanically, docs/README.md link style resolved to the current absolute-URL convention, and an identity-at-one-repo test added covering all three scaling curves. Refs #3498 Signed-off-by: Douglas Baggett <doug.baggett@gmail.com> Signed-off-by: Andy Anderson <andy@clubanderson.com>
π fix(relay): the CLI-liveness probe could never see a dead CLI, so task prompts were typed into a bare shell
β¦th a deleted cwd killed every agy task
An agy contributor's CLI died a few seconds after EVERY task delivery: it
started fine, sat at its prompt, the relay reported "CLI ready" and "Task
prompt sent to CLI", and then it was gone. No crash, no log, no message. The
pane fell back to a shell and the next prompt was typed into it.
It was not agy, the relay, the prompt, the keystrokes, the task, the account
or the quota β each was ruled out by direct test, including driving agy by
hand through the relay's exact key sequence (Escape, C-a, C-k, the literal
1100-char prompt, three Enters) and watching it complete a whole hive task.
The pane's own working directory was gone:
pane_current_path = '.../hive/kubestellar/hive/v2/pkg/agent (deleted)'
That path belonged to a nested clone and was orphaned when the repo renamed
v2/ -> src/. The tmux SERVER had been holding it since a Go test created the
server hours earlier, so every pane it forked started there. The shell said so
each time ("shell-init: error retrieving current directory"), and agy β which
needs a resolvable cwd β exited 2 about thirty seconds in. A wrapper around
the launch captured AGY_EXIT=2, which ruled out any signal: nothing was
killing it. claude, codex and goose tolerate a dead cwd, so the same server
looked healthy for them and the fault read as agy-specific.
The launch command now cds into the repo first. `tmux new-session -c <path>`
is NOT sufficient on its own β verified: on a server whose own cwd is gone,
new-session with an explicit, valid -c still forks the pane into the deleted
directory β so -c is passed as defense in depth for a healthy server while the
cd carries the fix.
relaunchCLI() gets the same prefix. It rebuilds the launch line on every
restart (memory-cleanup restarts, crash recovery, a stale readiness latch), so
without this the first relaunch would silently undo what the Justfile pinned.
And because a silent exit is what made this cost an evening, the recipe now
inspects pane_current_path after creating the session and, when it is deleted
or unreadable, says so and points at `tmux kill-server` β the only thing that
truly clears a poisoned server.
Tests pin both halves: the relaunch command carries the cd (and reaches tmux
that way), and a Go test reads the JUSTFILE rather than restating it, so the
cd, the -c and the warning cannot regress quietly. Docs get a troubleshooting
entry keyed on the symptom, since the failure gives you nothing to search for.
Signed-off-by: Douglas Baggett <doug.baggett@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#4046 fixed the tmux-server-holds-a-deleted-cwd failure for the Justfile's local-mode recipe and contributor-relay.sh's relaunchCLI(), but left bin/contributor-agent.sh β the DEFAULT docker/container-mode entrypoint (`just contribute-hive` defaults to mode="docker") β uncovered. That entrypoint launches the CLI the identical way and is exposed to the identical bug: it relied on `tmux new-session -c "$HIVE_WORKSPACE_DIR"` alone, which #4046's own investigation already showed does not rescue a pane once the tmux server's own working directory is gone. Mirror #4046's mechanism here: cd into a directory as part of the literal tmux send-keys launch line, the same way the Justfile now does. The cd target is NOT $HIVE_WORKSPACE_DIR β that is the exact directory agents clone per-task repos into (contribute_ws.go's assignment prompt), so pinning the launch there would recreate the #4046 trap the moment that directory is ever removed and recreated under a still-running server. $HOME is the durable anchor: nothing in the task lifecycle deletes or recreates it, so the CLI always starts somewhere resolvable, and its own per-task cd into $HIVE_WORKSPACE_DIR/<repo> is unchanged. mkdir -p on that cd target is belt-and-braces in case $HOME itself is ever unusual in a given image. Extends the #4046 Go regression test with the same read-the-source-not-a- restatement approach, now covering both spawn sites, and asserts the container-mode fix does NOT cd into $HIVE_WORKSPACE_DIR. Signed-off-by: Douglas Baggett <doug.baggett@gmail.com> Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦ may initiate work on (#4040) * β¨ feat(config): project.issue_filter β label-gate which issues agents may work A fleet owner running hive against a busy upstream expected agent work to be gated on an approval label (their issues route through a queue label; the hive scanner nevertheless opened a PR for an issue carrying only a triage label). Investigation confirmed the gate they assumed has never existed on any branch: - The hive's own agents have never had a require-label option. The only label allow-list keyed config (hub.contribute_labels_mode: "allow" + list) gates exclusively the /contribute queue handed to external contributors β it never gated the hive's own scanner, and it still works (contribute_filter_test.go). - The agent side has only exclusion mechanics: governor.labels.exempt, hold/on-hold, do-not-merge β all enforced in fetchIssues. So an operator could label-route work to contributors and still have the hive's own agents eligible for every other open issue. This adds the missing agent-side gate: project: issue_filter: require_labels: [approved-for-agents] # only these are agent work exclude_labels: [no-ai] # these never are Semantics: absent/empty = current behavior EXACTLY (no default-on filtering β that would idle every existing hive); require admits on any-of, exclusion wins over require; matching is case-insensitive and exact (prefix matching would over-admit through an approval gate). Enforced at THE choice point β github.Client.fetchIssues, where issues become the actionable set β the same layer as the hold/exempt checks and upstream of the #3792 claim guard, so governor counts, every kick, plan-from-label, and the contribute queue all inherit it and a prompt-injected agent re-listing the repo cannot bypass it. Kick prompts additionally state the active policy (prepended to ${ISSUE_LIST} and the hardcoded scanner/generic builders) so agents are never told to go find excluded issues. The filter rides HeartbeatProjectConfig like org/repos (nil = keep the spoke's own filter, so the hub's every-beat echo can never blank it β the AIAuthor lesson), and the dashboard shows the active filter read-only under Repositories. Tests: require admits/refuses with positive controls and a count floor, exclude wins over require, absent config = unchanged enumeration (regression pin), PRs and the Hold list untouched, empty-list kick still carries the policy, unconfigured kick output byte-identical, nil-receiver setter safety. Signed-off-by: Andy Anderson <andy@clubanderson.com> * β»οΈ refactor(issue-filter): one label-policy story β drop exclude_labels, edit require_labels on the Labels tab Design-coherence pass: the dashboard's Governor Configuration β Labels tab (governor.labels.exempt + permanent hold/do-not-merge) already IS the exclusion mechanism, enforced at the same fetchIssues choice point. Shipping issue_filter.exclude_labels would have been a second, disjoint exclusion system. Dropped it: project.issue_filter now carries ONLY the genuinely-new require_labels allow-list, and the exempt list remains the sole exclusion story. Precedence is by construction β fetchIssues applies hold/exempt before the require gate, so an issue carrying both an exempt label and the approval label stays excluded (pinned by TestEnumerateActionable_ExemptWinsOverRequire). The require list is editable on the same Labels tab: a clearly separated "Required labels (allow-list)" section with the polarity explained, riding the tab's existing add/remove plumbing. The PUT /api/config/governor/labels body fields are now pointer-typed so a save touching one polarity cannot blank the other. Docs rewritten to present both polarities side by side: exempt = "never touch these", require = "only touch these; empty = everything" β and that require is what "only work approved issues" means. Signed-off-by: Andy Anderson <andy@clubanderson.com> --------- Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦t/create retry unlabeled; per-repo ensure cache; fail loud on empty token cache (#4043) (#4047) Live fleet-owner report: agents 'dead in the water β permissions issues' on the gh wrapper. Root cause chain, all in bin/gh-wrapper.sh: 1. issue/pr edit appended --add-label agent/<name>,hive/<id> with no fallback; a missing label on the target repo failed the ENTIRE edit with "'hive/<id>' not found". Retry unlabeled, like create intends. 2. The create arm's unlabeled retry was DEAD CODE: under set -e the bare failing gh exited the wrapper before rc was read. || rc=$? in both arms. 3. _ensure_labels cached one global /tmp flag for the whole pod session: the first-touched repo got the labels, every other repo was skipped forever (verified live: exactly the un-ensured repos were broken). Key the cache per target repo. 4. An empty (pod rolled, token not yet re-minted) or unreadable token cache passed the -f check and exported an EMPTY GH_TOKEN β every call went out unauthenticated instead of the gate's promised fail-loud. Check -r and -s too. 5. The token-access audit append leaked the shell's own 'Permission denied' into stderr on EVERY gh call when the log dir is unwritable (2>/dev/null did not cover the redirection). Group the redirect. Behavioural tests: a stub gh that rejects label-injection flags pins that edit/create retry unlabeled and succeed, and that an empty token cache blocks loud. Fixes #4043 Signed-off-by: Andy Anderson <andy@clubanderson.com>
π fix(contribute): pin the CLI's working directory β a tmux server with a deleted cwd killed every agy task
β¦nt tool shells (#4048) Agent CLI backends re-export their own live GitHub credential as GITHUB_TOKEN into every shell they spawn for tool calls β after all launch-path scrubbing (#3931) has already run. Observed live on a Copilot-backed fleet: a wrapper-denied agent fell back to raw curl -H "Authorization: Bearer $GITHUB_TOKEN" and succeeded at a repo write, bypassing every gh-wrapper control (#3854 allowlist, mode/ACMM gates, merge eligibility, authorship routing, provenance). Fix at the only boundary that sees the re-export β CHILD shell startup: - bin/agent-env-scrub.sh (new): POSIX-safe unset of GITHUB_TOKEN, GH_TOKEN, GH_ENTERPRISE_TOKEN, GITHUB_ENTERPRISE_TOKEN, COPILOT_GITHUB_TOKEN, GITHUB_COPILOT_TOKEN, HIVE_GITHUB_TOKEN. - agent-launch.sh exports BASH_ENV/ENV pointing at it, so every non-interactive shell a backend spawns (including ones handed the token explicitly in the spawn env) scrubs itself before the agent's command runs. - Dockerfile ships the scrub and adds an /etc/bash.bashrc guard for interactive shells, gated on agent identity env so operator shells are untouched. The backend PROCESS keeps its own auth (it is not a shell and never sources the scrub): Copilot API auth via COPILOT_GITHUB_TOKEN and the opt-in app_authored_prs MCP token are unchanged. gh-wrapper and git-credential-hive keep authenticating from HIVE_AGENT_TOKEN_CACHE (a path, deliberately not scrubbed). bin/test_agent_env_scrub.sh (wired into v2-ci) replays the incident shape, asserts nested re-exported tokens are re-scrubbed, includes a positive control proving the probe can see a leak, a sanctioned-path control proving the wrapper still authenticates from the per-agent cache, and source-level drift guards. Residual: deliberate /proc/<pid>/environ extraction by a same-uid agent remains until proxy-side Authorization strip/injection (#1861) is enabled; this change composes with that work rather than replacing it. Fixes #4045 Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦usted bot-identity file replaces the /user oracle App installation tokens can never satisfy (#4044) (#4049) #3982 hardened _resolve_self_login to resolve identity EXCLUSIVELY via 'gh api user' (fail-closed, env untrusted). Correct anti-spoof goal β but staff agents authenticate with App INSTALLATION tokens (ghs_β¦), and GitHub's /user endpoint structurally 403s for server-to-server tokens: they have no user identity. So every author-gated list ('gh issue/pr list --author β¦', including @me, which the API equally cannot resolve) failed closed for ALL staff agents, always, pushing agents toward raw-API bypasses of the wrapper. Fix β keep the anti-spoof property, provide a legitimate oracle: - The hive process, which MINTS every tier token and therefore knows the App bot login ('<app-slug>[bot]', cfg.GitHub.BotLogin()), now publishes it to /var/run/hive-metrics/agent-tokens/gh-bot-login on every mint/refresh (AppAuth.publishBotLogin, wired via NewClientFromAppWithBotLogin β SetBotLogin so every reinit path is covered). Atomic tmp+rename, 0644: the login is public metadata; the security property is UNWRITABILITY β the dir is dev-owned 0755 and group 'node' (every agent) has no write bit, now asserted explicitly in entrypoint.sh. - gh-wrapper.sh's _resolve_self_login reads that file first (constant path, #3249/#3982 pattern β an env-selected path would let an agent point the gate at a file it controls), falling back to 'gh api user' so the contributor/user-token path is unchanged. Neither oracle resolving still fails closed; agent env is still never consulted. - '--author @me' is rewritten to the trusted identity for staff agents β @me has no server-side meaning for an installation token, so without the substitution there is no working self-listing form at all. Tests: gate harness drives the wrapper with a stub that reproduces the installation-token 403 β self-listing works via the trusted file (explicit login, bare slug, @me both forms), foreign authors still refused, missing/empty file still fail-closed, env-var identity still refused (#3982 positive control), user-token oracle unchanged. Go tests pin that WriteAgentToken publishes/refreshes the identity file, keeps it 0644, and publishes nothing when no usable App is configured. Fixes #4044 Signed-off-by: Andy Anderson <andy@clubanderson.com>
β¦reach existing hives (#4041) (#4051) Every hive that ever saved its config had the era's DEFAULT login_patterns written into the persisted file as explicit values: applyDefaults() fills the list on load and Save() marshals the whole struct, so the defaults looked operator-chosen. Because defaults only apply to an empty list, the #3959 defaults fix (generic English phrases -> CLI login chrome) never reached those hives β on a hosted hive the quality agent flapped on `(?i)copilot auth` for days (restart_count 83 at first trip), and both hives inspected in #4041 carry the pre-#3959 generic list pinned in /data/hive.yaml.runtime. Three-part fix, operator intent preserved throughout: - Migrate on load: a persisted list byte-identical to the frozen pre-#3959 default set (values AND order) expresses no operator intent β drop it with a clear log line so the corrected code defaults apply. Any deviation, even one entry removed, means customized: preserved verbatim, never touched. - Fix the materialization source: redactedForPersist() now persists a default-equal list as absent, so future default fixes reach existing hives instead of being pinned out forever. Customized lists persist verbatim. - Guard the legacy state-overrides replay (sensing_login in hive-state.json) with the same byte-identical check so it cannot re-pin the old list over the corrected defaults after the config-file migration. Tests: legacy list -> migrated to current defaults (the frostyard fixture, including the `copilot auth` entry); customized lists (fully custom, legacy minus one, legacy plus one) -> verbatim; current-default list -> unchanged on load and dropped on save; save/reload round-trips for both default and custom lists; byte-identical exactness of the legacy matcher; overrides-replay skip plus its positive control. Signed-off-by: Andy Anderson <andy@clubanderson.com>
Merge commit (not squash) so both lineages stay intact on v5. Incoming from v4 (14 commits), notably: - #4047 gh-wrapper label injection never fails the operation - #4049 gh-wrapper author-gate via trusted bot-identity file - #4048 agent-launch scrubs backend-re-exported GitHub tokens - #4046 deleted-cwd pinned across three spawn sites - #4040 project.issue_filter label gate + Labels tab - #4051 de-materialize stale login_patterns - #3898 default mode thresholds scaled by repo count - #4035 openshift-netadmin SCC overlay - #4028 hub commit-order resolve leak (coverage flake) v5 RFC line preserved: pkg/turn, pkg/toolapprove, the RFC design doc, the agent state inventory, and the v5 CI enablement. Signed-off-by: Andy Anderson <andy@clubanderson.com>
Contributor
|
[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 |
β¦where a pause is read (#4041) (#4055) The frostyard incident: an owner deliberately paused all 8 staff agents via the dashboard, and three days (and four auto-upgrade restarts) later the fleet read as a systemic malfunction β the audit trail had the actor all along, but nothing the dashboard, the hub fleet view, or the boot log renders carried it, and the boot line inflated the count with the always-startup- paused on-demand brainstorm ("restoring 9 paused agent(s)" for an 8-agent quiesce). - Agent state carries PausedBy: Manager.PauseBy(name, trigger, reason, by) records the acting user (Pause delegates with empty β a system pause never fabricates an actor); Resume clears it; snapshot()/persist/state-restore round-trip it so provenance survives restarts instead of downgrading to anonymous. - Dashboard: /api/pause records the authenticated X-Hive-User (same resolution the audit log has always used, now shared via requestUser); the agent payload carries pausedBy, and the paused-agent tooltip leads with one human sentence β "paused by bketelsen via dashboard, 3d ago" / "login-detector: login required detected, 2h ago". - Hub: AgentSummary rides pausedTrigger/pausedReason/pausedBy/pausedAt over the heartbeat (omitempty β zero cost on non-paused agents), sanitized at ingest like every other spoke-reported field; the My Hives Agents tooltip renders the provenance line per paused agent. HIVES_CACHE_VERSION 2 -> 3 (agent rows changed shape). - Boot log: "restoring N paused agent(s)" now excludes agents that are startup-paused BY DESIGN (on-demand, e.g. brainstorm β reported separately) and breaks the restored count down by trigger, so a fleet owner reading logs sees "restoring 8 paused agent(s) (dashboard-api: 8); 1 on-demand agent(s) startup-paused by design" instead of an alarming bare 9. Tests: PauseBy records the actor (positive control) and Pause leaves it empty; Resume clears the whole provenance set; seed/persist/restore replay the actor; the API pause path records X-Hive-User (and falls back to "local"); the agent payload carries and clears provenance; the boot-line helper excludes by-design agents (config- and pack-on-demand), breaks down by trigger with a deterministic order, counts provenance-less agents as unknown, and never leaks the by-design count into the headline; hub summaries carry provenance and omit all four fields on non-paused agents. Signed-off-by: Andy Anderson <andy@clubanderson.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com> #4055 landed on v4 mid-sync. No conflicts; touches no RFC package.
Contributor
|
Thank you for your contribution! Your PR has been merged. Check out what's new:
Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey |
clubanderson
added a commit
that referenced
this pull request
Aug 18, 2026
v5 moved twice under this branch: the v4βv5 top-up (#4058) merged cleanly, then #4001's state-triggered hooks (#4056) conflicted in config.go. Conflict: both RFCs added a config block and a type to the same regions of pkg/config/config.go. Purely additive on both sides β kept Hooks/HookRule and ToolApproval/ToolApprovalConfig/ToolApprovalRule. Closing the seam #4001 left open: it shipped `enqueue-approval` over a narrow hooks.ApprovalQueue with a NIL sink, so the action reported an unwired-sink error rather than silently dropping approvals, and its doc comment names this PR as the adapter's home. cmd/hive/approvalhook.go is that adapter β a hook-produced approval now lands in the same durable inbox, renders in the same panel, and resolves through the same idempotent path as a sweep-produced one. Two deliberate choices, both tested: - The adapter does NOT re-decide. A hook firing enqueue-approval has already expressed the operator's intent that this needs a human; running it back through Desk.Resolve would let an auto-approve rule silently discard an approval the operator explicitly asked for. (Inbox.Enqueue refuses non-operator-approve verdicts anyway, so re-deciding could only ever drop the request.) - The idempotency key is hook name + transition + scope rather than the default content hash, so a flapping transition produces ONE pending row instead of one per firing, and a resolved approval is never re-raised. Verified after the merge: whole tree builds, toolapprove holds 93.8%, and cmd/hive + toolapprove + hooks + turn + dashboard all pass. The two pkg/config failures are pre-existing on clean v5 (verified by stashing). Signed-off-by: Andy Anderson <andy@clubanderson.com>
This was referenced Aug 18, 2026
clubanderson
added a commit
to gregoryhunt/hive
that referenced
this pull request
Sep 8, 2026
Merge origin/v4 (3834cda) into the v5 implementation branch. 19 commits folded in; both overlapping files auto-merged with zero textual conflicts. The two files touched on both sides β src/pkg/dashboard/api.go and src/pkg/dashboard/static/index.html β carry v4's ad6f0af (stale v2 branch refs replaced with HEAD in URLs) and v5's e1a569b (tool-approval decision point + operator queue). The edits land in disjoint regions, so both sides survive intact; verified by grep rather than by trusting the clean automerge. Notable v4 content landing here: - 20e2bab audit-9 L1/L3: gh-wrapper allowlist comment correction plus a hermetic privateURLResolver seam in BOTH pkg/hub and pkg/dashboard. The fail-closed branch of isPrivateURL (DNS error => treat as private) is intact in both packages. - 3adcfa9 apiproxy client-auth gate separated from upstream-key swap - b285659 / 68feb84 Kyverno exec policy + namespace guard - e29fddd hosted bearer-authenticated API clients - relay fixes 0159fbd / 4b4d561 / 0c2fe30 / 45c4f78 / 9d6f6d0 The reach phase 2a/2b/2c commits and the workspace-cleanup git-clone guard (e081447) were already ancestors of v5 from the prior top-up (hivecommons#4058); their code is re-verified present here. No v5 commit is rewritten: this is a merge, not a squash or rebase, so the merge-base --is-ancestor checks this repo relies on continue to hold. Signed-off-by: Andy Anderson <andy@clubanderson.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Top-up merge of
v4(production) intov5(the RFC implementation line, #4000/#4001/#4002).A merge commit, not a squash β sync PRs preserve both histories, matching the repo's prior v2βv4 and v4βdd top-ups.
Incoming from v4 (15 commits)
login_patternsso #3959 defaults reach existing hives/useroracle App installation tokens can never satisfyproject.issue_filterβ label-gate which issues agents may initiate work onConflicts and resolution
No merge conflicts in either merge (the initial top-up, and the second merge that folded in #4055 after it landed on v4 mid-sync). The two branches' change sets were almost entirely disjoint β exactly one file was touched on both sides:
.github/workflows/v2-ci.ymlbranches: [v2, v4]β[v2, v4, v5], lines 5 and 11); v4 appended a new job step (agent shell token scrub (#4045)runningbin/test_agent_env_scrub.sh). Non-overlapping hunks, so the v5-wins/v4-wins split did not have to be adjudicated β both intents are present in the merged file and were confirmed by grep rather than assumed from git's exit code.No file in the RFC packages (
src/pkg/turn/,src/pkg/toolapprove/,src/docs/design/reentrant-turn-model.md,agent-state-inventory.md) was touched by v4, so the "v5 wins in the RFC packages" rule never had to fire. No shared source file was touched by both sides, so no v4 production fix could be regressed by a stale v5 copy.Anchor verification (on the merged tree)
Standing security anchors β all present and non-empty:
MintSSOTokenverifyHeartbeatBearerderivePerHiveKeySecretFilePathAllowedmintHubUserCookieValueV2Tonight's anchors β all present:
CONTRIBUTOR_MODE_MARKERconstant, no env indirectionbin/gh-wrapper.sh:32βCONTRIBUTOR_MODE_MARKER="/etc/hive/contributor-mode", a literal.gh-wrapper.test.sh:49-51rewrites the constant in a test copy and hard-fails if the literal is gone, so env indirection cannot be reintroduced silently._extract_authorlast-value semanticsbin/gh-wrapper.sh:317β loop over all args withcontinue, never an earlybreak, so the last--author/-Awins; consumed at line 411.bin/gh-wrapper.sh:34-48β path is a CONSTANT;HIVE_GH_WRAPPER_BOT_LOGIN_FILEhonored only under the test harness'sREAL_GHoverride. Gate asserted bybin/test_gh_wrapper_gates.sh:476.agent-env-scrub.sh+ BASH_ENV wiring (#4048)unset GITHUB_TOKEN / GH_TOKEN / GH_ENTERPRISE_TOKEN / GITHUB_ENTERPRISE_TOKEN. Exported atbin/agent-launch.sh:229; interactive-shell guard insrc/Dockerfile:361,378.cd-prefixed launches, 3 spawn sites (#4046)bin/contributor-relay.sh:1397(cd ${shellQuote(cwd)} && ${launchCmd}),bin/contributor-agent.sh:551(cd $(printf %q "$HIVE_AGENT_CWD") && β¦), plus the dashboard site covered bysrc/pkg/dashboard/contribute_pane_cwd_test.go.Verification
go build ./...β cleango vet ./pkg/turn/... ./pkg/toolapprove/...β cleango test ./pkg/turn/... ./pkg/toolapprove/...β okgo test ./pkg/dashboard/ -run 'PinsPaneWorkingDirectory|Cwd'β ok (π fix(contribute): pin the CLI's working directory β a tmux server with a deleted cwd killed every agy taskΒ #4046 regression cover)bin/gh-wrapper.test.sh,bin/test_gh_wrapper_gates.sh,bin/test_agent_env_scrub.sh,bin/test_gh_auth_native_no_cat.shβ all passbin/contributor-relay.test.jsβ pass