Skip to content

🌱 sync: forward-port v4 (v4.12.5…v4.14.1) into v5 - #6046

Merged
clubanderson merged 136 commits into
v5from
sync/v4-into-v5-2026-09-04
Sep 5, 2026
Merged

🌱 sync: forward-port v4 (v4.12.5…v4.14.1) into v5#6046
clubanderson merged 136 commits into
v5from
sync/v4-into-v5-2026-09-04

Conversation

@clubanderson

Copy link
Copy Markdown
Member

Summary

  • Merge origin/v4 into v5 using the same merge-commit top-up style as prior v5 syncs (for example 🔄 sync: top up v5 with latest v4 #5845).
  • Includes v4 changes through the latest fetched origin/v4 tip (v4.16.1) so origin/v4 is no longer ahead of this branch.
  • Preserves v5's split/refactored layout and reapplies v4 intent in the new homes.

Ported PRs / changes

Conflict resolutions

  • cmd/hive/main.go: kept v5's decomposed 114-line entrypoint; moved v4 logic into main_helpers.go, hubwire.go, and state_restore.go.
  • pkg/agent/manager.go: kept v5 split layout; ported provider/start-failure and restart telemetry into split files (provider_error.go, start_failure.go, restart_telemetry.go, manager_poll.go, manager_restart.go, manager_pane_signals.go).
  • pkg/config/config.go: kept v5 split config files; ported overlay source provenance and validation into agent_config.go, agent_overlay.go, load.go, and validate.go.
  • pkg/hub/saas.go: kept v5 split SaaS files; ported leaked namespace detection, channel targeting, image build status/staleness, wildcard TLS, restart reset, and version-absent drift into their split homes.
  • pkg/hub/saas_provision.go: preserved the provisioning template filename for source-reading tests, but split vanity mint budget helpers to keep the v5 size ratchet green.
  • pkg/github/*: preserved v5 package split and applied compatible v4 client/proxy intent without reintroducing config coupling.
  • .github/workflows/docker.yml / image tag scripts: kept v5 channel ownership (edge) and did not add v4 stable/candidate publishing to v5.
  • .github/workflows/v2-ci.yml: kept v5's safer PR runner expression while carrying applicable v4 workflow changes.
  • CHANGELOG.md and release-generated content: kept v5 versions/content; kept changelog fragments from both sides instead of importing v4 release-generated sections.
  • pkg/dashboard/static/index.html: resolved ACMM active view once before tile rendering, preserved repo-scoped labels/tables, and ported terminal handoff auth hardening.
  • pkg/dashboard/server.go: kept v5 imports/wiring while adding inference gateway/terminal auth fixes; bumped the dashboard import ratchet for the new legitimate inferencehealth dependency.
  • pkg/dashboard/webstatic/static_index_test.go: ported v4 static terminal renewal test to v5's webstatic package path.

Validation

  • cd src && go build ./... && go vet ./...
  • cd src && golangci-lint run ./...
  • cd src && go test -race ./pkg/... ./cmd/...
  • bash bin/test_kick_governor.sh
  • bash src/deploy/test_entrypoint_egress_ruleset.sh
  • bash src/deploy/test_entrypoint_iptables_gate.sh
  • bash src/deploy/test_entrypoint_xt_module_preflight.sh
  • bash bin/test_dibs_cutover_preflight.sh
  • bash bin/test_dibs_cutover_verify.sh
  • python3 src/scripts/check-docs-links.py
  • cd dashboard && npm ci --quiet && npm test -- --test-reporter=dot
  • cd src/proxy && npm ci --quiet && npm test -- --test-reporter=dot

Remaining v4 delta

  • git rev-list --count HEAD..origin/v4 = 0 after final fetch.

clubanderson and others added 30 commits September 3, 2026 07:55
Move legacy/v2 hub session-cookie minting and telemetry helpers into test-only code so production binaries no longer compile the forgery-capable HMAC lane while rejection tests keep their fixtures.\n\nFixes #5812\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>


(cherry picked from commit 8e4d93e)

Signed-off-by: Andy Anderson <andy@clubanderson.com>
Fixes #5817. Fixes #5811.

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

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Strip inbound X-Hive identity headers before dispatching public routes, and only restore hub identity when X-Hive-Proxy-Auth proves the trusted proxy path. Add regression tests for forged contributor controls and self-service impersonation paths.

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

reconcileNetAdmin (pkg/hub/netadmin_reconcile.go) was 18.4% covered: the
kubectl get/patch remediation, unreachable-breaker arming, suppressed and
unresolvable cluster skips, and the empty-selection warn branch were all
untested. This is the sweep that once shipped as silent dead code (#2674),
so the selection/remediation paths are pinned with a scripted fake kubectl
on PATH (established pkg/hub test pattern) against saasHivesDir fixtures.
reconcileNetAdmin is now 100% covered.

lookupGitHubInstallationAccount (pkg/hub/gh_setup.go) was 22.2% covered:
the real (non-hook) path's fail-closed missing-App-key branch was untested,
as was the router degrading to the friendly page when the lookup fails.

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>
)

src/pkg/dashboard/branding.go has always honoured two operator-facing env
overrides, and neither appeared in env-vars.md or branding.md. env-vars.md's
parity guard is one-directional, so the omission never failed CI.

Adds both rows to the core runtime table with the real lookup chain, and an
"Overriding the paths" section to branding.md covering the part that surprises
people: HIVE_BRANDING_JSON defaults to branding.json beside the *resolved* CSS
path, so setting only HIVE_BRANDING_CSS silently relocates the strings file too.

The section is explicit about what the code does not do. handleBrandingCSS
reads the resolved path with no ownership, mode, or size check; the default
directory sits on the same volume as agents_dir, so an agent-writable /data is
a CSS-injection path into the operator dashboard, and with img-src 'self' data:
https: in force a stylesheet can beacon out. Documented as operator
responsibilities rather than implied safety, with a read-only Secret-mounted
example that keeps the path operator-owned.

Also records why branding and CSP are coupled: setting `mark` rewrites bytes
inside an inline script (the flyer's &#x1F41D; entity), which is why Start()
feeds the served document to setBrandedIndex and script-src-elem hashes are
computed over served rather than embedded bytes.

Fixes #5807

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

Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Route trusted v4 CI jobs to the repository self-hosted runner pool while keeping fork pull requests and risky privileged lanes on hosted runners.

Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* 🐛 bug fix: stop hosted-spoke provisioning leaking namespaces, and report the ones already leaked

A console Live Promote canary found 76 stuck pods — 64 Unschedulable and
12 Pending on unbound PVCs — spread across dozens of leaked
`hive-hosted-hosted-*` namespaces on one CI cluster (#5768). Those are
hosted-spoke provisioning namespaces that were created and never torn
down. They hold quota and PVCs on a shared cluster and put permanent
noise into every health surface that reads pod issues.

Two independent things let that accumulate.

PRODUCTION. provisionHive renders the whole spoke into one manifest and
applies it with a single `kubectl apply -f`. The Namespace is
necessarily the first object — every other object is namespaced into it
— so when the apply fails partway (a rejected Deployment, a missing
StorageClass, an exhausted quota) the namespace has already been created
and kubectl rolls nothing back. provisionHive returns an error, the
caller marks the hive record "error", and the namespace stays forever
with whatever partial objects preceded the failure.

provisionHive now records whether the namespace existed immediately
BEFORE the apply, via `get namespace --ignore-not-found` so that "absent"
and "could not tell" stay distinguishable, and deletes it on failure in
exactly one case: it was absent beforehand. A pre-existing namespace is
left alone — deleting it would cascade to a live spoke's pods and PVCs —
and so is one whose pre-check failed, because "could not tell" must never
be resolved into a destructive verb. The rollback is best-effort and
never changes the error the admin sees.

DETECTION. Every hub-side sweep over hosted namespaces derives its
namespace list FROM the hive registry — reapOrphanedPods walks
listSaaSHives() and calls hostedNamespaceForHive on each — which is what
confines those sweeps to namespaces the hub provisioned, and which makes
a namespace with no registry entry invisible to them by construction.
The stuck-pod signal could not see these either: its predicate requires a
deletionTimestamp, and these pods were never asked to terminate.

Per-cluster health gains `leaked_namespaces`: hive-hosted-* namespaces
the cluster holds that no hive record accounts for, older than six hours,
oldest first, carrying the hive-id label when one was stamped (its
absence is itself diagnostic — the stamp only happens after a successful
apply). It is strictly READ-ONLY. A janitor that reclaims these is
deliberately not in this change: a namespace delete is the widest
destructive verb the hub owns, and the rule that would drive it has never
been measured against a real cluster. This report is that measurement.

The load-bearing safety property is the registry-empty guard.
listSaaSHives() returns nil both when the hub genuinely hosts nothing and
when it cannot read the hive directory at all. Under an empty known set
every hosted namespace in the fleet satisfies "has no registry entry", so
without the guard one transient disk error would report the entire fleet
as leaked — and would hand any future janitor a delete list containing
every live spoke. Collection therefore reports UNKNOWN, loudly, rather
than a report, when the known set is empty; the same nil-means-unknown
contract StuckPods already follows for an unreachable cluster.

The known set is built once per health build from the union of the
on-disk hive records and the in-memory registry, and matched fleet-wide
rather than per-cluster, so a hive whose ClusterID is stale cannot be
convicted of being a leak.

Tests assert counts and executed commands, not field presence: the
predicate is exercised against a population whose leak count is known by
construction and seeded with every shape a looser rule would wrongly
convict (registry-known, Terminating, inside the age window, exactly at
the boundary, unreadable timestamp, not a hive namespace), and the
rollback is asserted against a recording fake kubectl — the harm being
guarded against is an executed delete, not a returned bool.

Signed-off-by: Danathar <doug.baggett@gmail.com>

* 🐛 bug fix: avoid false leaked namespace reports

Skip leaked-namespace reporting whenever the durable SaaS hive record scan fails, even if the in-memory registry still has entries. Otherwise a partial known set can report live hosted namespaces as leaked.

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

---------

Signed-off-by: Danathar <doug.baggett@gmail.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Danathar <doug.baggett@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve display labels for pending access requests and provision decision attribution while preserving raw identity keys for auth actions.

Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the same-repository Go test shards and SUID runtime contract lanes onto the self-hosted runner pool while preserving hosted fallback for forks.

Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* 🌱 ci: run v2 CI and backend-smoke on self-hosted runners

Push/schedule/dispatch-only lanes; no fork exposure. Relieves the
hosted-runner queue by moving the heaviest recurring jobs to the fleet.

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

* 🌱 ci: keep image-heavy jobs hosted; ensure PyYAML on fleet

docker/overlayfs-exec-guard revert to ubuntu-latest (Docker Hub 429 via
shared cluster egress); build-and-test installs PyYAML when missing;
fragment renamed to a valid category.

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

* 🌱 ci: runner image has no pip3 — bootstrap via ensurepip

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

* 🌱 ci: PyYAML bootstrap via apt with get-pip fallback

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

* 🐛 test: model masked selinuxfs in podman host preflight test

The host-preflight test only modeled two SELinux states (readable
enforce, absent selinuxfs). On container runners atop SELinux hosts,
/sys/fs/selinux is mounted but enforce is unreadable — the script
correctly reports 'could not be determined'; the test now asserts that
instead of failing.

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

---------

Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep fleet pending access-request panels expanded across periodic row re-renders by remembering expanded hive ids and reapplying the state as rows are rebuilt.

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

* 🌱 ci: path-filter podman lanes and quadlet gate to src/deploy changes

These lanes probe the published image plus repo deploy scripts; they do
not build PR Go code, so PRs that don't touch src/deploy, the justfile,
or the lane workflow itself gain nothing from running them. Filtering
cuts ~4 hosted-runner runs per PR during queue starvation.

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

* 🌱 ci: rename changelog fragment to PR number

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

* 🌱 ci: fix changelog fragment name format

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

* 🌱 ci: fragment must be a '- ' bullet entry

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

---------

Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* migrate: point GHCR image identity at ghcr.io/hivecommons

Fleet-migration lockstep change (kubestellar -> hivecommons org transfer,
merges only after transfer day): every runtime GHCR literal for the spoke,
hub, and contributor images now names ghcr.io/hivecommons/... — the
ghcrRepoSpoke/ghcrRepoHub constants, the branch-switch and self-patch image
strings, the embedded provisioning Deployment templates, the bulk repo
constant, the dashboard GHCR token/manifest probes, the GHCR tag-list
poller, the Justfile contributor image, the standalone kustomize overlay
and README, and the arm64 lane's documented probe default.

The io.kubestellar.hive.* OCI label prefix in docker.yml is deliberately
KEPT (data-format identifier, not an org pointer) — recorded in a comment
there.

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

* migrate: repoint GitHub API literals and org-gated behavior at hivecommons

The hub's SHA poller (branch list, branch head, docker-workflow runs,
commit message), the commit-order and commit-behind compare URLs, the
/api/reach repo constants, the dashboard's compare/hash/commit-message
calls, and the hub-mode reach client now name hivecommons/hive.

Behavior fixes riding along:
- metrics_collector: the adopters/ACMM/outreach gate now accepts BOTH
  org "kubestellar" and "hivecommons", since pre-transfer hive configs
  still report the old org (commented at the site).
- github client: documented that the search org:/-org: qualifiers take
  the configured org verbatim and GitHub search does NOT follow repo
  transfers (verified both already use the caller-supplied org).
- saas.go pulls-chart tooltip/comment text updated to the hivecommons
  image the counter now tracks (pullPackagePageURL derives from
  ghcrRepoSpoke).

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

* migrate: move frontend and doc deep links to github.com/hivecommons/hive

- Agent-import example/raw URLs now point at hivecommons; the blob->raw
  rewrite handles BOTH github.com/hivecommons/hive and the legacy
  github.com/kubestellar/hive spelling so pre-transfer links keep
  importing (commented at the site).
- Advisory digest footer links now name github.com/hivecommons/hive.
- Wiki getting-started/agents deep links moved to hivecommons.

The kubestellar-hive GitHub App slug copy in index.html is deliberately
untouched: the App is NOT renamed by the org transfer.

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

* 🔁 migrate: install pluk from its new npm home @hivecommons/pluk

The pluk package moved scopes as part of the hivecommons migration
(@kubestellar/pluk is deprecated with a pointer). Same package, same
versions, new name.

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

* 🌱 migrate: add fleet identity changelog

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

* 🐛 fix: keep ACMM on legacy docs during org migration

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

* 🐛 fix: tolerate pluk npm mirror lag

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

* 🌱 migrate: retrigger fleet identity gate

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

* 🌱 chore: refresh NOTICE for fleet identity branch

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

* 🌱 chore: restore deterministic NOTICE source

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

* 🌱 ci: route v2 test matrix for trusted PRs

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

---------

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Manual release PR for v4.8.0 after tagged-release.yml published immutable GHCR tags but could not open its own PR.
Automated release commit. Compiles changelog.d/ fragments and moves
the CHANGELOG.md Unreleased section into a dated v4.9.0 entry.
See src/docs/releases.md.

Signed-off-by: hive-release-bot <actions@github.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…symlink (#5854)

handleBrandingCSS did a bare os.ReadFile and wrote the bytes as text/css
with only nosniff + no-cache. The default path <data>/branding/custom.css
resolves to the parent of agents_dir — the same volume agents write to —
and the dashboard CSP is `img-src 'self' data: https:`, so a stylesheet
dropped there renders in the operator's browser and can beacon out via an
image URL. Safety rested entirely on documentation asking operators to own
the path.

Both branding reads (custom.css per request, branding.json at startup) now
go through readBrandingFile, which refuses a file that is:

  - group- or world-writable — somebody other than the owner can replace
    the served bytes, which is the agent-on-the-shared-volume threat;
  - owned by neither the hive process uid nor root;
  - larger than 128 KiB — reusing customStyleMaxBytes, the cap the ?style=
    feature already applies, rather than inventing a second number.
    Refused outright, never truncated: half a stylesheet is a
    differently-broken page and truncated JSON is invalid JSON;
  - reached through a symlink resolving outside its own directory.

A refusal 404s exactly as an absent file does and logs once (custom.css is
read on every request, so an unguarded Warn would emit per page load) with
the path, the offending mode or owner, and the chmod/chown that fixes it.
The reason never reaches the browser — that would leak the file layout.

Two allowances are deliberate, so hardening is not punished. Root-owned
files pass, because the documented read-only Secret mount is uid 0 /
defaultMode 0444 and the process cannot rewrite it; and symlinks *within*
the branding directory pass, because Kubernetes projected volumes are built
entirely out of links into ..data/ and rejecting them would break every
ConfigMap and Secret mount. HIVE_BRANDING_ALLOW_UNSAFE_OWNER waives the
ownership check alone — group/world-writable has no legitimate shape.

Ownership lookup is split behind unix/!unix build tags, mirroring the
existing pkg/github/fileowner_{unix,other}.go, so off-unix degrades to the
portable mode and size checks rather than failing to build.

branding.md's "Operator responsibilities" section shrinks accordingly: the
three obligations it listed are now enforced, and what remains is the part
the code still cannot do — the guards check who may write the file, not
what is in it.

Fixes #5849

Signed-off-by: Andy Anderson <andy@clubanderson.com>
…5874)

Issues route to one agent by matching lane keywords against the title AND
the labels, and the label side was strings.Contains over the
space-joined label list. Scanner's L4 keyword `fix` is a substring of
`ai-fix-requested` — the label the dashboard's ACMM evaluator stamps on
every maturity-gap issue it files (api_acmm_eval.go) — so EVERY ACMM gap
issue routed to scanner, which is ISSUES_ONLY at L4 and cannot open a
pull request.

Nothing logged an error. The issues were enumerated as actionable
correctly; they simply arrived in the one queue with no ability to act on
them, while quality, ci-maintainer and sec-check never saw them at all —
filterByLane admits an issue to an agent only when issue.Lane ==
agentName || issue.Lane == "". A live 4-repo L4 hive had 11 of 11 ACMM
issues parked there indefinitely. The label whose entire purpose is to
mark an issue AI-fixable was the reason nothing fixed it.

Reproduced by toggling ONLY the label, title and lane table held fixed:

  "[ACMM L4] Add AI security policy" labels=""                      -> sec-check
  "[ACMM L4] Add AI security policy" labels="acmm ai-fix-requested" -> scanner

THE RULE NOW: a keyword or lane name matches a label only as the WHOLE
label, or as a whole "/"-delimited segment of it.

The two separator characters are treated differently on purpose, and that
asymmetry is the whole fix. "/" is a GitHub label NAMESPACE separator —
kind/bug, kind/regression, area/api — so the segment after it is the
label's actual subject, and ci-maintainer's `regression` keyword matching
`kind/regression` is routing working as intended; this package already
relies on that convention (classifyTier tests kind/security by name).
"-" is part of a single label's own name: `ai-fix-requested` is one word
meaning one thing, and it is no more a request about "fix" than
`do-not-merge` is a request about "merge". Splitting on "-" would have
left the bug exactly where it was.

A SECOND BUG THIS CLOSES: the joined-string test let a multi-word keyword
straddle two unrelated labels. Keyword `breaking change` matched an issue
labelled `breaking` + `change` — two separate labels — because the join
put a space between them. Matching per-label makes that impossible.

Titles are untouched and keep substring matching, which is right for
prose: "fix the login crash" should reach the `fix` lane.

LANE ORDER IS NOW DETERMINISTIC. classifyLane is first-match-wins over a
slice that initAgentConfigDrivenSystems built by ranging over cfg.Agents
— a Go map, whose iteration order is randomized per range. An issue
matching two lanes went to whichever came out first, and the winner could
differ between two runs of the same binary on the same config, with
nothing recording which it had been. That is live on an L4 hive:
"[ACMM L4] Add AI fix workflow" matches ci-maintainer's `workflow` and
scanner's `fix`, and those two have different modes, so the coin flip
decided whether the issue could be fixed at all. Sorting by name is a
STABLE order, not a claim about precedence; choosing a deliberate
precedence between colliding lanes is a separate call.

WHAT THIS DOES NOT DO, measured rather than estimated. The fix makes the
ACMM labels routing-NEUTRAL; it does not re-home every ACMM issue. Of the
13 titles in the report, 11 still reach scanner — one on the title
keyword `issue`, ten on DefaultLane, which is "scanner" rather than "",
so an unmatched issue stays invisible to every other agent. The report
raises that as a separate matter and does not propose changing it;
flipping the fallback to "" would make every unclassified issue in the
fleet visible to every agent, which is a routing-policy decision and not
a bug fix. TestACMMLabelIsRoutingNeutral asserts the count of 11 so the
follow-up decision has data attached and moving it is deliberate.

Also not done, and for the same reason: dropping `fix` from scanner's
default L4 keywords, which the report lists as optional. Measured against
the reported issue set it changes nothing (none of those titles contain
`fix`), and it would narrow scanner's triage reach on every L4 hive.

While measuring, one finding not in the report: `quality` is
ISSUES_AND_PRS at L4 and declares NO lane_keywords, so it is absent from
the lane table entirely and can never be routed anything. That is a
second, independent reason the reported issues could not reach it.
TestInitAgentConfigDrivenSystemsKeywordlessAgentsStayOutOfTheTable pins
the build behaviour that makes it so.

Both defects were confirmed to reproduce before the fix: the pre-fix
classifier routes the security-policy title to scanner with the label and
sec-check without it, and the determinism test fails on its first
iteration without the sort.

Signed-off-by: Danathar <doug.baggett@gmail.com>
…5827)

The panel scores how ready a repo is for agentic contribution and reports
one level for the whole hive. That level is the BEST CASE across every
watched repo: evaluateAllRepos marks a criterion passed if ANY repo has
it. A repo meeting none of the 43 criteria therefore does not move the
headline at all, so it is invisible — and an operator reading L3
reasonably assumes every watched repo is at L3.

A per-repo drill-down existed but was half-wired, and that was the more
confusing half. acmmRenderCard wrote the four headline tiles from the
aggregate payload and only THEN resolved the active repo view, which was
consumed by the level bars alone. Clicking a repo chip moved the bars and
left the tiles above them on hive-wide numbers: two halves of one card
disagreeing, with nothing to indicate which of them had followed the
click.

THE TILES NOW FOLLOW THE SELECTOR. The active view is resolved before the
tiles are written, and the second, now-redundant resolution below them is
gone — two resolutions are how the halves came to disagree in the first
place. Codebase Readiness and Criteria Passed read the selected repo.
Overall ACMM is min(codebase, operational) and the server computes it
against the UNION, so it is recomputed for a selection; leaving it would
have put a fleet-wide reading directly beside a per-repo one. Unscoped,
the server's value stands, so the panel never disagrees with the payload
it was handed.

Operational Autonomy deliberately does NOT follow the selector: it
measures the hive's own operating behaviour, not a repository. That makes
the card's scope mixed, which is fine as long as it is stated — an
unlabelled tile that ignores a click is indistinguishable from one that
failed to follow it — so each tile label now carries its scope, and the
ops tile is marked hive-wide whenever a repo is selected.

THE RANKING ANSWERS THE QUESTION DIRECTLY. Finding the weakest repo used
to mean opening the Codebase Readiness dialog and expanding each repo row
one at a time, with nothing sorted by gap; the chips carried a level but
not the ratio, so "lacking, and by how much" cost one click per repo. A
per-repo table now ranks every repo worst-first with its level,
passed/total, and the level it is blocked at, and its rows select their
repo so the table and the selector are one control. The chips carry the
ratio too.

The rank key is the raw GAP (unmet criteria), not the percentage: with a
fixed criteria set the two agree, and the count is what an operator acts
on — "14 things to fix" is a work estimate, "67%" is not. Ties break on
the lower level, then on name, because this table is read repeatedly and
an order that shuffles between identical renders reads as churn that is
not there. "Blocked at" is the FIRST failing level, since levels must
pass in order; counting failures instead would rank a repo stuck at L2
below one with gaps at L5. The "weakest" badge appears only when the
repos actually differ — on a level fleet, singling one out would invent a
problem.

The panel now also states that the aggregate is a union. That was
mentioned only inside the Codebase Readiness dialog, and only when more
than one repo was configured.

Nit from the report, fixed: the repo selector carried both display:none
and display:flex in one style attribute. The later declaration won, so it
rendered visible — holding only the "All (aggregate)" chip — until the
first evaluation response let acmmRenderCard hide it.

NO SERVER CHANGE. The evaluation already returns repo_results[] with each
repo's level, ratio, per-level scores and full criterion list, so this is
a presentation gap and not a collection gap; nothing here adds an API
call. The optional min-across-repos field the report floats is
deliberately not added: it is derivable from repo_results, no Go consumer
outside this panel reads codebase_level at all, and a duplicated
derivable field is a second source of truth that can drift.

The ordering rule is tested by EXECUTION, not by pattern-matching: the
test extracts acmmRepoGapRows and acmmFirstFailingLevel from index.html
and runs them under node against a fixture whose correct order is known
by construction and is neither the input order nor its reverse. Asserting
that a sort comparator appears verbatim would catch a rule that was
changed and miss one that was always wrong, which for a ranking is the
failure that matters. The remaining tests pin the wiring that cannot be
executed — including the ORDERING of the active-view resolution against
the tile writes, since an edit that moves it back down reintroduces the
original defect while every value-reading assertion still passes. All six
were confirmed to fail against the pristine file before the fix.

Signed-off-by: Danathar <doug.baggett@gmail.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ter (#5881)

`ClusterConfig.InCluster` is a claim, not a fact. For an InCluster cluster,
kubectlArgsForCluster appended --server/--certificate-authority/--token only
when KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT were both set, and
appended nothing at all otherwise.

"Nothing at all" is not a safe default. kubectl with no --server and no
--kubeconfig resolves against its ambient configuration -- $KUBECONFIG or
~/.kube/config -- so the command targets whatever cluster the machine's
current context happens to name.

That is the mechanism behind #5768. Six of the seven test files that drive
handleCreateHive install no scripted kubectl, and the hub test package had no
TestMain isolation before 2026-07-31. Any of those tests reaching a
provisioning entry point issued a real `kubectl apply` of a Namespace and
Deployment named from its own fixture org. The hub kept no record because
saveSaaSHive writes under saasHivesDir, which each test redirects to a
t.TempDir() -- the bookkeeping was written and then deleted with the temp dir
while the cluster-side objects persisted.

The guard goes at the single argv chokepoint both kubectl constructors funnel
through, not in individual tests: a per-test fix would have to be repeated
forever and re-missed once. Absence of both ServiceAccount env vars is
positive, reliable evidence the process is not in a pod -- the kubelet always
injects them -- so genuine in-cluster provisioning is untouched, which
TestRealInClusterStillUsesServiceAccount pins.

TestKubectlForCluster asserted "in-cluster should not use --kubeconfig" with
those vars unset, encoding the bug as expected behaviour. It now sets them,
pinning the real-pod behaviour it meant to describe.

Refs #5768

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…rg policy denies GITHUB_TOKEN (#5875) (#5879)

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

Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bounce, relative evaluation age (#5877) (#5882)

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

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: clubanderson

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

The pull request process is described here

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

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

@kubestellar-prow kubestellar-prow Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 5, 2026
@clubanderson clubanderson added lgtm Indicates that a PR is ready to be merged. dco-signoff: yes Indicates the PR's author has signed the DCO. and removed dco-signoff: no Indicates the PR's author has not signed the DCO. labels Sep 5, 2026
@clubanderson
clubanderson force-pushed the sync/v4-into-v5-2026-09-04 branch from e7ba319 to 21e7678 Compare September 5, 2026 04:37
@kubestellar-prow kubestellar-prow Bot added dco-signoff: no Indicates the PR's author has not signed the DCO. and removed lgtm Indicates that a PR is ready to be merged. labels Sep 5, 2026
@kubestellar-prow

Copy link
Copy Markdown
Contributor

New changes are detected. LGTM label has been removed.

@kubestellar-prow kubestellar-prow Bot removed the dco-signoff: yes Indicates the PR's author has signed the DCO. label Sep 5, 2026
Merge origin/v4 through v4.16.1 while keeping v5 release-generated changelog content unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
@clubanderson
clubanderson force-pushed the sync/v4-into-v5-2026-09-04 branch from 21e7678 to fb05be5 Compare September 5, 2026 04:48
@kubestellar-prow

Copy link
Copy Markdown
Contributor

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:

Details

Instructions 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.

@clubanderson clubanderson added dco-signoff: yes Indicates the PR's author has signed the DCO. lgtm Indicates that a PR is ready to be merged. and removed dco-signoff: no Indicates the PR's author has not signed the DCO. labels Sep 5, 2026
@clubanderson
clubanderson merged commit 35386c9 into v5 Sep 5, 2026
61 of 65 checks passed
@kubestellar-prow
kubestellar-prow Bot deleted the sync/v4-into-v5-2026-09-04 branch September 5, 2026 04:55
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

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

We'd love to hear how your experience was: share feedback

clubanderson added a commit that referenced this pull request Sep 5, 2026
…nc-policy.md) (#6052)

* [strategist] planning: document v4→v5 forward-port sync policy

Adds src/docs/v5-sync-policy.md proposing cadence (v4 minor release or
50-commit drift), single-owner rotation, the merge-commit top-up
procedure already used by #5845/#6046, DCO expectations, and explicit
PR-body and review contracts for sync PRs. Links it from the docs index.

Refs #6051

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: sec-check <sec-check@hive.kubestellar.io>

* 📖 document active v5 sync policy

Align the v4-to-v5 sync policy with the current merge-commit top-up process, weekly/release cadence, maintainer ownership, verification checklist, and zero-delta check.

Closes #6051

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

---------

Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
Co-authored-by: sec-check <sec-check@hive.kubestellar.io>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Andy Anderson <andy@clubanderson.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. dco-signoff: yes Indicates the PR's author has signed the DCO. lgtm Indicates that a PR is ready to be merged. no-changelog No changelog fragment required size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants