A service that reads this repository's Gitea issues, decides what to do about each thread as the Program Manager persona, and runs one worker agent per assignment that authors a change, runs the merge gate and opens a pull request. It reacts to issue creation and to comments; the assignment and all correspondence travel through the issue thread and the PR. It authenticates with an Anthropic subscription token. It never pushes to main and never merges. Three choices carry the design, with alternatives recorded in docs/plans/: - it POLLS rather than receiving webhooks (Gitea's webhook SSRF guard, lost deliveries, and no inbound reach needed at all); - it keeps NO state: the trigger is "the newest thing in this thread was not written by me", so the issue thread is the state — no PVC, no backup obligation; - the model holds NO tools: its reply is one JSON object this code validates, so nothing in the path executes text. The boundary is platform/kyverno/admission/pm-agent-job-shape.yaml (Enforce, failurePolicy: Fail), not the RBAC: Kubernetes places no `use` check on serviceAccountName, so "may create Jobs" is "may run arbitrary code as the namespace's most privileged identity". Six rules admit only the committed shape, and the worker identity is bound to nothing with no token mounted, so there is nothing to escalate to. 28/28 offline Kyverno assertions. Nothing has run: the image has never been built or pushed, and both the image digest and the two credentials are deliberate placeholders. See the handoff entry's "What I did NOT verify". Refs CLAUDE.md §1/§3/§6, R-SEC-02, R-GITOPS-02. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
82 lines
7.2 KiB
YAML
82 lines
7.2 KiB
YAML
---
|
|
# Base Flux entrypoint set. Shared by BOTH environment overlays (clusters/dev, clusters/prod).
|
|
# Overlays reference this base and patch ONLY the cluster-vars ConfigMap (R-ENV-02).
|
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
kind: Kustomization
|
|
resources:
|
|
- flux-system.yaml # GitRepository (reconciliation source)
|
|
# Cilium (the CNI) — its OWN Kustomization, split OUT of `platform` (2026-09-01). The mirror image
|
|
# of the oauth2-proxy/headlamp splits: those are leaves that must never gate a tier (wait:false);
|
|
# Cilium is the one component that legitimately SHOULD gate everything, so it keeps wait:true and
|
|
# `platform` dependsOn it. Splitting it out buys a per-CNI Ready condition, CNI-appropriate
|
|
# interval/timeout, and `flux suspend kustomization cilium` as an emergency valve that does not
|
|
# freeze the other eleven platform components. See clusters/base/cilium.yaml.
|
|
- cilium.yaml # Flux Kustomization -> ./platform/cilium (dependsOn flux-system, wait:true)
|
|
# Intel GPU device plugin — its OWN Kustomization (dependsOn cilium, wait:true), NOT a line in
|
|
# platform/kustomization.yaml. Same reasoning as the Cilium split-out, applied to a leaf: inside
|
|
# that wait:true tier a wedged 3-node DaemonSet would be indistinguishable from eleven unrelated
|
|
# components AND would stall platform-config + every apps wave on "dependency not ready" — so a
|
|
# component added to fix a silent no-op (Plex software transcoding) could cause a real outage.
|
|
# wait:true here is a HEALTH SIGNAL, not a gate: nothing in the graph dependsOn it.
|
|
# It advertises `gpu.intel.com/i915`; the renderer change that REQUESTS that resource must land
|
|
# strictly later. See clusters/base/intel-gpu-plugin.yaml.
|
|
- intel-gpu-plugin.yaml # Flux Kustomization -> ./platform/intel-gpu-plugin (dependsOn cilium)
|
|
- platform.yaml # Flux Kustomization -> ./platform (CONTROLLERS: HelmReleases / CRDs)
|
|
- platform-config.yaml # Flux Kustomization -> ./platform-config (CONFIGS: CRs; dependsOn platform)
|
|
# SECOND-ORDER CDI configs — CRs whose CRD is created by a controller AT RUNTIME, not by any
|
|
# manifest in git. cdi-operator.yaml registers only `cdis.cdi.kubevirt.io`; `storageprofiles` (and
|
|
# datavolumes, datasources, …) appear only after cdi-operator reconciles the `CDI` CR that
|
|
# platform-config applies. Because Flux dry-runs every object in a pass before applying any of them,
|
|
# keeping the StorageProfiles in platform-config was a TRUE DEADLOCK (unknown kind -> pass aborts ->
|
|
# CR never applied -> CRD never created -> tier never converges), invisible on dev and fatal on any
|
|
# cold cluster — observed on prod 2026-09-05. This tier is the durable fix; `standup.sh crds` stays
|
|
# as the belt-and-braces repair for clusters already wedged. See clusters/base/platform-config-cdi.yaml.
|
|
- platform-config-cdi.yaml # Flux Kustomization -> ./platform-config-cdi (dependsOn platform-config)
|
|
# App tier, split into dependency-ordered WAVES (was the single ./apps `apps` Kustomization).
|
|
# Each apps-wave-<N> dependsOn wave N-1 (wave 0 dependsOn platform-config) and health-gates
|
|
# (wait:true) ONLY its own wave, so a crashloop in a later wave can't stall an earlier one.
|
|
# GENERATED by `catalog/render.py --emit-waves`; add a line here only when a NEW deepest wave
|
|
# is introduced (a service deeper than any existing wave). See DESIGN-waves.md.
|
|
- apps-wave-0.yaml # Flux Kustomization -> ./apps/wave-0 (dependsOn platform-config)
|
|
- apps-wave-1.yaml # Flux Kustomization -> ./apps/wave-1 (dependsOn apps-wave-0)
|
|
- apps-wave-2.yaml # Flux Kustomization -> ./apps/wave-2 (dependsOn apps-wave-1)
|
|
# VM tier (KubeVirt) — non-gating (wait:false), separate lifecycle from the app waves.
|
|
- apps-vms.yaml # Flux Kustomization -> ./apps/vms (dependsOn platform-config)
|
|
- conformance.yaml # Flux Kustomization -> ./conformance/incluster (non-gating; R-SEC-02 trigger identity)
|
|
# oauth2-proxy forward-auth — POST-platform tier (dependsOn platform-config, wait:false). A leaf
|
|
# service that needs Keycloak + trusted TLS + split-horizon DNS must NOT sit in the gating
|
|
# `platform` tier, where it self-gated its own dependencies into a cold-start deadlock (twice).
|
|
- oauth2-proxy.yaml # Flux Kustomization -> ./platform/oauth2-proxy (dependsOn platform-config)
|
|
# headlamp dashboard — POST-platform tier (dependsOn platform-config, wait:false). A leaf UI whose
|
|
# HelmRelease can crashloop (upstream headlamp-0.40.1 `-session-ttl` chart/image desync) must NOT
|
|
# sit in the gating `platform` tier, where its unhealth stalled platform-config + oauth2-proxy +
|
|
# every apps wave/vm on "dependency not ready" (DECISIONS-LOG: "Headlamp gating stall").
|
|
- headlamp.yaml # Flux Kustomization -> ./platform/headlamp (dependsOn platform-config)
|
|
# Homepage apex landing dashboard — POST-platform tier (dependsOn platform-config, wait:false).
|
|
# A leaf UI at home.keithjackson.dev; same non-gating rationale as headlamp/oauth2-proxy (a UI must
|
|
# never gate the platform chain). Carries no SOPS secret, so clusters/dev adds no decryption patch.
|
|
- homepage.yaml # Flux Kustomization -> ./platform/homepage (dependsOn platform-config)
|
|
# Crafty Controller — Minecraft management UI, web UI Keycloak-gated by a per-pod oauth2-proxy
|
|
# reverse-proxy sidecar (Option B). POST-platform tier (dependsOn platform-config, wait:false); same
|
|
# non-gating leaf-UI rationale as headlamp/homepage/oauth2-proxy (a UI must never gate the platform).
|
|
- crafty.yaml # Flux Kustomization -> ./platform/crafty (dependsOn platform-config)
|
|
# Alert runbooks — POST-platform tier (dependsOn platform-config, wait:false). Serves the 34
|
|
# runbooks the alert rules' `runbook_url` annotations point at, at <apex>/runbooks/<name>; before
|
|
# this tier existed that path was a 404 and every alert handed the operator a dead link. Same
|
|
# non-gating leaf rationale as headlamp/homepage/crafty. Carries NO postBuild substitution on
|
|
# purpose — it renders documentation containing `${...}` shell examples that envsubst would empty;
|
|
# see clusters/base/runbooks.yaml.
|
|
- runbooks.yaml # Flux Kustomization -> ./runbooks (dependsOn platform-config)
|
|
# Velero backup controller — POST-platform tier (dependsOn platform-config, wait:false). A backup
|
|
# controller must NEVER gate the platform chain; same non-gating rationale as headlamp/oauth2-proxy.
|
|
# Carries velero-cloud.sops.yaml, so clusters/dev adds a SOPS decryption patch for it (by name).
|
|
- backup.yaml # Flux Kustomization -> ./platform/velero (dependsOn platform-config)
|
|
# PM agent — reads Gitea issues and runs one worker agent per assignment that authors a
|
|
# change, gates it and opens a pull request. POST-APP tier (dependsOn apps-wave-0,
|
|
# wait:false): what it needs is GITEA, not Keycloak or the Gateway, and it must never gate
|
|
# anything — it CrashLoops by design while its credentials are still placeholders. Carries
|
|
# pm-agent-credentials.sops.yaml, so both overlays add a SOPS decryption patch for it (by
|
|
# name). See clusters/base/pm-agent.yaml and platform/pm-agent/README.md.
|
|
- pm-agent.yaml # Flux Kustomization -> ./platform/pm-agent (dependsOn apps-wave-0)
|
|
- cluster-vars.yaml # overlay knob surface (base defaults)
|