fix(ops): name the cluster on every guarded kubectl call before the rebuild #977

Merged
coilyco-ops merged 2 commits from aos/claude/mt75-kubectl-context-callsites into main 2026-08-28 19:46:59 +00:00
Owner

Fixes the infrastructure half of agentic-os#1352, a regression I introduced in agentic-os#1349.

Why now

#1349 made --context mandatory on aosguard ops kubectl. It binds at the next aosguard rebuild, not today, so this is latent. At that moment every call written without the flag starts failing closed, in a deny that mentions #1349 nowhere:

aosguard: `get` denied: context did not match any allowed pattern [kai-server ser8] (fail-closed)

65 call sites here were written without it. This has to land before the rebuild, or the first person to hit it debugs a fail-closed deny with no link to the change that caused it.

The two scripts, which are the part that actually breaks

bootstrap-ser8-forgejo-runner-canary.sh - 19 calls, all now naming ser8. Worth stating plainly: this script targets ser8 by name and applies through remote onto a host whose active kubeconfig context is not guaranteed to be ser8. An unnamed target on a remote mutation is precisely the ambiguity #1349 exists to catch, so this file is the strongest argument for the guard rather than against it.

forgejo-external-account-lockdown-rollout.sh - a context variable defaulting to kai-server, overridable via KUBE_CONTEXT. Both of its surfaces are kai-server: the Forgejo server, and the scoped runners that ser8 superseded but which still sit at 0 replicas on that cluster.

I checked rather than inferred from filenames: forgejo-86b7b6f66f and forgejo-db-0 run on kai-server; ser8's forgejo namespace carries only runners.

The flag goes after the verb. aosguard's own urfave/cli parser eats a leading --context as an unknown global flag (flag provided but not defined: -context). A mechanical prefix rewrite would have silently broken all 19 calls it touched. Vera found that parser behaviour; it is the reason this is not a one-line sed.

The runbooks

39 commands across 11 files take --context <cluster>, a placeholder rather than a value. Hardcoding kai-server into an incident recipe would be actively wrong during a ser8 incident, and <cluster> matches the <pod> / <ns> convention these files already use.

Each of the five ops-investigation-* skills gains one line naming the two valid values, so an operator learns them without first triggering a deny.

Three prose mentions are deliberately untouched - they reference the surface rather than invoke it, e.g. "aosguard ops kubectl exposes no delete verb".

Not included

cleanup-orphaned-cluster-objects.sh mentions the wrap only in a comment as an optional KUBECTL= value, and its actual verb is delete, which the wrap never exposed. Unaffected in practice.

Verification

bash -n clean on both scripts, pre-commit run --all-files green including shellcheck and the Markdown size caps.

A transform assertion caught me mid-change: I had counted 3 call sites in the bootstrap script from a truncated grep, and the script has 19. The count in this PR is from the transform itself, not from my earlier reading.

