Maintain: Platform
Platform has two surfaces to keep healthy and advance — the repository (manifests, Helm,
Flux, policies) and the live platform (the running prod cluster it delivers). Cover both each
time you select Platform: the repo can be green while the cluster is unhealthy (and vice-versa).
Repository menu (canonical, maintained in the product itself)
The canonical repo maintenance task menu lives in the repo — read the ## Maintenance section
of platform/AGENTS.md (on the submodule's latest main):
https://github.com/devantler-tech/platform/blob/main/AGENTS.md. For validating a repo/PR change,
use static validation (kustomize build + schema/kubeconform, per that menu) — never spin up a
cluster just to test a manifest diff. Shared cross-repo rules are in the monorepo
AGENTS.md. This part of the card is a pointer by design — the repo menu is
maintained once, in the product's own AGENTS.md.
Status & live-platform health investigation (read-only)
Investigating how the platform itself is doing is a first-class part of every Platform run — do it
read-only against the running prod cluster + its observability stack, and dedupe against the
accepted "known non-issues" baseline in your native memory so you don't re-chase by-design/transient
signals. Pin the context — resolve it ONCE with ctx=$(.claude/scripts/prod-kube-context.sh) || exit 2, then
pass --context="$ctx" to every call (the shared kubeconfig drifts across
parallel sessions, and its API endpoint can go stale after a control-plane recreate; the refresh recipe
- healthy baseline counts live in native memory). Query at least the following; the list is
non-exhaustive — chase any other anomaly you see:
- Flux Kustomizations —
kubectl --context="$ctx" get kustomization -A (Ready? suspended?
drifting/last-applied revision? health-check timeouts?). For a stuck/failing reconciliation, the
gitops-cluster-debug skill (Flux MCP server) traces the dependency chain on the live cluster.
- Flux HelmReleases —
kubectl --context="$ctx" get helmrelease -A (Ready? install/upgrade
retries exhausted? stuck/pending-upgrade?).
- Coroot — Incidents, Alerts/SLO burn, Traces, Logs, Risks (deployment & health-check risks), and
cost optimizations (Node/cost view). Use its read API — the access recipe (project id, auth,
the SPA-200-masks-404 gotcha, the empty-Hetzner-cost-rollup known limitation) is in native memory.
- Kubescape — the three finding surfaces (posture / CVE / runtime), for security-posture regressions
vs the last run. A
0/empty reading is NOT automatically "clean" — verify the scanner is actually
producing data first (a broken scanner reads identically to a compliant cluster). See the dedicated
Security posture section below for the object names, the broken-vs-clean checks, and the
fix-vs-except ladder.
- Kyverno + Policy Reporter — policy validation & enforcement:
kubectl --context="$ctx" get cpol,pol -A (policies present? mode Audit vs Enforce?) and polr,cpolr -A (PolicyReport /
ClusterPolicyReport — failing rules and violating resources). Policy Reporter aggregates every
report into a dashboard + read API (SSO UI at policy-reporter.${domain}; in-cluster API
policy-reporter.policy-reporter.svc:8080) for a whole-cluster view of failing results. Driving
those failures to zero and holding it is a standing objective — see the dedicated Policy
compliance section below.
- Kubernetes events & warnings —
kubectl --context="$ctx" get events -A --field-selector type=Warning, plus unhealthy / CrashLooping / Pending pods and abnormal restart
counts.
- Other problems — node/Talos & etcd-quorum health, Longhorn volume health, cert-manager certs,
external-secrets/OpenBao sync, ingress/Envoy reachability — and anything else off the healthy baseline.
Guardrails. Investigation is read-only — never mutate prod to "test", and never spin up a new
cluster for it (live-cluster reliability E2E is the ~weekly heavy task, and you never spin up real
clusters more than once a day portfolio-wide — contract Cadence). Operational recovery follows
platform/AGENTS.md + the DR runbook. Turn a confirmed, off-baseline problem into the right
artifact — a root-cause draft PR to the platform repo (manifest/Helm/policy fix) or a triaged issue
— never a hand-edit of generated files and never a guardrail bypass.
Security posture (Kubescape) — drive to 100% and hold
Kubescape runs three finding surfaces; driving all three to 100% and holding them is a standing
objective, not a floor (epic + children: devantler-tech/platform#2447).
On the live-health cadence the Survey step delegates this read to the read-only
platform-security-surveyor agent (compact delta
digest, liveness-first); the object names and checks below are the shared reference for that agent
and for any interactive investigation.
The recurring trap, learned the hard way (2026-07-04, all three surfaces were silently dead): an
empty/zero reading almost always means the scanner is broken, not that the cluster is clean — a broken
scanner and a compliant cluster look identical, so check liveness first, every time:
Probe rule that overrides reflex: Kubescape CR LISTs return spec-stripped skeletons. Use a
cluster-wide LIST only as LIST metadata for coverage and freshness: select object names, timestamps,
and identity labels; all-zero severities, empty matches, or empty VEX payloads in that response are
display artifacts, not findings. For CVE coverage, reconcile the result identities and manifest/summary
pairs against the current workload/container inventory before making any cluster-wide claim; an
absent or stale result is a coverage gap, not zero findings. Before judging payload quality,
kubectl --context="$ctx" get <crd> <name> -n <ns> -o json by name and sample 2–3 objects per surface. For CVE
liveness, directly GET both named vulnerabilitymanifests and their corresponding
vulnerabilitymanifestsummaries, then cross-check scanner freshness and logs. Never declare the
scanner broken or the cluster clean from a LIST projection. Sampling proves liveness only. If the
contributing set fits the read bound, directly GET every object whose payload contributes; otherwise
use a trusted aggregate endpoint already verified against direct samples. When neither a payload-
complete source nor complete inventory coverage can be established safely,
report the cluster-wide result as unavailable or partial and name the missing proof. Never
extrapolate cluster-wide findings from the liveness sample. Bounded proof failure is per-surface:
posture, CVE, and runtime each report unavailable or partial instead of inventing a numeric value.
Every confirmed partial coverage gap must also appear in deltas_needing_action so the orchestrator
turns the blind spot into tracked remediation.
- Posture (config scan) —
configurationscansummaries / workloadconfigurationscansummaries
(per-namespace scores + failed controls). Broken if scores are 0.00 across frameworks,
controls: null en masse, or objects are days stale — check the kubescape scanner pod logs for scan
aborts. Note: the CI gate (ksail workload scan --framework nsa --compliance-threshold N) is a
separate static scan in a healthy CLI context — a green CI gate does NOT prove the in-cluster
scan works.
- CVE (kubevuln) —
vulnerabilitymanifestsummaries / vulnerabilitymanifests. Liveness requires
(Grype matches or scanner version metadata) with coherent paired summaries
(spec.metadata.tool.version or the kubescape.io/tool-version annotation) across the direct
samples. A genuinely zero-match image is healthy when version evidence, summary refs, and zero
counters agree; positive manifest matches with an empty or disagreeing summary are partial, not
healthy. A blank
spec.metadata.tool.name alone is an upstream representation quirk, not an outage.
Broken if direct manifests carry neither matches nor scanner-version evidence and summaries are
empty (both .all and .relevant) — check kubevuln logs for ScanCP … partial (the relevancy path
aborting on partial ApplicationProfiles). Prioritise by relevancy × severity × fixability; emit VEX
to suppress non-reachable CVEs.
- Runtime (node-agent) —
applicationprofiles, networkneighborhoods, and the
node_agent_alert_counter metric. Invisible if the exporters are stdout-only
(alertManagerExporterUrls: [], prometheusExporterEnabled: false). Route natively to Coroot
(minimal-custom, all declarative): node-agent Prometheus exporter → coroot.com/scrape-metrics
annotation → a custom PromQL alertingRules[] in the Coroot CR → the existing Slack notificationIntegrations
webhook — no new infra (Coroot CE supports custom-PromQL alerts natively; it has no standard
Alertmanager, so don't add one).
Fix-vs-except ladder — the definition of done for a finding. An exception is the audited last resort,
never the first move:
- Fix the manifest/root cause — the real remediation (securityContext, RBAC scope, probes, labels).
- Runtime-enforce — if it's mutated/enforced at admission (Kyverno) or by the network layer
(Cilium), it's covered even where the static scan can't see it; graduate a fixed control into Kyverno
Enforce so it can't regress.
- Except only when genuinely irreducible (e.g. C-0002, the KubeVirt operator's
pods/exec RBAC) — a
narrow, justified ClusterSecurityException in k8s/bases/infrastructure/cluster-security-exceptions/
with a written why, reviewed via PR, and periodically pruned. A growing exceptions dir is a smell,
not progress.
A confirmed off-baseline finding on any surface — including a scanner that has silently stopped
producing data — becomes a security issue under the epic (capture step), or a hotfix if it's active
breakage. Ratchet the CI --compliance-threshold up as gaps close; never lower it.
Policy compliance (Kyverno) — drive violations to zero and hold
Kyverno admission policies emit PolicyReport / ClusterPolicyReport results for every workload;
driving the failing results to zero and holding them there — so everything in the cluster runs
compliant — is a standing objective, not a floor. This is the admission-time twin of the Kubescape
Security posture program above (policy vs posture scan); apply the same discipline.
- Enumerate every run via Policy Reporter — the dashboard/read API that aggregates all reports
(SSO UI at
policy-reporter.${domain}; in-cluster API policy-reporter.policy-reporter.svc:8080) —
or kubectl --context="$ctx" get polr,cpolr -A. The fail count is the zero-and-hold
target — treat a newly introduced fail as a regression to clear promptly, like a red CI
check, not just the long-standing backlog. Track warn results in a separate tally: they are a
softer lead signal (a policy still in Audit that will graduate to Enforce, or a deprecation
notice), so triage and work them down — fix the ones that mask a real issue — but they do not
share fail's hard zero-gate, and lumping them into one number just muddies the remediation queue.
- Fix-vs-except ladder — same as Kubescape (an exception is the audited last resort, never first):
- Fix the manifest/root cause — correct the offending workload at its source so it satisfies the
rule (add the missing securityContext / limits / label / PDB / probe / image pin), shipped as a
draft PR. Never silence a real violation.
- Runtime-enforce & graduate — once a rule's findings are at zero, graduate its policy from
Audit to Enforce so the violation is blocked at admission and cannot regress (a weak
Audit-only policy that could Enforce is itself a roadmap lever).
- Except only when genuinely by-design — add a scoped, reasoned
exclude to the relevant
Kyverno ClusterPolicy in k8s/bases/infrastructure/cluster-policies/, with a written why,
reviewed via PR. The repo exempts via per-policy exclude blocks — PolicyException CRs are
not enabled. An exclude that hides a fixable violation is a suppression, not an exception; a
growing exclude list is a smell, not progress.
Record the residual (genuinely-excepted) baseline in native memory so a later run doesn't re-chase a
documented by-design exemption — mirroring the Kubescape known-non-issues baseline. A confirmed
violation becomes a security/bug draft PR (fix) or a triaged issue (larger cleanup); active
admission breakage (an Enforce policy rejecting a legitimate workload) is a hotfix.
Roadmap & enhancement
Platform's roadmap lives in GitHub Issues on devantler-tech/platform (roadmap epics +
milestones). Advance via product-engineering — on the
repo side: manifest/Helm/Flux structure & quality, policy & security posture, Kustomize hygiene
(static validation, not unit tests). On the live side: the health investigation above is also an
enhancement engine — gaps it surfaces (a missing alert/SLO, a weak/Audit-only policy that should
Enforce, an unaddressed Coroot risk or cost optimization, a reliability hotspot) become roadmap/
enhancement issues or focused draft PRs.
1---2name: maintain-platform3description: Repo + live-platform health/maintenance menu for devantler-tech/platform (GitOps — Kustomize overlays + Flux CD, Cilium/Talos/KSail/SOPS). Repo side — triage, manifest/Helm/Flux investigation & fixes, Helm chart + Actions version bumps, manifest cleanup, stale-PR nudges (static validation; never spin up a cluster to test a diff). Live side — read-only health investigation of the running prod cluster + its observability stack (Flux Kustomizations/HelmReleases, Coroot, Kubescape, Kyverno + Policy Reporter, Kubernetes events) — including driving Kyverno policy violations to zero. Use when the daily maintainer selects Platform.4---56# Maintain: Platform78Platform has **two surfaces to keep healthy *and* advance** — the **repository** (manifests, Helm,9Flux, policies) and the **live platform** (the running prod cluster it delivers). **Cover both** each10time you select Platform: the repo can be green while the cluster is unhealthy (and vice-versa).1112## Repository menu (canonical, maintained in the product itself)13The canonical repo maintenance task menu lives **in the repo** — read the **`## Maintenance`** section14of `platform/AGENTS.md` (on the submodule's latest `main`):15<https://github.com/devantler-tech/platform/blob/main/AGENTS.md>. For **validating a repo/PR change**,16use **static validation** (kustomize build + schema/kubeconform, per that menu) — **never spin up a17cluster just to test a manifest diff**. Shared cross-repo rules are in the monorepo18[`AGENTS.md`](../../../../AGENTS.md). This part of the card is a pointer by design — the repo menu is19maintained once, in the product's own `AGENTS.md`.2021## Status & live-platform health investigation (read-only)22Investigating **how the platform itself is doing** is a first-class part of every Platform run — do it23**read-only** against the running prod cluster + its observability stack, and **dedupe against the24accepted "known non-issues" baseline in your native memory** so you don't re-chase by-design/transient25signals. Pin the context — resolve it ONCE with `ctx=$(.claude/scripts/prod-kube-context.sh) || exit 2`, then26pass `--context="$ctx"` to every call (the shared kubeconfig drifts across27parallel sessions, and its API endpoint can go stale after a control-plane recreate; the refresh recipe28+ healthy baseline counts live in native memory). Query **at least** the following; the list is29**non-exhaustive — chase any other anomaly** you see:3031- **Flux Kustomizations** — `kubectl --context="$ctx" get kustomization -A` (Ready? suspended?32 drifting/last-applied revision? health-check timeouts?). For a stuck/failing reconciliation, the33 `gitops-cluster-debug` skill (Flux MCP server) traces the dependency chain on the live cluster.34- **Flux HelmReleases** — `kubectl --context="$ctx" get helmrelease -A` (Ready? install/upgrade35 retries exhausted? stuck/pending-upgrade?).36- **Coroot** — Incidents, Alerts/SLO burn, Traces, Logs, Risks (deployment & health-check risks), and37 **cost optimizations** (Node/cost view). Use its **read API** — the access recipe (project id, auth,38 the SPA-200-masks-404 gotcha, the empty-Hetzner-cost-rollup known limitation) is in native memory.39- **Kubescape** — the three finding surfaces (posture / CVE / runtime), for security-posture regressions40 vs the last run. **A `0`/empty reading is NOT automatically "clean" — verify the scanner is actually41 producing data first** (a broken scanner reads identically to a compliant cluster). See the dedicated42 **Security posture** section below for the object names, the broken-vs-clean checks, and the43 fix-vs-except ladder.44- **Kyverno + Policy Reporter** — policy **validation & enforcement**: `kubectl --context="$ctx"45 get cpol,pol -A` (policies present? mode Audit vs Enforce?) and `polr,cpolr -A` (PolicyReport /46 ClusterPolicyReport — failing rules and violating resources). **Policy Reporter** aggregates every47 report into a dashboard + read API (SSO UI at `policy-reporter.${domain}`; in-cluster API48 `policy-reporter.policy-reporter.svc:8080`) for a whole-cluster view of failing results. Driving49 those failures to **zero and holding it** is a standing objective — see the dedicated **Policy50 compliance** section below.51- **Kubernetes events & warnings** — `kubectl --context="$ctx" get events -A52 --field-selector type=Warning`, plus unhealthy / CrashLooping / Pending pods and abnormal restart53 counts.54- **Other problems** — node/Talos & etcd-quorum health, Longhorn volume health, cert-manager certs,55 external-secrets/OpenBao sync, ingress/Envoy reachability — and anything else off the healthy baseline.5657**Guardrails.** Investigation is **read-only** — never mutate prod to "test", and never spin up a *new*58cluster for it (live-cluster reliability E2E is the ~weekly heavy task, and you **never spin up real59clusters more than once a day** portfolio-wide — contract *Cadence*). Operational recovery follows60`platform/AGENTS.md` + the DR runbook. Turn a **confirmed, off-baseline** problem into the right61artifact — a root-cause **draft PR** to the platform repo (manifest/Helm/policy fix) or a triaged issue62— never a hand-edit of generated files and never a guardrail bypass.6364## Security posture (Kubescape) — drive to 100% and hold65Kubescape runs **three finding surfaces**; **driving all three to 100% and holding them is a standing66objective, not a floor** (epic + children: [devantler-tech/platform#2447](https://github.com/devantler-tech/platform/issues/2447)).67On the live-health cadence the Survey step delegates this read to the read-only68[`platform-security-surveyor`](../../../agents/platform-security-surveyor.md) agent (compact delta69digest, liveness-first); the object names and checks below are the shared reference for that agent70and for any interactive investigation.71The recurring trap, learned the hard way (2026-07-04, all three surfaces were silently dead): **an72empty/zero reading almost always means the scanner is broken, not that the cluster is clean** — a broken73scanner and a compliant cluster look identical, so **check liveness first, every time**:7475**Probe rule that overrides reflex:** **Kubescape CR LISTs return spec-stripped skeletons.** Use a76cluster-wide LIST only as **LIST metadata for coverage and freshness**: select object names, timestamps,77and identity labels; all-zero severities, empty matches, or empty VEX payloads in that response are78display artifacts, not findings. For CVE coverage, reconcile the result identities and manifest/summary79pairs against the **current workload/container inventory** before making any cluster-wide claim; an80absent or stale result is a coverage gap, not zero findings. Before judging payload quality,81`kubectl --context="$ctx" get <crd> <name> -n <ns> -o json` by name and **sample 2–3 objects per surface**. For CVE82liveness, directly GET both named `vulnerabilitymanifests` and their corresponding83`vulnerabilitymanifestsummaries`, then cross-check scanner freshness and logs. Never declare the84scanner broken or the cluster clean from a LIST projection. Sampling proves **liveness only**. If the85contributing set fits the read bound, directly GET every object whose payload contributes; otherwise86use a trusted aggregate endpoint already verified against direct samples. When neither a payload-87complete source nor complete inventory coverage can be established safely,88**report the cluster-wide result as unavailable or partial** and name the missing proof. Never89extrapolate cluster-wide findings from the liveness sample. Bounded proof failure is per-surface:90**posture, CVE, and runtime each report unavailable or partial** instead of inventing a numeric value.91Every confirmed partial coverage gap must also appear in `deltas_needing_action` so the orchestrator92turns the blind spot into tracked remediation.9394- **Posture** (config scan) — `configurationscansummaries` / `workloadconfigurationscansummaries`95 (per-namespace scores + failed controls). **Broken if** scores are `0.00` across frameworks,96 `controls: null` en masse, or objects are days stale — check the `kubescape` scanner pod logs for scan97 aborts. Note: the CI gate (`ksail workload scan --framework nsa --compliance-threshold N`) is a98 **separate** static scan in a healthy CLI context — **a green CI gate does NOT prove the in-cluster99 scan works.**100- **CVE** (kubevuln) — `vulnerabilitymanifestsummaries` / `vulnerabilitymanifests`. Liveness requires101 **(Grype matches or scanner version metadata) with coherent paired summaries**102 (`spec.metadata.tool.version` or the `kubescape.io/tool-version` annotation) across the direct103 samples. A genuinely zero-match image is healthy when version evidence, summary refs, and zero104 counters agree; positive manifest matches with an empty or disagreeing summary are partial, not105 healthy. A blank106 `spec.metadata.tool.name` alone is an upstream representation quirk, not an outage.107 **Broken if** direct manifests carry neither matches nor scanner-version evidence and summaries are108 empty (both `.all` and `.relevant`) — check kubevuln logs for `ScanCP … partial` (the relevancy path109 aborting on partial ApplicationProfiles). Prioritise by relevancy × severity × fixability; emit VEX110 to suppress non-reachable CVEs.111- **Runtime** (node-agent) — `applicationprofiles`, `networkneighborhoods`, and the112 `node_agent_alert_counter` metric. **Invisible if** the exporters are stdout-only113 (`alertManagerExporterUrls: []`, `prometheusExporterEnabled: false`). Route **natively to Coroot**114 (minimal-custom, all declarative): node-agent Prometheus exporter → `coroot.com/scrape-metrics`115 annotation → a custom PromQL `alertingRules[]` in the Coroot CR → the existing Slack `notificationIntegrations`116 webhook — **no new infra** (Coroot CE supports custom-PromQL alerts natively; it has no standard117 Alertmanager, so don't add one).118119**Fix-vs-except ladder — the definition of done for a finding.** An exception is the audited last resort,120never the first move:1211. **Fix the manifest/root cause** — the real remediation (securityContext, RBAC scope, probes, labels).1222. **Runtime-enforce** — if it's mutated/enforced at admission (Kyverno) or by the network layer123 (Cilium), it's covered even where the static scan can't see it; **graduate a fixed control into Kyverno124 `Enforce`** so it can't regress.1253. **Except only when genuinely irreducible** (e.g. C-0002, the KubeVirt operator's `pods/exec` RBAC) — a126 narrow, justified `ClusterSecurityException` in `k8s/bases/infrastructure/cluster-security-exceptions/`127 with a written *why*, reviewed via PR, and periodically pruned. A growing exceptions dir is a smell,128 not progress.129130A confirmed off-baseline finding on any surface — **including a scanner that has silently stopped131producing data** — becomes a `security` issue under the epic (capture step), or a hotfix if it's active132breakage. Ratchet the CI `--compliance-threshold` up as gaps close; never lower it.133134## Policy compliance (Kyverno) — drive violations to zero and hold135Kyverno admission policies emit `PolicyReport` / `ClusterPolicyReport` results for every workload;136**driving the failing results to zero and holding them there — so everything in the cluster runs137compliant — is a standing objective, not a floor.** This is the admission-time twin of the Kubescape138*Security posture* program above (policy vs posture scan); apply the same discipline.139140- **Enumerate every run** via **Policy Reporter** — the dashboard/read API that aggregates all reports141 (SSO UI at `policy-reporter.${domain}`; in-cluster API `policy-reporter.policy-reporter.svc:8080`) —142 or `kubectl --context="$ctx" get polr,cpolr -A`. **The `fail` count is the zero-and-hold143 target** — treat a **newly introduced** `fail` as a regression to clear promptly, like a red CI144 check, not just the long-standing backlog. **Track `warn` results in a *separate* tally**: they are a145 softer lead signal (a policy still in `Audit` that will graduate to `Enforce`, or a deprecation146 notice), so triage and work them down — fix the ones that mask a real issue — but they do **not**147 share `fail`'s hard zero-gate, and lumping them into one number just muddies the remediation queue.148- **Fix-vs-except ladder — same as Kubescape** (an exception is the audited last resort, never first):149 1. **Fix the manifest/root cause** — correct the offending workload at its source so it satisfies the150 rule (add the missing securityContext / limits / label / PDB / probe / image pin), shipped as a151 draft PR. **Never** silence a real violation.152 2. **Runtime-enforce & graduate** — once a rule's findings are at zero, **graduate its policy from153 `Audit` to `Enforce`** so the violation is blocked at admission and cannot regress (a weak154 Audit-only policy that could Enforce is itself a roadmap lever).155 3. **Except only when genuinely by-design** — add a **scoped, reasoned `exclude`** to the relevant156 Kyverno `ClusterPolicy` in `k8s/bases/infrastructure/cluster-policies/`, with a written *why*,157 reviewed via PR. The repo exempts via **per-policy `exclude` blocks — `PolicyException` CRs are158 not enabled**. An `exclude` that hides a fixable violation is a suppression, not an exception; a159 growing exclude list is a smell, not progress.160161Record the residual (genuinely-excepted) baseline in native memory so a later run doesn't re-chase a162documented by-design exemption — mirroring the Kubescape known-non-issues baseline. A confirmed163violation becomes a `security`/`bug` draft PR (fix) or a triaged issue (larger cleanup); active164admission breakage (an `Enforce` policy rejecting a legitimate workload) is a hotfix.165166## Roadmap & enhancement167Platform's roadmap lives in **GitHub Issues** on `devantler-tech/platform` (`roadmap` epics +168milestones). **Advance** via [`product-engineering`](../../product-engineering/SKILL.md) — on the169**repo** side: manifest/Helm/Flux structure & quality, policy & security posture, Kustomize hygiene170(static validation, not unit tests). On the **live** side: the health investigation above is also an171enhancement engine — gaps it surfaces (a missing alert/SLO, a weak/Audit-only policy that should172Enforce, an unaddressed Coroot risk or cost optimization, a reliability hotspot) become `roadmap`/173`enhancement` issues or focused draft PRs.