HOMELAB-1666: feat(agent-sandbox): pinned v1.0.4 install + namespace + metrics #520

Merged
aaron merged 2 commits from plane/HOMELAB-1666-agent-sandbox into live 2026-09-29 06:26:39 +00:00
Owner

What

LD0.3 (Lab Director rebuild): the sandbox substrate — kubernetes-sigs/agent-sandbox — as a pinned, GitOps-installable module in infra-core. The Sandbox instance itself is in the sibling prod PR (Homelab/prod#…, same branch name).

File What
core/manifests/agent-sandbox/vendor/sandbox-with-extensions-v1.0.4.yaml upstream release asset for tag v1.0.4, byte-for-byte (sha256 in vendor/SHA256SUMS: 8cbe7f4c2524…57052c0)
core/manifests/agent-sandbox/kustomization.yaml the three deliberate deltas (below)
core/manifests/agent-sandbox/README.md pin table, delta list, CRD-prune rationale, upgrade procedure, "left out on purpose"
core/manifests/namespaces/agent-sandbox-system.yaml new namespace + PSA posture
core/manifests/monitoring/servicemonitors/agent-sandbox-controller.yaml scrape target (hand-written equivalent of the upstream chart's, which we don't use)
core/manifests/monitoring/rules/agent-sandbox-alerts.yaml upstream's starter alert, ported to the homelab's release: kubernetes-prometheus-stack rule label

Why vendored manifest, not the Helm chart

Upstream publishes no chart repo (kubernetes-sigs.github.io/agent-sandbox/index.yaml → 404, only 3 release assets), and names GitOps engines as the primary consumer of the pinned manifest. Vendoring means: the deployed artifact is verifiable byte-for-byte, re-derivable offline, and needs no external source at sync time (repo-wide convention: the only remote sources are Helm repos, everything else is Forgejo).

Extensions (sandboxtemplates/sandboxwarmpools/sandboxclaims + --extensions) are installed now rather than later, because the extension path is the one that gets a controller-generated default-deny NetworkPolicy — upstream does not default bare Sandbox pods. RBAC delta vs core-only is tabulated in the module README (adds networkpolicies write + the three extension kinds + leases.delete).

The three deltas (all in kustomization.yaml, all commented in place)

  1. Upstream Namespace object removed — the namespace is declared once in core/manifests/namespaces/, so one object is not owned by two Applications, and the namespace/PSA inventory stays in one directory (longhorn-system, headscale, cilium-system do the same).
  2. CRDs annotated argocd.argoproj.io/sync-wave: "-10" + sync-options: Prune=false. Upstream's own warning: deleting a CRD cascade-deletes every resource of that kind — for sandboxes.agents.x-k8s.io that is every Sandbox pod and its PVCs. The annotation is the "prune: false on CRDs" requirement, scoped to the CRDs; the Application keeps prune: true so upstream upgrades still clean stale RBAC/Deployment objects. Added as two JSON6902 adds on individual annotation keys, so the CRDs' controller-gen annotation survives and new CRDs are covered automatically.
  3. Controller pod hardened + resourced — upstream ships it with neither. Values are upstream's own documented example: pod runAsNonRoot + seccompProfile: RuntimeDefault; container allowPrivilegeEscalation: false, drop: [ALL], runAsNonRoot, runAsUser: 65532 (the image's uid, verified with crane config), seccompProfile; requests 10m/64Mi, limits 500m/128Mi. With these the controller pod satisfies PSA restricted.