Fixes the infrastructure half of agentic-os#1352, a regression I introduced in agentic-os#1349. ## Why now #1349 made `--context` mandatory on `aosguard ops kubectl`. It binds at the **next aosguard rebuild**, not today, so this is latent. At that moment every call written without the flag starts failing closed, in a deny that mentions #1349 nowhere: ``` aosguard: `get` denied: context did not match any allowed pattern [kai-server ser8] (fail-closed) ``` 65 call sites here were written without it. This has to land before the rebuild, or the first person to hit it debugs a fail-closed deny with no link to the change that caused it. ## The two scripts, which are the part that actually breaks **`bootstrap-ser8-forgejo-runner-canary.sh`** - 19 calls, all now naming ser8. Worth stating plainly: this script targets ser8 by name and applies through `remote` onto a host whose *active* kubeconfig context is not guaranteed to be ser8. An unnamed target on a remote mutation is precisely the ambiguity #1349 exists to catch, so this file is the strongest argument for the guard rather than against it. **`forgejo-external-account-lockdown-rollout.sh`** - a `context` variable defaulting to `kai-server`, overridable via `KUBE_CONTEXT`. Both of its surfaces are kai-server: the Forgejo server, and the scoped runners that ser8 superseded but which still sit at 0 replicas on that cluster. I checked rather than inferred from filenames: `forgejo-86b7b6f66f` and `forgejo-db-0` run on kai-server; ser8's `forgejo` namespace carries only runners. **The flag goes after the verb.** aosguard's own urfave/cli parser eats a leading `--context` as an unknown global flag (`flag provided but not defined: -context`). A mechanical prefix rewrite would have silently broken all 19 calls it touched. Vera found that parser behaviour; it is the reason this is not a one-line sed. ## The runbooks 39 commands across 11 files take `--context <cluster>`, a placeholder rather than a value. Hardcoding `kai-server` into an incident recipe would be **actively wrong** during a ser8 incident, and `<cluster>` matches the `<pod>` / `<ns>` convention these files already use. Each of the five `ops-investigation-*` skills gains one line naming the two valid values, so an operator learns them without first triggering a deny. Three prose mentions are deliberately untouched - they reference the surface rather than invoke it, e.g. "`aosguard ops kubectl` exposes no `delete` verb". ## Not included `cleanup-orphaned-cluster-objects.sh` mentions the wrap only in a comment as an optional `KUBECTL=` value, and its actual verb is `delete`, which the wrap never exposed. Unaffected in practice. ## Verification `bash -n` clean on both scripts, `pre-commit run --all-files` green including shellcheck and the Markdown size caps. A transform assertion caught me mid-change: I had counted 3 call sites in the bootstrap script from a truncated grep, and the script has 19. The count in this PR is from the transform itself, not from my earlier reading.
fix(ops): name the cluster on every guarded kubectl call before the rebuild
All checks were successful
TruffleHog / Scan for secrets (pull_request) Successful in 4s
CI / lint (pull_request) Successful in 43s
c8c74df4bd
agentic-os#1349 made `--context` mandatory on `aosguard ops kubectl`. It binds
at the next aosguard rebuild, so every call written without the flag starts
failing closed then, in a deny that names #1349 nowhere. 65 call sites here
were written without it. See agentic-os#1352.

Two live-mutation scripts, the part that actually breaks:

`bootstrap-ser8-forgejo-runner-canary.sh` targets ser8 by name and applies
through `remote` onto a host whose active kubeconfig context is not
guaranteed to be ser8. An unnamed target on a remote mutation is the exact
ambiguity #1349 exists to catch, so all 19 calls now name ser8 explicitly.

`forgejo-external-account-lockdown-rollout.sh` gets a `context` variable
defaulting to kai-server, overridable by `KUBE_CONTEXT`. Both of its
surfaces are kai-server: the Forgejo server, and the scoped runners ser8
superseded but which still sit at 0 replicas there. Verified by looking:
`forgejo-86b7b6f66f` and `forgejo-db-0` run on kai-server, ser8 carries only
runners.

The flag goes after the verb, never before it. aosguard's own urfave/cli
parser eats a leading `--context` as an unknown global flag, so the
mechanical prefix rewrite would have broken every call it touched.

39 runbook commands take `--context <cluster>` rather than a real value.
Stamping kai-server into an incident recipe would be actively wrong during a
ser8 incident, and `<cluster>` matches the `<pod>` / `<ns>` convention these
files already use. Each of the five skills gains one line naming the two
valid values, so an operator learns them without first triggering a deny.

Three prose mentions are left alone. They reference the surface rather than
invoke it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
Merge branch 'main' into aos/claude/mt75-kubectl-context-callsites
All checks were successful
TruffleHog / Scan for secrets (pull_request) Successful in 4s
CI / lint (pull_request) Successful in 43s
52d4c8bc8b
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/infrastructure!977
No description provided.