Deliberately not changed

  • automountServiceAccountToken on the controller stays true (it authenticates to the API server). The zero-keys requirement is on the Sandbox podTemplate — see the prod PR.
  • readOnlyRootFilesystem on the controller: not set. Unverified for this static Go binary in this cluster; flipping it blind risks a crash-loop that looks like a controller bug. Documented + tracked on the ticket.
  • Nothing in agent-sandbox/pi-image/** (LD0.2) — touching it would trigger that directory's image build and it is another ticket's territory. Note: the LD0.2 example pod says namespace: agent-fleet # placeholder namespace — created by LD0.3; the real namespace is agent-sandbox-system (single new namespace, per the ticket's constraint). Worth a one-line comment fix in a later PR.

Verify (all agent-doable, no cluster access)

kubectl kustomize core/manifests/agent-sandbox      # 11 objects: 4 CRDs (wave -10, Prune=false), SA, 2x CR/CRB, Service, Deployment (hardened)
(cd core/manifests/agent-sandbox/vendor && sha256sum -c SHA256SUMS)
yamllint -d relaxed core/manifests/                 # exit 0 (repo has pre-existing line-length warnings)
crane digest registry.k8s.io/agent-sandbox/agent-sandbox-controller:v1.0.4
  -> sha256:c241245d7ff0068784ec0423f35efeb77c70ab08592533ca5b0cd7c36f8b2020
crane config registry.k8s.io/agent-sandbox/agent-sandbox-controller:v1.0.4
  -> User 65532 (distroless, non-root), Entrypoint /agent-sandbox-controller

registry.k8s.io pulls are already cached through Harbor at the node level (prod/talos/mirror-patch.yaml → v2/k8s-cache), same as core/manifests/external-snapshotter.

Review notes / risk

  • Dangerous class (new namespace, cluster-scoped CRDs + networkpolicies RBAC). Not to be auto-merged; this PR is expected to stop at operator review.
  • The Application that consumes this module is the sibling prod PR; merging infra-core first (or the two together) is the correct order — otherwise that Application sits in chart-not-found state (same dependency note as apps/omniroute.yaml).
  • Open questions carried on HOMELAB-1666, not decided here: the sandbox-router/SDK port contract, PI_SKIP_VERSION_CHECK, the in-cluster model endpoint, and escalating agent-sandbox-system from PSA baseline to restricted.
## What LD0.3 (Lab Director rebuild): the sandbox substrate — kubernetes-sigs/agent-sandbox — as a pinned, GitOps-installable module in infra-core. The `Sandbox` instance itself is in the sibling prod PR (Homelab/prod#…, same branch name). | File | What | |---|---| | `core/manifests/agent-sandbox/vendor/sandbox-with-extensions-v1.0.4.yaml` | upstream release asset for tag **v1.0.4**, byte-for-byte (sha256 in `vendor/SHA256SUMS`: `8cbe7f4c2524…57052c0`) | | `core/manifests/agent-sandbox/kustomization.yaml` | the three deliberate deltas (below) | | `core/manifests/agent-sandbox/README.md` | pin table, delta list, CRD-prune rationale, upgrade procedure, "left out on purpose" | | `core/manifests/namespaces/agent-sandbox-system.yaml` | new namespace + PSA posture | | `core/manifests/monitoring/servicemonitors/agent-sandbox-controller.yaml` | scrape target (hand-written equivalent of the upstream chart's, which we don't use) | | `core/manifests/monitoring/rules/agent-sandbox-alerts.yaml` | upstream's starter alert, ported to the homelab's `release: kubernetes-prometheus-stack` rule label | ## Why vendored manifest, not the Helm chart Upstream publishes no chart repo (`kubernetes-sigs.github.io/agent-sandbox/index.yaml` → 404, only 3 release assets), and names GitOps engines as the primary consumer of the pinned manifest. Vendoring means: the deployed artifact is verifiable byte-for-byte, re-derivable offline, and needs no external source at sync time (repo-wide convention: the only remote sources are Helm repos, everything else is Forgejo). Extensions (`sandboxtemplates`/`sandboxwarmpools`/`sandboxclaims` + `--extensions`) are installed now rather than later, because the extension path is the one that gets a controller-generated default-deny NetworkPolicy — upstream does not default bare `Sandbox` pods. RBAC delta vs core-only is tabulated in the module README (adds `networkpolicies` write + the three extension kinds + `leases.delete`). ## The three deltas (all in kustomization.yaml, all commented in place) 1. **Upstream `Namespace` object removed** — the namespace is declared once in `core/manifests/namespaces/`, so one object is not owned by two Applications, and the namespace/PSA inventory stays in one directory (`longhorn-system`, `headscale`, `cilium-system` do the same). 2. **CRDs annotated** `argocd.argoproj.io/sync-wave: "-10"` + `sync-options: Prune=false`. Upstream's own warning: deleting a CRD cascade-deletes every resource of that kind — for `sandboxes.agents.x-k8s.io` that is every Sandbox pod **and its PVCs**. The annotation is the "prune: false on CRDs" requirement, scoped to the CRDs; the Application keeps `prune: true` so upstream upgrades still clean stale RBAC/Deployment objects. Added as two JSON6902 `add`s on individual annotation keys, so the CRDs' `controller-gen` annotation survives and new CRDs are covered automatically. 3. **Controller pod hardened + resourced** — upstream ships it with neither. Values are upstream's own documented example: pod `runAsNonRoot` + `seccompProfile: RuntimeDefault`; container `allowPrivilegeEscalation: false`, `drop: [ALL]`, `runAsNonRoot`, `runAsUser: 65532` (the image's uid, verified with `crane config`), `seccompProfile`; requests `10m/64Mi`, limits `500m/128Mi`. With these the controller pod satisfies PSA `restricted`. ## Deliberately not changed - `automountServiceAccountToken` on the **controller** stays `true` (it authenticates to the API server). The zero-keys requirement is on the Sandbox podTemplate — see the prod PR. - `readOnlyRootFilesystem` on the controller: not set. Unverified for this static Go binary in this cluster; flipping it blind risks a crash-loop that looks like a controller bug. Documented + tracked on the ticket. - Nothing in `agent-sandbox/pi-image/**` (LD0.2) — touching it would trigger that directory's image build and it is another ticket's territory. Note: the LD0.2 example pod says `namespace: agent-fleet # placeholder namespace — created by LD0.3`; the real namespace is `agent-sandbox-system` (single new namespace, per the ticket's constraint). Worth a one-line comment fix in a later PR. ## Verify (all agent-doable, no cluster access) ``` kubectl kustomize core/manifests/agent-sandbox # 11 objects: 4 CRDs (wave -10, Prune=false), SA, 2x CR/CRB, Service, Deployment (hardened) (cd core/manifests/agent-sandbox/vendor && sha256sum -c SHA256SUMS) yamllint -d relaxed core/manifests/ # exit 0 (repo has pre-existing line-length warnings) crane digest registry.k8s.io/agent-sandbox/agent-sandbox-controller:v1.0.4 -> sha256:c241245d7ff0068784ec0423f35efeb77c70ab08592533ca5b0cd7c36f8b2020 crane config registry.k8s.io/agent-sandbox/agent-sandbox-controller:v1.0.4 -> User 65532 (distroless, non-root), Entrypoint /agent-sandbox-controller ``` `registry.k8s.io` pulls are already cached through Harbor at the node level (`prod/talos/mirror-patch.yaml` → `v2/k8s-cache`), same as `core/manifests/external-snapshotter`. ## Review notes / risk - **Dangerous class** (new namespace, cluster-scoped CRDs + `networkpolicies` RBAC). Not to be auto-merged; this PR is expected to stop at operator review. - The Application that consumes this module is the sibling prod PR; merging infra-core first (or the two together) is the correct order — otherwise that Application sits in chart-not-found state (same dependency note as `apps/omniroute.yaml`). - Open questions carried on HOMELAB-1666, not decided here: the sandbox-router/SDK port contract, `PI_SKIP_VERSION_CHECK`, the in-cluster model endpoint, and escalating `agent-sandbox-system` from PSA `baseline` to `restricted`.
HOMELAB-1666: feat(agent-sandbox): pinned v1.0.4 install + namespace + metrics
All checks were successful
0/0 projects applied successfully.
CI Review / receiver-test (pull_request) Successful in 5s
CI Review / helm-validate (pull_request) Successful in 6s
Lint & Validate / shellcheck (pull_request) Successful in 4s
Lint & Validate / yaml-lint (pull_request) Successful in 11s
Lint & Validate / terraform-validate (pull_request) Successful in 2m45s
abe8b7e43a
LD0.3 of the Lab Director rebuild: the sandbox substrate as an ArgoCD app.

- core/manifests/agent-sandbox/ — kubernetes-sigs/agent-sandbox v1.0.4
  vendored verbatim from the pinned release asset (sha256 in
  vendor/SHA256SUMS), rendered through kustomize with three explicit deltas:
  upstream Namespace dropped (declared once in core/manifests/namespaces/),
  CRDs annotated sync-wave -10 + Prune=false (deleting a CRD cascade-deletes
  every Sandbox and its PVCs), controller pod hardened + resourced to
  upstream's own documented example (satisfies PSA restricted).
- core/manifests/namespaces/agent-sandbox-system.yaml — namespace with
  PSA enforce baseline (+ audit/warn restricted), Prune=false.
- core/manifests/monitoring/{servicemonitors,rules}/agent-sandbox-* — scrape
  target + upstream's starter alert, in the homelab's monitoring convention.

The Sandbox instance itself lives in prod (manifests/agent-sandbox).
Author
Owner

Cross-link + evidence update (the body has a placeholder where this was not known yet):

  • Sibling PR: Homelab/prod#794 — the Applications that consume this module (agent-sandbox-namespace wave -1, agent-sandbox wave 10 multi-source) plus the first Sandbox (manifests/agent-sandbox/pi-sandbox.yaml). Merge this one first (or together): #794's Application points at core/manifests/agent-sandbox on live.

CI on this branch (abe8b7e) is green, 6/6: yaml-lint (the job that actually covers core/manifests/), whitespace/shellcheck, terraform-validate, CI Review / helm-validate, CI Review / receiver-test, Atlantis 0/0 projects applied.

Note for whoever reviews the CI tab: Homelab/infra-core's Forgejo actions/tasks API endpoint answers 500 for this repo, so the run list is not browsable from the API — commit status had to be read from /api/v1/repos/Homelab/infra-core/commits/abe8b7e/status. The web UI shows the same six contexts.

Also added, beyond the files in this diff: an Outline runbook for the operator side — "agent-sandbox — pi Sandbox operations (HOMELAB-1666 / LD0.3)" in the Runbooks collection.

Not merged by design (dangerous class): stopping here for operator review.

Cross-link + evidence update (the body has a placeholder where this was not known yet): - Sibling PR: **Homelab/prod#794** — the Applications that consume this module (`agent-sandbox-namespace` wave -1, `agent-sandbox` wave 10 multi-source) plus the first `Sandbox` (`manifests/agent-sandbox/pi-sandbox.yaml`). Merge this one first (or together): #794's Application points at `core/manifests/agent-sandbox` on `live`. **CI on this branch (abe8b7e) is green, 6/6:** `yaml-lint` (the job that actually covers `core/manifests/`), `whitespace`/`shellcheck`, `terraform-validate`, `CI Review / helm-validate`, `CI Review / receiver-test`, Atlantis `0/0 projects applied`. Note for whoever reviews the CI tab: `Homelab/infra-core`'s Forgejo `actions/tasks` API endpoint answers 500 for this repo, so the run list is not browsable from the API — commit status had to be read from `/api/v1/repos/Homelab/infra-core/commits/abe8b7e/status`. The web UI shows the same six contexts. Also added, beyond the files in this diff: an Outline runbook for the operator side — *"agent-sandbox — pi Sandbox operations (HOMELAB-1666 / LD0.3)"* in the Runbooks collection. Not merged by design (dangerous class): stopping here for operator review.
Author
Owner

Correction to my previous comment: the six green contexts are Lint & Validate / yaml-lint, Lint & Validate / shellcheck, Lint & Validate / terraform-validate, CI Review / helm-validate, CI Review / receiver-test, and the Atlantis status ("0/0 projects applied"). I mistyped one of them as "whitespace/shellcheck".

Correction to my previous comment: the six green contexts are `Lint & Validate / yaml-lint`, `Lint & Validate / shellcheck`, `Lint & Validate / terraform-validate`, `CI Review / helm-validate`, `CI Review / receiver-test`, and the Atlantis status ("0/0 projects applied"). I mistyped one of them as "whitespace/shellcheck".
HOMELAB-1666: docs(agent-sandbox): correct CRD prune vs app-deletion and PVC retention
All checks were successful
CI Review / receiver-test (pull_request) Successful in 4s
CI Review / helm-validate (pull_request) Successful in 7s
Lint & Validate / shellcheck (pull_request) Successful in 4s
Lint & Validate / yaml-lint (pull_request) Successful in 10s
Lint & Validate / terraform-validate (pull_request) Successful in 2m53s
cadb8d70e2
Author
Owner

Landing-prep review package — HOMELAB-1666 (LD0.3) · DANGEROUS CLASS · operator ratifies

Nothing was merged, nothing was applied to the cluster, no agent decision to merge. This is the decision input: verdict, what was independently verified, exposure, options, checklist. You ratify.

Heads reviewed — infra-core cadb8d7 (was abe8b7e; cadb8d7 is docs-only, render byte-identical) · prod ca921ff (was 5d968e7; comments/README only, spec: deep-equal).
CI on those heads: infra-core 5/5 green (yaml-lint, shellcheck, terraform-validate, helm-validate, receiver-test), prod 4/4 green (validate-manifests, Validate Manifests/validate, plan-cluster, plan-zitadel) — read via commits/{sha}/status; the actions API still 500s (HOMELAB-1736).

1. Verdict

APPROVE FOR RATIFICATION — merge as they now stand, infra-core first (Option A). No manifest change is needed for the AC. Two reviewers ran in fresh contexts (diffs + ticket AC + frozen constraints only, implementer summary withheld) and were then adjudicated against primary sources.

  • 1 blocker — found independently by both reviewers, fixed, re-verified. The runbook claimed deleting a Sandbox leaves its PVCs alone. It does not.
  • 5 concerns, all documented with dispositions (§3). 0 mismatches in the pinning/render/schema claims that were re-derived; 3 claims were overstated or attributed to the wrong mechanism and are corrected in the docs commit.
  • The change is inert until prod merges (nothing in infra-core applies it), and every destructive path left is manual and explicit.

2. Independently re-derived (not taken from the PR text)

Claim Re-derived here
vendored install == published asset fresh download of v1.0.4/sandbox-with-extensions.yaml → sha256 8cbe7f4c…57052c0, diff byte-identical, sha256sum -c OK. v1.0.4 is the latest release (2026-09-24), 3 assets, no chart repo (index.yaml → 404)
render kubectl kustomize core/manifests/agent-sandbox → 11 objects, no Namespace, 4/4 CRDs sync-wave:"-10" + sync-options: Prune=false (controller-gen annotation preserved), controller patch present (pod runAsNonRoot+seccomp, container allowPrivilegeEscalation:false, drop:[ALL], runAsUser:65532, 10m/64Mi → 500m/128Mi)
no behavioural drift from the docs fix render byte-identical to the pre-fix head; prod pi-sandbox.yaml spec:+metadata: deep-equal
controller image crane digest registry.k8s.io/agent-sandbox/agent-sandbox-controller:v1.0.4 = sha256:c241245d…f8b2020; crane config → User 65532 (matches the runAsUser and the README)
pi image crane digest harbor.aaron.reynoza.org/lab-director/pi:0.87.1 = sha256:739c7b83…343e5a9f3 — byte-exact the digest pinned in pi-sandbox.yaml and the digest LD0.2's landing reported for the live-promoted 0.87.1/latest (#519). Config: User node, Entrypoint [pi], Workdir /workspace, PI_CODING_AGENT_DIR=/home/node/.pi/agent, /etc/passwd node:x:1000:1000 → matches runAsUser/Group/fsGroup 1000 and both mounts
can the pod pull the image? Harbor project lab-director is public: anonymous token carries pull, anonymous manifest HEAD → 200. No imagePullSecret needed (a wrong answer here = permanent ImagePullBackOff)
zero-keys render adds no Secret; only upstream's controller ServiceAccount; automountServiceAccountToken: false explicit on the Sandbox podTemplate; no env/envFrom/extra volumes/hostPath/host*; no webhooks or admission config; CRDs are Namespaced-scope, v1beta1 only, no conversion webhook, no preserveUnknownFields
schema validity every field of pi-sandbox.yaml walked against the pinned v1.0.4 sandboxes.agents.x-k8s.io openAPIV3Schema (v1beta1 served+storage, podTemplate required) — all resolve; negative controls (unknown spec field, unknown container field, unknown volumeClaimTemplate field) all detected, so the walk is not vacuous
no auto-apply outside ArgoCD no hooks, no Jobs, no new CI workflows in either diff; infra-apply remains workflow_dispatch

3. Findings

# Severity Finding Evidence Disposition
F1 BLOCKER (fixed) Runbook claimed "deleting a Sandbox leaves its PVCs alone" (4 places across both PRs, incl. a reclaim command that could never work). False: v1.0.4 reconcilePVCs stamps each PVC with a controller ownerReference to the Sandbox (SetControllerReference, also on adoption) and adds no Sandbox finalizer, so deleting the Sandbox garbage-collects workspace-pi + agent-state-pi. Upstream issue #1001 is the open request for persistentVolumeClaimRetentionPolicy; the field does not exist in v1.0.4. upstream controllers/sandbox_controller.go:1739,1773; e2e volumeclaimtemplate_test.go asserts controller ownerRef; upstream #1001 Fixed in cadb8d7 + ca921ff (docs/comments + corrected PR body). Re-verified: comment-only diffs, render byte-identical, spec: deep-equal. Data is still recoverable: the longhorn class is reclaimPolicy: Retain, so the volumes become Released (detached) PVs — but the PVCs are gone, a re-created Sandbox gets new empty volumes, and recovery is manual.
F2 CONCERN Prune=false was credited with protecting CRDs from Application deletion. Wrong mechanism (right outcome): Prune=false only governs the sync-prune path. App-deletion cascade runs shouldBeDeleted() = !kube.IsCRD && !Delete=false && !helm-keep, so ArgoCD never cascade-deletes CRDs (guard present since ≤ v2.4, verified at v2.13.2) — but the Sandbox is not protected: kubectl delete application agent-sandbox deletes the controller SA/RBAC/Service/Deployment and the Sandbox → its PVCs. argo-cd v2.13.2 controller/appcontroller.go:1102; gitops-engine pkg/utils/kube/kube.go IsCRD Docs corrected. If you want Application deletion to preserve the Sandbox, the annotation is Delete=false, not Prune=false → Option B.
F3 CONCERN Constraint "images digest-pinned" is met for pi, not for the controller (registry.k8s.io/…-controller:v1.0.4, tag only; digest only documented). render; crane digest matches README Accept for the pilot: upstream asset is sha256-pinned, digest recorded, registry.k8s.io is mirrored via Harbor k8s-cache, and external-snapshotter already pulls the same way in this repo. Follow-up if you want the invariant literal → Option C.
F4 CONCERN Half-merge alert: infra-core alone lands the ServiceMonitor + PrometheusRule through the existing monitoring-servicemonitors/monitoring-rules apps, and the rule has an absent(up{…}) clause → AgentSandboxControllerMetricsTargetsDown (warning) fires ~5–6 min later and keeps firing until prod #794 lands. the two apps' path/targetRevision: live; rule expr Merge the two close together, or accept one warning. Optionally gate the absent() clause on the target existing (follow-up).
F5 CONCERN Cross-Application ordering is weaker than the PR text says. Sync waves order Application creation at the app-of-apps root, but ArgoCD has not health-gated argoproj.io/Application since 1.8 (no health customization in this cluster's argocd values), so wave 10 can start before wave −1's namespace exists; it converges via retry:5 + selfHeal (with a brief CreateNamespace=false error). Inside the app the ordering is real: CRD −10 → controller 0 → Sandbox 10, with the Deployment's built-in health and ArgoCD's ensure-CRD-ready wait. argo-cd health docs; grep argoproj.io_Application → none in argocd values Accept (self-heals). Follow-up candidate: restore the Application health.lua in argocd-cm.
F6 CONCERN The controller's RBAC is cluster-wide, upstream-verbatim: create/delete/get/list/patch/update/watch on pods, persistentvolumeclaims, services in every namespace; extensions role adds pods patch/update, leases delete, full networkpolicies CRUD. No pods/exec, no pods/attach, no secrets, no impersonate/escalate/bind. rendered ClusterRoles; kustomize patches only CRDs/Namespace/Deployment Accept as upstream; documented in §4. Tightening = namespaced rewrite that diverges from upstream (follow-up).
F7 SUGGESTION No readiness probe on the controller Deployment (upstream) → wave 10 gates on Deployment availability, not reconciler readiness (bounded; the controller requeues). ConfigMap/agent-sandbox-config is referenced optional: true and not shipped (harmless empty volume). Controller limits (500m/128Mi) are upstream's example, unmeasured. RBAC delta table overstates the pods row (the core role already had those verbs). readOnlyRootFilesystem deliberately off (pi writes /tmp). render + module README Record as follow-ups; none affect the AC.

4. Exposure bound

What this touches. 4 new cluster-scoped CRDs (agents.x-k8s.io, extensions.agents.x-k8s.io); 2 ClusterRoles + 2 ClusterRoleBindings (cluster-wide write to pods/PVCs/services/networkpolicies); 1 new namespace agent-sandbox-system (PSA enforce: baseline, audit/warn: restricted, Prune=false); 1 controller Deployment/SA/Service; 1 Sandbox/pi → Pod + 2 Longhorn PVCs (6Gi); 1 ServiceMonitor + 1 warning rule. No webhooks, no hooks, no Jobs, no Secrets, no CI apply path.

Blast radius if wrong.

  • Controller bug/compromise: create/delete pods, PVCs and Services in any namespace, and rewrite NetworkPolicies anywhere. That is the club-wide reach — it is upstream's own RBAC, not something this PR widened.
  • kubectl delete crd …: cascades to every Sandbox of that kind and, via the PVC ownerReferences, every Sandbox PVC.
  • kubectl delete application agent-sandbox (it carries the ArgoCD resources finalizer): controller SA/RBAC/Service/Deployment + the Sandbox + its PVCs; CRDs and the Namespace survive.
  • Deleting the namespace: everything in it, including both PVCs.

What is protected, by which mechanism. sync-prune of CRDs/Namespace → Prune=false; CRDs under Application deletion → ArgoCD's !kube.IsCRD cascade exclusion (not Prune=false); Sandbox API credentials → automountServiceAccountToken: false (no Secrets anywhere); install integrity → sha256-pinned vendored asset + digest-pinned pi image; PVC data → longhorn reclaimPolicy: Retain (volume survives as Released); merge-order safety → infra-core is inert on its own.

Rollback (operator-actionable).

# A) Stop managing, keep everything running (no data touched):
kubectl -n argocd patch application agent-sandbox --type merge -p '{"metadata":{"finalizers":null}}'
kubectl -n argocd delete application agent-sandbox agent-sandbox-namespace
#    then revert the git PRs so nothing re-creates them.

# B) Remove the Sandbox but keep the data (orphan the PVCs FIRST):
kubectl -n agent-sandbox-system patch pvc workspace-pi agent-state-pi \
  --type=json -p '[{"op":"remove","path":"/metadata/ownerReferences"}]'
kubectl -n agent-sandbox-system delete sandbox pi

# C) Full removal (PVC objects gone; Longhorn volumes survive Released until reclaimed):
kubectl -n argocd delete application agent-sandbox agent-sandbox-namespace
kubectl delete crd sandboxes.agents.x-k8s.io sandboxclaims.extensions.agents.x-k8s.io \
  sandboxtemplates.extensions.agents.x-k8s.io sandboxwarmpools.extensions.agents.x-k8s.io

Merge order. infra-core #520 first, then prod #794 — or back-to-back. Nothing in infra-core is applied until the prod Application points at it, so #520 alone is inert apart from the monitoring alert (F4). Do not merge prod first alone: its Application would reference a path that does not exist on infra-core live (SyncError noise; nothing is applied).

5. Options + recommendation

  • Option A — merge as they now stand (RECOMMENDED). infra-core cadb8d7 → prod ca921ff. Manifest set unchanged since review except the docs fix; the AC only needs a cluster check, and every destructive path left is explicit and now documented truthfully. Follow-ups (recorded below) do not gate the pilot.
  • Option B — add argocd.argoproj.io/sync-options: Delete=false to the Sandbox (and namespace) first. Makes Application deletion non-destructive. Cost: a one-line annotation, CI re-run, and it changes semantics — a pruned pi-sandbox.yaml would then leave the Sandbox running (arguably worse than "Git is the source of truth"). My read: not needed now; take it with the VAP follow-up if you want the guard.
  • Option C — change first: digest-pin the controller image (a 4th kustomize delta), gate the alert's absent() clause, install upstream's ValidatingAdmissionPolicy for automountServiceAccountToken=false. Each is a real improvement but none affects the AC, and each costs a re-render + CI cycle. My read: separate follow-up PRs.

Recommendation: Option A. Then, in rough priority: (1) VAP for the automount invariant + Delete=false decision; (2) alert gating; (3) controller image digest pin or amended constraint wording; (4) Application-health customization in argocd-cm; (5) readOnlyRootFilesystem + /tmp emptyDir experiment; (6) PSA baseline → restricted flip and the Cilium egress allowlist; (7) the RBAC-delta doc nit and the LD0.2 example-pod namespace comment.

6. Operator-verify checklist (your AC)

  1. Read-only health — kubectl -n argocd get applications agent-sandbox agent-sandbox-namespace → Synced + Healthy. Then kubectl -n agent-sandbox-system get sandbox,pods,pvc,deploy,svc and kubectl -n agent-sandbox-system describe sandbox pi → Ready=True, Pod/pi Running 1/1, PVCs workspace-pi (5Gi) and agent-state-pi (1Gi) Bound, 4 CRDs Established.
  2. Wave evidence — kubectl -n argocd get application agent-sandbox -o jsonpath='{.status.operationState.syncResult.resources[*].name}' / the sync panel: CRDs at wave −10 before the Deployment, and the Sandbox only after the Deployment was Healthy. Expect a brief namespace-race retry on a cold install (F5) — that is expected, not a failure.
  3. AC — kubectl -n agent-sandbox-system attach -it pi → working pi TUI. Detach with Ctrl-P Ctrl-Q and confirm the pod survives. If rendering is off, kubectl -n agent-sandbox-system exec -it pi -- env TERM=xterm-256color pi (the image sets no TERM).
  4. First run needs one decision — the agent-state PVC is deliberately empty and the manifest carries no credentials. Configure a provider inside the TUI, or pre-seed settings.json. Intended steady state: pi pointed at the in-cluster OmniRoute gateway (open question on the ticket).
  5. Persistence — pwd = /workspace; ls -la /home/node/.pi/agent; write a file, kubectl -n agent-sandbox-system delete pod pi, confirm the file is still there.
  6. Guardrails while you test — do not kubectl delete crd (cascades every Sandbox and PVC), do not delete the agent-sandbox Application (it deletes the Sandbox and its PVCs), do not delete the namespace. To clear the Sandbox while keeping data: orphan the PVCs first (§4B). longhorn volumes survive as Released if you do delete the PVCs.

7. What I could not verify (no cluster access)

Live Application status / cold-install convergence; Sandbox/pi Ready, Pod Running, PVCs Bound; the attach/TUI behaviour and detach; PSA admission actually admitting both pods; the Harbor k8s-cache pull of the controller image at apply time; and the exact ArgoCD build in-cluster (chart argo-cd 7.7.10 → v2.13.x; the !kube.IsCRD cascade guard was verified at v2.13.2 and is present since at least v2.4). Everything else in this package was re-derived from primary sources on this machine.

## Landing-prep review package — HOMELAB-1666 (LD0.3) · DANGEROUS CLASS · operator ratifies **Nothing was merged, nothing was applied to the cluster, no agent decision to merge.** This is the decision input: verdict, what was independently verified, exposure, options, checklist. You ratify. **Heads reviewed** — infra-core `cadb8d7` (was `abe8b7e`; `cadb8d7` is docs-only, render byte-identical) · prod `ca921ff` (was `5d968e7`; comments/README only, `spec:` deep-equal). **CI on those heads:** infra-core 5/5 green (yaml-lint, shellcheck, terraform-validate, helm-validate, receiver-test), prod 4/4 green (validate-manifests, Validate Manifests/validate, plan-cluster, plan-zitadel) — read via `commits/{sha}/status`; the actions API still 500s (HOMELAB-1736). ### 1. Verdict **APPROVE FOR RATIFICATION — merge as they now stand, infra-core first (Option A).** No manifest change is needed for the AC. Two reviewers ran in fresh contexts (diffs + ticket AC + frozen constraints only, implementer summary withheld) and were then adjudicated against primary sources. - **1 blocker — found independently by both reviewers, fixed, re-verified.** The runbook claimed deleting a Sandbox leaves its PVCs alone. It does not. - **5 concerns**, all documented with dispositions (§3). **0 mismatches** in the pinning/render/schema claims that were re-derived; 3 claims were overstated or attributed to the wrong mechanism and are corrected in the docs commit. - The change is **inert until prod merges** (nothing in infra-core applies it), and every destructive path left is manual and explicit. ### 2. Independently re-derived (not taken from the PR text) | Claim | Re-derived here | |---|---| | vendored install == published asset | fresh download of `v1.0.4/sandbox-with-extensions.yaml` → sha256 `8cbe7f4c…57052c0`, `diff` byte-identical, `sha256sum -c OK`. v1.0.4 is the latest release (2026-09-24), 3 assets, no chart repo (`index.yaml` → 404) | | render | `kubectl kustomize core/manifests/agent-sandbox` → **11 objects**, no Namespace, 4/4 CRDs `sync-wave:"-10"` + `sync-options: Prune=false` (controller-gen annotation preserved), controller patch present (pod `runAsNonRoot`+seccomp, container `allowPrivilegeEscalation:false`, `drop:[ALL]`, `runAsUser:65532`, 10m/64Mi → 500m/128Mi) | | no behavioural drift from the docs fix | render **byte-identical** to the pre-fix head; prod `pi-sandbox.yaml` `spec:`+`metadata:` deep-equal | | controller image | `crane digest registry.k8s.io/agent-sandbox/agent-sandbox-controller:v1.0.4` = `sha256:c241245d…f8b2020`; `crane config` → `User 65532` (matches the `runAsUser` and the README) | | pi image | `crane digest harbor.aaron.reynoza.org/lab-director/pi:0.87.1` = `sha256:739c7b83…343e5a9f3` — byte-exact the digest pinned in `pi-sandbox.yaml` and the digest LD0.2's landing reported for the live-promoted `0.87.1`/`latest` (#519). Config: `User node`, `Entrypoint [pi]`, `Workdir /workspace`, `PI_CODING_AGENT_DIR=/home/node/.pi/agent`, `/etc/passwd` `node:x:1000:1000` → matches runAsUser/Group/fsGroup 1000 and both mounts | | can the pod pull the image? | Harbor project `lab-director` is **public**: anonymous token carries `pull`, anonymous manifest `HEAD` → 200. No `imagePullSecret` needed (a wrong answer here = permanent ImagePullBackOff) | | zero-keys | render adds **no Secret**; only upstream's controller ServiceAccount; `automountServiceAccountToken: false` explicit on the Sandbox podTemplate; no `env`/`envFrom`/extra volumes/`hostPath`/`host*`; **no webhooks or admission config**; CRDs are Namespaced-scope, `v1beta1` only, no conversion webhook, no `preserveUnknownFields` | | schema validity | every field of `pi-sandbox.yaml` walked against the pinned v1.0.4 `sandboxes.agents.x-k8s.io` `openAPIV3Schema` (`v1beta1` served+storage, `podTemplate` required) — all resolve; negative controls (unknown spec field, unknown container field, unknown volumeClaimTemplate field) **all detected**, so the walk is not vacuous | | no auto-apply outside ArgoCD | no hooks, no Jobs, no new CI workflows in either diff; `infra-apply` remains `workflow_dispatch` | ### 3. Findings | # | Severity | Finding | Evidence | Disposition | |---|---|---|---|---| | F1 | **BLOCKER (fixed)** | Runbook claimed "deleting a Sandbox leaves its PVCs alone" (4 places across both PRs, incl. a reclaim command that could never work). False: v1.0.4 `reconcilePVCs` stamps each PVC with a **controller** ownerReference to the Sandbox (`SetControllerReference`, also on adoption) and adds no Sandbox finalizer, so deleting the Sandbox **garbage-collects `workspace-pi` + `agent-state-pi`**. Upstream issue #1001 is the open request for `persistentVolumeClaimRetentionPolicy`; the field does not exist in v1.0.4. | upstream `controllers/sandbox_controller.go:1739,1773`; e2e `volumeclaimtemplate_test.go` asserts controller ownerRef; upstream #1001 | **Fixed** in `cadb8d7` + `ca921ff` (docs/comments + corrected PR body). Re-verified: comment-only diffs, render byte-identical, `spec:` deep-equal. Data is still recoverable: the `longhorn` class is `reclaimPolicy: Retain`, so the volumes become `Released` (detached) PVs — but the PVCs are gone, a re-created Sandbox gets **new empty** volumes, and recovery is manual. | | F2 | CONCERN | `Prune=false` was credited with protecting CRDs from Application deletion. Wrong mechanism (right outcome): `Prune=false` only governs the **sync-prune** path. App-deletion cascade runs `shouldBeDeleted()` = `!kube.IsCRD && !Delete=false && !helm-keep`, so **ArgoCD never cascade-deletes CRDs** (guard present since ≤ v2.4, verified at v2.13.2) — but the **Sandbox is not protected**: `kubectl delete application agent-sandbox` deletes the controller SA/RBAC/Service/Deployment *and* the Sandbox → its PVCs. | argo-cd v2.13.2 `controller/appcontroller.go:1102`; gitops-engine `pkg/utils/kube/kube.go` `IsCRD` | Docs corrected. If you want Application deletion to preserve the Sandbox, the annotation is `Delete=false`, not `Prune=false` → **Option B**. | | F3 | CONCERN | Constraint "images digest-pinned" is met for pi, **not** for the controller (`registry.k8s.io/…-controller:v1.0.4`, tag only; digest only documented). | render; `crane digest` matches README | Accept for the pilot: upstream asset is sha256-pinned, digest recorded, `registry.k8s.io` is mirrored via Harbor `k8s-cache`, and `external-snapshotter` already pulls the same way in this repo. Follow-up if you want the invariant literal → **Option C**. | | F4 | CONCERN | **Half-merge alert:** infra-core alone lands the ServiceMonitor + PrometheusRule through the *existing* `monitoring-servicemonitors`/`monitoring-rules` apps, and the rule has an `absent(up{…})` clause → `AgentSandboxControllerMetricsTargetsDown` (warning) fires ~5–6 min later and keeps firing until prod #794 lands. | the two apps' `path`/`targetRevision: live`; rule expr | Merge the two close together, or accept one warning. Optionally gate the `absent()` clause on the target existing (follow-up). | | F5 | CONCERN | **Cross-Application ordering is weaker than the PR text says.** Sync waves order Application *creation* at the app-of-apps root, but ArgoCD has not health-gated `argoproj.io/Application` since 1.8 (no health customization in this cluster's argocd values), so wave 10 can start before wave −1's namespace exists; it converges via `retry:5` + `selfHeal` (with a brief `CreateNamespace=false` error). **Inside** the app the ordering is real: CRD −10 → controller 0 → Sandbox 10, with the Deployment's built-in health and ArgoCD's ensure-CRD-ready wait. | argo-cd health docs; `grep argoproj.io_Application` → none in argocd values | Accept (self-heals). Follow-up candidate: restore the Application `health.lua` in `argocd-cm`. | | F6 | CONCERN | The controller's RBAC is **cluster-wide**, upstream-verbatim: create/delete/get/list/patch/update/watch on `pods`, `persistentvolumeclaims`, `services` in *every* namespace; extensions role adds pods patch/update, `leases` delete, full `networkpolicies` CRUD. No `pods/exec`, no `pods/attach`, no `secrets`, no impersonate/escalate/bind. | rendered ClusterRoles; kustomize patches only CRDs/Namespace/Deployment | Accept as upstream; documented in §4. Tightening = namespaced rewrite that diverges from upstream (follow-up). | | F7 | SUGGESTION | No readiness probe on the controller Deployment (upstream) → wave 10 gates on Deployment *availability*, not reconciler readiness (bounded; the controller requeues). `ConfigMap/agent-sandbox-config` is referenced `optional: true` and not shipped (harmless empty volume). Controller limits (500m/128Mi) are upstream's example, unmeasured. RBAC delta table overstates the `pods` row (the core role already had those verbs). `readOnlyRootFilesystem` deliberately off (pi writes `/tmp`). | render + module README | Record as follow-ups; none affect the AC. | ### 4. Exposure bound **What this touches.** 4 new **cluster-scoped** CRDs (`agents.x-k8s.io`, `extensions.agents.x-k8s.io`); 2 ClusterRoles + 2 ClusterRoleBindings (cluster-wide write to pods/PVCs/services/networkpolicies); 1 new namespace `agent-sandbox-system` (PSA `enforce: baseline`, `audit`/`warn: restricted`, `Prune=false`); 1 controller Deployment/SA/Service; 1 `Sandbox/pi` → Pod + 2 Longhorn PVCs (6Gi); 1 ServiceMonitor + 1 warning rule. **No webhooks, no hooks, no Jobs, no Secrets, no CI apply path.** **Blast radius if wrong.** - Controller bug/compromise: create/delete **pods, PVCs and Services in any namespace**, and rewrite NetworkPolicies anywhere. That is the club-wide reach — it is upstream's own RBAC, not something this PR widened. - `kubectl delete crd …`: cascades to every Sandbox of that kind **and, via the PVC ownerReferences, every Sandbox PVC**. - `kubectl delete application agent-sandbox` (it carries the ArgoCD resources finalizer): controller SA/RBAC/Service/Deployment **+ the Sandbox + its PVCs**; CRDs and the Namespace survive. - Deleting the namespace: everything in it, including both PVCs. **What is protected, by which mechanism.** sync-prune of CRDs/Namespace → `Prune=false`; CRDs under Application deletion → ArgoCD's `!kube.IsCRD` cascade exclusion (not `Prune=false`); Sandbox API credentials → `automountServiceAccountToken: false` (no Secrets anywhere); install integrity → sha256-pinned vendored asset + digest-pinned pi image; PVC **data** → `longhorn` `reclaimPolicy: Retain` (volume survives as `Released`); merge-order safety → infra-core is inert on its own. **Rollback (operator-actionable).** ```bash # A) Stop managing, keep everything running (no data touched): kubectl -n argocd patch application agent-sandbox --type merge -p '{"metadata":{"finalizers":null}}' kubectl -n argocd delete application agent-sandbox agent-sandbox-namespace # then revert the git PRs so nothing re-creates them. # B) Remove the Sandbox but keep the data (orphan the PVCs FIRST): kubectl -n agent-sandbox-system patch pvc workspace-pi agent-state-pi \ --type=json -p '[{"op":"remove","path":"/metadata/ownerReferences"}]' kubectl -n agent-sandbox-system delete sandbox pi # C) Full removal (PVC objects gone; Longhorn volumes survive Released until reclaimed): kubectl -n argocd delete application agent-sandbox agent-sandbox-namespace kubectl delete crd sandboxes.agents.x-k8s.io sandboxclaims.extensions.agents.x-k8s.io \ sandboxtemplates.extensions.agents.x-k8s.io sandboxwarmpools.extensions.agents.x-k8s.io ``` **Merge order.** infra-core #520 **first**, then prod #794 — or back-to-back. Nothing in infra-core is applied until the prod Application points at it, so #520 alone is inert apart from the monitoring alert (F4). **Do not merge prod first alone**: its Application would reference a path that does not exist on infra-core `live` (SyncError noise; nothing is applied). ### 5. Options + recommendation - **Option A — merge as they now stand (RECOMMENDED).** infra-core `cadb8d7` → prod `ca921ff`. Manifest set unchanged since review except the docs fix; the AC only needs a cluster check, and every destructive path left is explicit and now documented truthfully. Follow-ups (recorded below) do not gate the pilot. - **Option B — add `argocd.argoproj.io/sync-options: Delete=false` to the Sandbox (and namespace) first.** Makes Application deletion non-destructive. Cost: a one-line annotation, CI re-run, and it changes semantics — a pruned `pi-sandbox.yaml` would then leave the Sandbox running (arguably worse than "Git is the source of truth"). My read: not needed now; take it with the VAP follow-up if you want the guard. - **Option C — change first:** digest-pin the controller image (a 4th kustomize delta), gate the alert's `absent()` clause, install upstream's `ValidatingAdmissionPolicy` for `automountServiceAccountToken=false`. Each is a real improvement but none affects the AC, and each costs a re-render + CI cycle. My read: separate follow-up PRs. **Recommendation: Option A.** Then, in rough priority: (1) VAP for the automount invariant + `Delete=false` decision; (2) alert gating; (3) controller image digest pin or amended constraint wording; (4) Application-health customization in `argocd-cm`; (5) `readOnlyRootFilesystem` + `/tmp` emptyDir experiment; (6) PSA `baseline → restricted` flip and the Cilium egress allowlist; (7) the RBAC-delta doc nit and the LD0.2 example-pod namespace comment. ### 6. Operator-verify checklist (your AC) 1. **Read-only health** — `kubectl -n argocd get applications agent-sandbox agent-sandbox-namespace` → `Synced` + `Healthy`. Then `kubectl -n agent-sandbox-system get sandbox,pods,pvc,deploy,svc` and `kubectl -n agent-sandbox-system describe sandbox pi` → `Ready=True`, `Pod/pi` `Running 1/1`, PVCs `workspace-pi` (5Gi) and `agent-state-pi` (1Gi) `Bound`, 4 CRDs `Established`. 2. **Wave evidence** — `kubectl -n argocd get application agent-sandbox -o jsonpath='{.status.operationState.syncResult.resources[*].name}'` / the sync panel: CRDs at wave −10 before the Deployment, and the Sandbox only after the Deployment was Healthy. Expect a brief namespace-race retry on a cold install (F5) — that is expected, not a failure. 3. **AC** — `kubectl -n agent-sandbox-system attach -it pi` → working pi TUI. Detach with **Ctrl-P Ctrl-Q** and confirm the pod survives. If rendering is off, `kubectl -n agent-sandbox-system exec -it pi -- env TERM=xterm-256color pi` (the image sets no `TERM`). 4. **First run needs one decision** — the agent-state PVC is deliberately empty and the manifest carries no credentials. Configure a provider inside the TUI, or pre-seed `settings.json`. Intended steady state: pi pointed at the in-cluster OmniRoute gateway (open question on the ticket). 5. **Persistence** — `pwd` = `/workspace`; `ls -la /home/node/.pi/agent`; write a file, `kubectl -n agent-sandbox-system delete pod pi`, confirm the file is still there. 6. **Guardrails while you test** — do not `kubectl delete crd` (cascades every Sandbox **and PVC**), do not delete the `agent-sandbox` Application (it deletes the Sandbox **and its PVCs**), do not delete the namespace. To clear the Sandbox while keeping data: orphan the PVCs first (§4B). `longhorn` volumes survive as `Released` if you do delete the PVCs. ### 7. What I could not verify (no cluster access) Live `Application` status / cold-install convergence; `Sandbox/pi` `Ready`, Pod `Running`, PVCs `Bound`; the attach/TUI behaviour and detach; PSA admission actually admitting both pods; the Harbor `k8s-cache` pull of the controller image at apply time; and the exact ArgoCD build in-cluster (chart `argo-cd 7.7.10` → v2.13.x; the `!kube.IsCRD` cascade guard was verified at v2.13.2 and is present since at least v2.4). Everything else in this package was re-derived from primary sources on this machine.
aaron merged commit bf90e6af41 into live 2026-09-29 06:26:39 +00:00
Author
Owner

LANDED — infra-core #520 merged (operator-ratified Option A)

Merge method: merge. Merge commit: bf90e6af41893b42a9aacc571a9a66338ae89b49 at 2026-09-29T06:26:38Z. live was c7a74c7d4a02d49bb77087ef63399b96f0811be8 before.

Pre-merge re-verification (at merge time)

  • Branch head = cadb8d7 (plane/HOMELAB-1666-agent-sandbox) — exactly the reviewed head, no new commits.
  • CI re-read via commits/cadb8d7/status (actions API still 500s, HOMELAB-1736): 5/5 success — yaml-lint, shellcheck, terraform-validate, CI Review / helm-validate, CI Review / receiver-test.
  • Branch is exactly 2 commits ahead of live; reviewed diff is purely additive (0 deletion lines, 7 files, +9489).
  • Sibling prod #794 head also re-verified (ca921ff, 4/4 green) and merged 9 s later (2e44e41, 06:26:47Z), keeping the two inside the F4 alert window.

Tree integrity (post-merge)

git diff c7a74c7d..bf90e6a (live before → after) is byte-identical to the reviewed PR diff (diff -q clean). The merge introduced nothing beyond the reviewed contents.

7 files changed, 9489 insertions(+), 0 deletions(-)

GitOps continuation — converged, read-only verified

Time (UTC) Observation
06:26:38 / 06:26:47 #520 / #794 merged
06:30:28 app-of-apps root picked up prod live 2e44e41 (~3 min ArgoCD poll latency)
06:30:09 agent-sandbox-namespace sync finished → Synced + Healthy
06:30:59 4 CRDs Established, all sync-wave: -10 + Prune=false
06:31:10 agent-sandbox sync finished → Synced + Healthy, synced revisions ["bf90e6af…","2e44e413…"] = exactly the two merged SHAs
06:31:43 Sandbox/pi Ready=True (DependenciesReady)
06:32:01 Pod/pi 1/1 Running, workspace-pi 5Gi + agent-state-pi 1Gi Bound

Merge → fully healthy: 5 m 14 s (well inside the 20-minute budget).

Wave ordering held inside the app — syncResult.resources order: 4 CRDs → SA → 2 ClusterRoles → 2 ClusterRoleBindings → Service → Deployment → Sandbox. Deployment/agent-sandbox-controller 1/1 on prod-wk-05 at registry.k8s.io/agent-sandbox/agent-sandbox-controller:v1.0.4 --leader-elect=true --extensions.

F4 did not materialise. The AgentSandboxControllerMetricsTargetsDown rule landed (PrometheusRule/agent-sandbox-alerts, release: kube-prometheus-stack) but never fired: the ServiceMonitor target is up (up{namespace="agent-sandbox-system",service="agent-sandbox-controller"} = 1) and prod merged 9 s after infra-core, so absent(up{…}) was never armed. No alert worth explaining to the operator.

Read-only access note (no new credentials created)

The argocd CLI is configured in ~/.config/argocd but its session token is expired — per the landing instruction I did not re-login. Health was read through the path the ratified package itself documents (kubectl -n argocd get applications …), which spec/tools.md sanctions ("kubectl — cluster operations (read freely)"). Zero mutating calls were made: only get/describe/logs.

Anomaly observed (pre-existing, NOT caused by this change)

agent-sandbox-controller restarted once (exit 1, 06:33:54Z). Logs show the cause: Error retrieving lease lock … context deadline exceeded against https://10.96.0.1:443 → controller-runtime exits on leader-election loss by design. It is collateral, not a defect in these manifests: 9 unrelated components lost leases in the same 2-second window (06:33:52–54) — cilium-operator, kube-controller-manager ×2, kube-scheduler ×3, csi-attacher, csi-provisioner, csi-resizer, csi-snapshotter, sops-secrets-operator — and KubeAPIErrorBudgetBurn is firing at critical (5m/1h). Restart counters in the thousands (kube-scheduler 1255, sops-secrets-operator 3091) and the ArgoCD repo-server's x253 over 20d liveness failures show the condition long predates this landing. This is the tracked incident HOMELAB-1427 (control-plane instability / etcd storage-latency stalls).

Impact on this change: none lasting. The controller re-acquired the lease at 06:33:56 and has been stable since; Sandbox/pi stayed Ready=True, Pod/pi has 0 restarts, both PVCs stayed Bound.

Also confirmed live: PVCs carry a controller ownerReference to the Sandbox (F1's fix is materially correct), and the namespace PSA labels are enforce: baseline / audit+warn: restricted as declared.

## LANDED — infra-core #520 merged (operator-ratified Option A) **Merge method:** `merge`. **Merge commit:** `bf90e6af41893b42a9aacc571a9a66338ae89b49` at `2026-09-29T06:26:38Z`. `live` was `c7a74c7d4a02d49bb77087ef63399b96f0811be8` before. ### Pre-merge re-verification (at merge time) - Branch head = `cadb8d7` (`plane/HOMELAB-1666-agent-sandbox`) — **exactly the reviewed head**, no new commits. - CI re-read via `commits/cadb8d7/status` (actions API still 500s, HOMELAB-1736): **5/5 success** — `yaml-lint`, `shellcheck`, `terraform-validate`, `CI Review / helm-validate`, `CI Review / receiver-test`. - Branch is exactly 2 commits ahead of `live`; reviewed diff is **purely additive** (0 deletion lines, 7 files, +9489). - Sibling prod #794 head also re-verified (`ca921ff`, 4/4 green) and merged **9 s later** (`2e44e41`, `06:26:47Z`), keeping the two inside the F4 alert window. ### Tree integrity (post-merge) `git diff c7a74c7d..bf90e6a` (`live` before → after) is **byte-identical** to the reviewed PR diff (`diff -q` clean). The merge introduced **nothing beyond the reviewed contents**. ``` 7 files changed, 9489 insertions(+), 0 deletions(-) ``` ### GitOps continuation — converged, read-only verified | Time (UTC) | Observation | |---|---| | 06:26:38 / 06:26:47 | #520 / #794 merged | | 06:30:28 | app-of-apps `root` picked up prod `live` `2e44e41` (~3 min ArgoCD poll latency) | | 06:30:09 | `agent-sandbox-namespace` sync finished → **Synced + Healthy** | | 06:30:59 | 4 CRDs **Established**, all `sync-wave: -10` + `Prune=false` | | 06:31:10 | `agent-sandbox` sync finished → **Synced + Healthy**, synced revisions `["bf90e6af…","2e44e413…"]` = exactly the two merged SHAs | | 06:31:43 | `Sandbox/pi` **Ready=True** (`DependenciesReady`) | | 06:32:01 | `Pod/pi` **1/1 Running**, `workspace-pi` 5Gi + `agent-state-pi` 1Gi **Bound** | **Merge → fully healthy: 5 m 14 s** (well inside the 20-minute budget). Wave ordering held inside the app — `syncResult.resources` order: 4 CRDs → SA → 2 ClusterRoles → 2 ClusterRoleBindings → Service → Deployment → Sandbox. `Deployment/agent-sandbox-controller` 1/1 on `prod-wk-05` at `registry.k8s.io/agent-sandbox/agent-sandbox-controller:v1.0.4 --leader-elect=true --extensions`. **F4 did not materialise.** The `AgentSandboxControllerMetricsTargetsDown` rule landed (`PrometheusRule/agent-sandbox-alerts`, `release: kube-prometheus-stack`) but never fired: the ServiceMonitor target is up (`up{namespace="agent-sandbox-system",service="agent-sandbox-controller"} = 1`) and prod merged 9 s after infra-core, so `absent(up{…})` was never armed. No alert worth explaining to the operator. ### Read-only access note (no new credentials created) The `argocd` CLI is configured in `~/.config/argocd` but its session token is **expired** — per the landing instruction I did **not** re-login. Health was read through the path the ratified package itself documents (`kubectl -n argocd get applications …`), which `spec/tools.md` sanctions ("kubectl — cluster operations (read freely)"). **Zero mutating calls** were made: only `get`/`describe`/`logs`. ### Anomaly observed (pre-existing, NOT caused by this change) `agent-sandbox-controller` restarted **once** (exit 1, `06:33:54Z`). Logs show the cause: `Error retrieving lease lock … context deadline exceeded` against `https://10.96.0.1:443` → controller-runtime exits on leader-election loss by design. It is collateral, not a defect in these manifests: **9 unrelated components lost leases in the same 2-second window** (06:33:52–54) — `cilium-operator`, `kube-controller-manager` ×2, `kube-scheduler` ×3, `csi-attacher`, `csi-provisioner`, `csi-resizer`, `csi-snapshotter`, `sops-secrets-operator` — and `KubeAPIErrorBudgetBurn` is firing at **critical** (5m/1h). Restart counters in the thousands (kube-scheduler 1255, sops-secrets-operator 3091) and the ArgoCD repo-server's `x253 over 20d` liveness failures show the condition long predates this landing. This is the tracked incident **HOMELAB-1427** (control-plane instability / etcd storage-latency stalls). Impact on this change: **none lasting**. The controller re-acquired the lease at 06:33:56 and has been stable since; `Sandbox/pi` stayed `Ready=True`, `Pod/pi` has **0 restarts**, both PVCs stayed `Bound`. Also confirmed live: PVCs carry a **controller** `ownerReference` to the `Sandbox` (F1's fix is materially correct), and the namespace PSA labels are `enforce: baseline` / `audit`+`warn: restricted` as declared.
Sign in to join this conversation.
No reviewers
No labels
operator-review
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
Homelab/infra-core!520
No description provided.