infra-helmfile: tatara-helmfile Deploy Runner + tatara Bucket Removal Implementation Plan¶
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: In the existing GitLab ~/Documents/infra/helmfile repo, add a dedicated in-cluster ARC runner scale set (arc-runner-tatara-helmfile) that runs as a new cluster-admin-bound ServiceAccount for autonomous helmfile apply from the new tatara-helmfile GitHub repo, and fully remove the helmfiles/tatara/ bucket (reversing the 2026-06-05 consolidation), via one GitLab MR off main.
Architecture: Two independent workstreams in a single worktree. (A) Add a new release in helmfiles/coding/helmfile.yaml.gotmpl mirroring the per-repo runner pattern but with its own values file (dedicated SA tatara-helmfile-deployer, not the shared tatara-ci-dispatcher kaniko SA) plus a raw pre-manifest creating that SA + a cluster-admin ClusterRoleBinding (modelled on the cluster_cleanup precedent). (B) Delete helmfiles/tatara/, drop its include from the root helmfile.yaml.gotmpl, confirm .parse.py regenerates valid CI with no dangling needs, and record the reversal in MEMORY.md. Tools on the runner come from the standard ghcr.io/actions/actions-runner image plus a mise install bootstrap in the tatara-helmfile workflows (decided below).
Tech Stack: Helmfile 1.5.3 (gotmpl), Helm 4.2.0, ARC gha-runner-scale-set 0.14.2, raw Kubernetes RBAC manifests applied via the repo .hook.sh presync, GitLab CI dynamic-pipeline generator (.parse.py, pydantic), sops PGP, pre-commit (yamllint/gitleaks/check-yaml).
Deferred-question resolution: runner image + tool delivery¶
The spec (Sub-system B) defers: "use the same runner image as other tatara runners + mise bootstrap, or a dedicated image. Decide in plan."
Decision: standard ghcr.io/actions/actions-runner:2.335.1 image (identical to every other per-repo tatara runner) + mise bootstrap inside the tatara-helmfile workflows. No dedicated runner image, no initContainer.
Rationale, grounded in the repo: - The base ghcr.io/actions/actions-runner image ships git, curl, jq, sudo and apt but NOT helm/helmfile/kubectl/sops/helm-secrets/helm-diff. The existing component CI proves this: tatara-operator/.github/workflows/ci.yml pulls each tool in-job (actions/setup-go@v5, azure/setup-kubectl@v4, azure/setup-helm@v4, curl ... gitleaks). So the runner image is a thin host, tools come from the workflow. - The infra GitLab CI's own helmfile pattern is mise install first (.parse.py HELMFILE_BEFORE_SCRIPT[0] == "mise install"), reading a pinned .mise.toml. tatara-helmfile will carry an identical .mise.toml (helm 4.2.0, helmfile 1.5.3, kubectl 1.36.1, sops 3.13.1, helm-secrets 4.7.4, helm-diff per the design spec). Bootstrapping with mise reuses that exact pinning and the helm-secrets/helm-diff plugin install hooks already encoded in .mise.toml's [hooks].postinstall. - Net: tool delivery is tatara-helmfile's problem (PLAN 1's diff.yaml/apply.yaml), NOT this repo's. This repo (PLAN 2) only declares the runner pod + its SA/RBAC. The runner image stays the cluster-standard ghcr.io/actions/actions-runner:2.335.1. This task list therefore does NOT build any image and does NOT add tool-install steps; it hands the mise install requirement to PLAN 1 as a cross-plan note (see "Handoff to PLAN 1").
This keeps the runner declaration a verbatim mirror of the existing per-repo runners (KISS, no new image to maintain, no tech-debt).
Orphaned-Repository-CR audit (CRITICAL handoff to PLAN 1)¶
The spec (Sub-system C) requires auditing helmfiles/tatara/values/tatara-operator/raw/ for any Repository CRs that removal would orphan.
Audit result (verified before writing this plan): - find helmfiles/tatara -type f returns exactly one raw manifest: values/tatara-operator/raw/project-tatara.tatara-operator.pre.yaml. - grep -rl "kind: Repository" helmfiles/ over the whole infra repo returns NONE. - grep -rn "tatara.dev" helmfiles/ returns only the single project-tatara...pre.yaml (a kind: Project, apiVersion: tatara.dev/v1alpha1).
Conclusion: ZERO Repository CRs exist in the infra helmfile - but they ARE required by the operator and DO exist live. Corrected picture (verified against the live cluster, kubectl get repositories.tatara.dev -n tatara): - The operator's per-repo fan-out scans + scheduled re-ingest are driven by Repository CRs (projectReposForScan lists them; tatara-operator/internal/agent/pod.go BuildPod builds TATARA_REPOS from the Project's Repository list). Discovery is NOT owner-only. - The live cluster has 6 Repository CRs (tatara-memory, tatara-cli, tatara-operator, tatara-chat, tatara-memory-repo-ingester, tatara-claude-code-wrapper), all phase: Ingested, reingestSchedule: "0 6 * * *", semanticIngest: true. They were applied ad-hoc via kubectl apply -f tatara-operator/deploy-samples/tatara-project.yaml, never under GitOps - which is the ONLY reason infra has none to "orphan." - Removing the infra bucket does NOT delete these live Repository CRs (they are raw kubectl objects, not helm-release resources; helmfile apply does not prune them). Do NOT kubectl delete them.
This is the critical handoff: PLAN 1 carries the Project CR tatara (full body below) AND brings all 6 live Repository CRs + a new tatara-helmfile self-enroll CR under tatara-helmfile GitOps (PLAN 1 Task 6, specs matched to the live cluster). After PLAN 1's first apply, tatara-helmfile is the sole declarative source for the Project + all 7 Repository CRs.
Cross-plan sequencing (HUMAN-GATED merge order)¶
This infra-removal MR MUST be merged ONLY AFTER tatara-helmfile is live and its first apply is green (it adopts the two releases and re-applies the Project + Repository CRs). Removing the bucket stops infra from managing tatara-chat/tatara-operator and from re-applying the Project; helmfile apply will not prune the still-running releases, but adopt-first eliminates any management gap. Merge order: (1) tatara-helmfile repo created + first apply green, then (2) merge this MR. The deploy-runner workstream (A) has no such ordering constraint and can merge anytime (it only adds a runner + RBAC; nothing in tatara-helmfile works until that runner exists, so ideally merge A first).
helmfiles/tatara/values/tatara-operator/raw/project-tatara.tatara-operator.pre.yaml (verbatim, the sole orphan-candidate):
apiVersion: tatara.dev/v1alpha1
kind: Project
metadata:
name: tatara
namespace: tatara
spec:
agent:
image: harbor.szymonrichert.pl/containers/tatara-claude-code-wrapper:a7eab9b
maxTurnsPerTask: 50
model: claude-opus-4-8
permissionMode: bypassPermissions
turnTimeoutSeconds: 1800
maxConcurrentTasks: 3
memory:
neo4jStorage: 10Gi
pgInstances: 1
pgStorage: 10Gi
scm:
approvalLabel: tatara/awaiting-approval
botLogin: szymonrychu-bot
cron:
brainstorm:
enabled: true
maxOpenProposals: 3
schedule: 0 * * * *
sources:
- docs
- memory
- internet
issueScan:
maxPerRepo: 1
schedule: 0 * * * *
mrScan:
maxPerRepo: 1
schedule: 0 * * * *
mergePolicy: afterApproval
owner: szymonrychu
prReactionScope: labeledOrMentioned
priorityLabel: tatara/priority
provider: github
scmSecretRef: tatara-scm
triggerLabel: tatara
PLAN 1 also needs the operator release pin + cluster values from the bucket (so the full release reproduces in tatara-helmfile): - tatara-operator chart oci://harbor.szymonrichert.pl/charts/tatara-operator version 0.0.0-7d45bd9, ns tatara, createNamespace: true, image.tag: "7d45bd9". - tatara-chat chart oci://harbor.szymonrichert.pl/charts/tatara-chat version 0.1.0, ns tatara, createNamespace: true. - bucket values/common.yaml: imagePullSecrets: [{name: regcred}]. - sops default.secrets.yaml: 5 keys (operatorOidcClientSecret, anthropicOauthToken, openaiApiKey, scmToken, scmWebhookSecret), same PGP key D39E46932A270AA3BA490B9DB9FE928D3E8BCED8 (copy verbatim, do NOT re-encrypt).
File Structure¶
Created¶
| Path | Responsibility |
|---|---|
helmfiles/coding/values/arc-runner-tatara-helmfile/common.yaml | Runner pod spec for the deploy runner: mirrors arc-runner-shared.yaml (control-plane nodeSelector, fsGroup 1001, regcred, maxRunners 1, ephemeral Ceph work volume, ghcr.io/actions/actions-runner:2.335.1) but overrides serviceAccountName to the dedicated tatara-helmfile-deployer and sets githubConfigUrl/runnerScaleSetName for the new repo. |
helmfiles/coding/values/arc-runner-tatara-helmfile/raw/tatara-helmfile-deployer.common.pre.yaml | Raw pre-manifest: ServiceAccount tatara-helmfile-deployer (ns arc-runners) + ClusterRoleBinding tatara-helmfile-deployer to the built-in cluster-admin ClusterRole (modelled on cluster_cleanup + the argo-workflow admin binding). Applied by .hook.sh presync. |
Modified¶
| Path | Responsibility |
|---|---|
helmfiles/coding/helmfile.yaml.gotmpl | Add the arc-runner-tatara-helmfile release (needs arc-system/arc-controller, uses values/arc-runner-tatara-helmfile/common.yaml via the *default template layering). |
helmfile.yaml.gotmpl (root) | Remove the - path: helmfiles/tatara/helmfile.yaml.gotmpl include line. |
MEMORY.md | Add a dated entry reversing the 2026-06-05 consolidation + recording the dedicated deploy runner + cluster-admin SA. |
Deleted¶
| Path | Responsibility |
|---|---|
helmfiles/tatara/ (entire directory) | The full tatara bucket: helmfile.yaml.gotmpl, values/common.yaml, values/tatara-chat/default.yaml, values/tatara-operator/{common,default,default.secrets}.yaml, values/tatara-operator/raw/project-tatara.tatara-operator.pre.yaml. All content reproduced in this plan / ported by PLAN 1. |
Test / validation (no code, so validation = concrete commands)¶
| Command | Where |
|---|---|
mise exec -- helmfile -e default build | repo root: confirms the root include list + coding bucket render after both changes |
mise exec -- ./.parse.py (via the CI venv, or LOG_LEVEL=DEBUG) | repo root: regenerates .sub.gitlab-ci.yml, must exit 0 and contain no tatara: jobs and no dangling needs |
mise exec -- helmfile -e default --selector application=arc-runner lint | repo root: lints the runner releases incl. the new one (Harbor OCI not required for the ARC OCI chart pull which is ghcr) |
kubectl apply --dry-run=client -f helmfiles/coding/values/arc-runner-tatara-helmfile/raw/tatara-helmfile-deployer.common.pre.yaml | validates the raw RBAC manifest |
pre-commit run --all-files | yamllint/gitleaks/check-yaml clean |
Pre-flight (Task 0)¶
Files: none (setup only).
- 0.1 Confirm the infra repo is a clean fresh
main:Expected:git -C ~/Documents/infra/helmfile fetch origin git -C ~/Documents/infra/helmfile checkout main git -C ~/Documents/infra/helmfile pull --ff-only origin main git -C ~/Documents/infra/helmfile status -sb## main...origin/mainwith no behind/ahead. (The untrackedhelmfiles/coding/values/tatara-argo-workflows/seen at plan-time is pre-existing and out of scope; leave it.) - 0.2 Create the worktree off
main(superpowers:using-git-worktrees). Native tool if available, else:All subsequent paths in this plan are relative to the worktree rootgit -C ~/Documents/infra/helmfile worktree add -b feat/tatara-deploy-runner ../helmfile-tatara-deploy-runner main~/Documents/infra/helmfile-tatara-deploy-runner. - 0.3 Bootstrap tools in the worktree: Expected: helm/helmfile/kubectl/sops resolve; helm-secrets + helm-diff plugins install via
[hooks].postinstall. - 0.4 Baseline: regenerate CI BEFORE any change and stash the output for diff later: Expected: exit 0;
cd ~/Documents/infra/helmfile-tatara-deploy-runner && mise exec -- ./.parse.py && cp .sub.gitlab-ci.yml /tmp/sub-ci-before.yml/tmp/sub-ci-before.ymlcontainstatara/tatara-operatorandtatara/tatara-chatjobs (the bucket still present). If./.parse.pycannot importgitlab_cicd_python_wrapperoutside the CI venv, note it and rely on thehelmfile build-based validation in Task 4 instead (the generator only consumeshelmfile buildoutput; a clean build is the load-bearing check).
Workstream A: deploy runner¶
Task A1: dedicated deployer SA + cluster-admin ClusterRoleBinding (raw manifest)¶
Files: - Create: helmfiles/coding/values/arc-runner-tatara-helmfile/raw/tatara-helmfile-deployer.common.pre.yaml - Validate: kubectl apply --dry-run=client -f <that file>
Steps: - [ ] A1.1 Create the directory:
- [ ] A1.2 Writehelmfiles/coding/values/arc-runner-tatara-helmfile/raw/tatara-helmfile-deployer.common.pre.yaml with EXACTLY this content (modelled on arc-runner-szymonrychu/raw/tatara-ci-dispatcher.common.pre.yaml for the SA + backbone/raw/cluster_cleanup.common.post.yaml for the ClusterRole/ClusterRoleBinding shape; reuses the built-in cluster-admin ClusterRole exactly as the argo-workflow RoleBinding does): # Deploy identity for the tatara-helmfile GitHub repo. The dedicated ARC runner
# pod (arc-runner-tatara-helmfile) runs as this ServiceAccount and executes
# `helmfile apply` against the cluster using the pod's in-cluster SA token (no
# KUBECONFIG leaves the cluster). The tatara-operator chart installs CRDs,
# ClusterRoles and validating/mutating webhooks, so the deployer needs broad
# cluster scope; we reuse the built-in cluster-admin ClusterRole rather than
# hand-curate verbs (same precedent as cluster_cleanup + the argo-workflow
# admin binding). This SA is DELIBERATELY separate from the shared kaniko
# tatara-ci-dispatcher SA: only tatara-helmfile workflows run on this runner,
# so the cluster-admin blast radius is scoped to that one private bot-write repo.
# The SA lives in arc-runners because .hook.sh applies raw manifests with
# `-n <release-namespace>` (= arc-runners); the ClusterRoleBinding is
# cluster-scoped so the -n flag is ignored for it.
apiVersion: v1
kind: ServiceAccount
metadata:
name: tatara-helmfile-deployer
namespace: arc-runners
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tatara-helmfile-deployer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: tatara-helmfile-deployer
namespace: arc-runners
mise exec -- kubectl apply --dry-run=client -f helmfiles/coding/values/arc-runner-tatara-helmfile/raw/tatara-helmfile-deployer.common.pre.yaml
serviceaccount/tatara-helmfile-deployer created (dry run) and clusterrolebinding.rbac.authorization.k8s.io/tatara-helmfile-deployer created (dry run). (If the cluster context is unavailable, fall back to mise exec -- kubectl apply --dry-run=client --validate=false -o yaml -f ... or yamllint the file; the live apply is the human-gated step.) - [ ] A1.4 Commit: git add helmfiles/coding/values/arc-runner-tatara-helmfile/raw/tatara-helmfile-deployer.common.pre.yaml
git commit -m "feat: tatara-helmfile-deployer SA + cluster-admin binding"
Task A2: deploy runner values file¶
Files: - Create: helmfiles/coding/values/arc-runner-tatara-helmfile/common.yaml
Steps: - [ ] A2.1 Write helmfiles/coding/values/arc-runner-tatara-helmfile/common.yaml with EXACTLY this content. It is a copy of arc-runner-szymonrychu/common.yaml with three deliberate changes: githubConfigUrl/runnerScaleSetName point at tatara-helmfile; serviceAccountName is the dedicated tatara-helmfile-deployer (NOT the kaniko tatara-ci-dispatcher); maxRunners: 1 (a single serialized deployer, reinforcing the workflow concurrency guard and matching the low-cap rationale). githubConfigSecret stays arc-runner-szymonrychu-github (the shared ARC GitHub App credential covers all repos under the account; this is the same secret every per-repo runner uses). No priorityClassName (the per-repo runners omit it; only the standalone arc-runner-szymonrychu sets gitlab-runner-low):
# Deploy runner for the tatara-helmfile GitHub repo. szymonrychu is a personal
# account (no org), so this is a repo-scoped gha-runner-scale-set; the
# tatara-helmfile workflows use `runs-on: arc-runner-tatara-helmfile`. Unlike
# the per-repo CI runners (arc-runner-shared.yaml), this one runs as its OWN
# ServiceAccount tatara-helmfile-deployer (cluster-admin bound, created by the
# raw/ pre-manifest in this release dir) so `helmfile apply` can manage the
# whole tatara platform via the pod's in-cluster SA token. It does NOT use the
# shared kaniko tatara-ci-dispatcher SA.
controllerServiceAccount:
namespace: arc-system
name: arc-controller-gha-rs-controller
githubConfigUrl: https://github.com/szymonrychu/tatara-helmfile
githubConfigSecret: arc-runner-szymonrychu-github
runnerScaleSetName: arc-runner-tatara-helmfile
minRunners: 0
# Single deployer: applies are serialized by the workflow concurrency guard;
# one runner is enough and keeps the cluster-admin blast radius minimal.
maxRunners: 1
template:
spec:
# The _work volume is a Ceph RBD PVC (ext4, root-owned at mount). The runner
# image runs as UID/GID 1001; fsGroup=1001 chowns the volume so the runner
# can create _work/_tool. Without it: EACCES, job dies in ~3s (no step logs).
securityContext:
fsGroup: 1001
# Dedicated deploy identity (see header). Bound to cluster-admin so
# `helmfile apply` can install the operator's CRDs/ClusterRoles/webhooks.
serviceAccountName: tatara-helmfile-deployer
# Cluster rewrites ghcr.io/* to harbor.szymonrichert.pl/proxy-ghcr/* which
# requires auth via regcred. regcred is applied to every release namespace
# by the global .hook.sh presync mechanism (raw/regcred.pre.secrets.yaml).
imagePullSecrets:
- name: regcred
# Builds run ON control-plane, mirroring the other tatara runners: keeps
# runner IO off the media-stack node's root disk (sda 53-90% busy,
# 2026-06-12). CP root-disk/etcd protection: ephemeral caps + arc-runners
# LimitRange + workdir on Ceph, not node disk.
nodeSelector:
node-role.kubernetes.io/control-plane: ""
volumes:
- name: work
ephemeral:
volumeClaimTemplate:
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: rook-ceph
resources:
requests:
storage: 40Gi
containers:
- name: runner
# renovate: image=ghcr.io/actions/actions-runner
image: ghcr.io/actions/actions-runner:2.335.1
command: ["/home/runner/run.sh"]
resources:
requests:
cpu: 200m
memory: 512Mi
ephemeral-storage: 4Gi
limits:
cpu: "2"
memory: 4Gi
ephemeral-storage: 40Gi
volumeMounts:
- name: work
mountPath: /home/runner/_work
mise exec -- yamllint -d '{extends: default, rules: {line-length: {max: 320}, document-start: disable}}' helmfiles/coding/values/arc-runner-tatara-helmfile/common.yaml
.pre-commit-config.yaml; note ^helmfiles/ is excluded in pre-commit, so this is a manual sanity check only). - [ ] A2.3 Commit: git add helmfiles/coding/values/arc-runner-tatara-helmfile/common.yaml
git commit -m "feat: arc-runner-tatara-helmfile deploy runner values"
Task A3: register the deploy runner release in the coding bucket¶
Files: - Modify: helmfiles/coding/helmfile.yaml.gotmpl
Steps: - [ ] A3.1 Read helmfiles/coding/helmfile.yaml.gotmpl (already known). The new release goes immediately AFTER the per-repo {{- range ... }}{{- end }} block (ends at the line {{- end }} following the range that lists tatara-operator ... tatara-chat), BEFORE the - name: joplin-database release. It deliberately does NOT use values/arc-runner-shared.yaml (that file hardcodes the kaniko SA); instead the *default template's values layering auto-loads values/arc-runner-tatara-helmfile/common.yaml via values/{{ .Release.Name }}/common.yaml. So the release body is minimal and carries no inline values: block. - [ ] A3.2 Apply this edit. Insert after the {{- end }} that closes the per-repo runner range (the block starting {{- range $repo := list "tatara-operator" ...) and before - name: joplin-database:
# Dedicated deploy runner for the tatara-helmfile GitHub repo. Unlike the
# per-repo CI runners above, it runs as its OWN cluster-admin-bound SA
# (tatara-helmfile-deployer, created by values/arc-runner-tatara-helmfile/raw/)
# so `helmfile apply` can manage the whole tatara platform from in-cluster.
# Values come from values/arc-runner-tatara-helmfile/common.yaml via the
# *default template layering (NOT arc-runner-shared.yaml, which pins the
# shared kaniko dispatcher SA).
- name: arc-runner-tatara-helmfile
version: 0.14.2
chart: oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set
namespace: arc-runners
needs:
- arc-system/arc-controller
labels:
purpose: ci
application: arc-runner
<<: *default
old_string to match for the Edit is the boundary between the range end and the joplin release: Replace with the new release block inserted between them (keep {{- end }} and - name: joplin-database). - [ ] A3.3 Validate the bucket renders with the new release present: Expected: the release appears with namespace: arc-runners, serviceAccountName: tatara-helmfile-deployer resolved from the values file, and chart: oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set version 0.14.2. A clean overall helmfile build (exit 0) is the load-bearing check. - [ ] A3.4 Commit: git add helmfiles/coding/helmfile.yaml.gotmpl
git commit -m "feat: register arc-runner-tatara-helmfile release"
Workstream B: tatara bucket removal¶
Task B1: drop the tatara include from the root helmfile¶
Files: - Modify: helmfile.yaml.gotmpl (root)
Steps: - [ ] B1.1 Edit the root helmfile.yaml.gotmpl. Remove the single line:
old_string is the last two include lines (anchor on the preceding blog line so the match is unique): Replace with: - [ ] B1.2 Validate the root still builds with the include gone (tatara dir still present at this point, so it must simply be ignored): Expected: BUILD OK, and NO tatara/tatara-operator/tatara/tatara-chat releases in the build output: Expected: 0. - [ ] B1.3 Commit: git add helmfile.yaml.gotmpl
git commit -m "refactor: drop tatara bucket include from root helmfile"
Task B2: delete the tatara bucket directory¶
Files: - Delete: helmfiles/tatara/ (entire tree)
Steps: - [ ] B2.1 Remove the directory (tracked files, so git rm -r):
helmfiles/tatara/helmfile.yaml.gotmpl, values/common.yaml, values/tatara-chat/default.yaml, values/tatara-operator/{common,default,default.secrets}.yaml, and values/tatara-operator/raw/project-tatara.tatara-operator.pre.yaml. - [ ] B2.2 Confirm nothing else in the repo references the bucket: grep -rn "helmfiles/tatara" . --include='*.yaml' --include='*.gotmpl' --include='*.py' --include='*.yml' 2>/dev/null || echo "NO DANGLING REFS"
NO DANGLING REFS. - [ ] B2.3 Commit: Task B3: confirm .parse.py regenerates valid CI with no dangling needs¶
Files: - Validate: .parse.py output (generated .sub.gitlab-ci.yml)
Steps: - [ ] B3.1 Re-run the generator AFTER both removals:
Expected: exit 0. (If the local env lacksgitlab_cicd_python_wrapper/pydantic_settings, run inside the CI builder image or a venv: python3 -m venv /tmp/pv && /tmp/pv/bin/pip install pydantic pydantic-settings pyyaml gitlab_cicd_python_wrapper && /tmp/pv/bin/python ./.parse.py. If the wrapper package is unavailable locally, the authoritative check is B3.3 below, since .parse.py derives every job purely from helmfile build output and the per-bucket value-path globs.) - [ ] B3.2 Confirm NO tatara jobs and NO dangling needs survive: Expected: NO TATARA JOBS. The generator names jobs {namespace}:{name} and resolves needs only within each bucket's own releases; the tatara bucket's two releases (tatara:tatara-operator, tatara:tatara-chat) had no cross-bucket needs and nothing in other buckets needs them (verified: no other release references tatara/...), so removal leaves no dangling reference. - [ ] B3.3 Diff before/after to confirm the ONLY delta is the two removed tatara jobs + the dropped coding-N/tatara-N stage names (the new arc-runner-tatara-helmfile release adds one arc-runners:arc-runner-tatara-helmfile job in the coding-* stages): Expected delta: REMOVED tatara:tatara-operator + tatara:tatara-chat jobs and any tatara-1/tatara-2 stage entries; ADDED arc-runners:arc-runner-tatara-helmfile. No other job's needs/stage changes. (If /tmp/sub-ci-before.yml was not capturable per Task 0.4, assert directly: grep -c "tatara:" /tmp/sub-ci-after.yml returns 0 and grep -c "arc-runner-tatara-helmfile" /tmp/sub-ci-after.yml returns >= 1.) - [ ] B3.4 Clean up the generated artifact (it is a CI build artifact, not committed): Expected: .sub.gitlab-ci.yml is untracked/ignored. If it is tracked here, leave it as-is (do not commit the regenerated copy); the CI process:template job regenerates it. Confirm it is in .gitignore or otherwise not staged; do NOT git add it. Task C: reverse-consolidation MEMORY.md entry¶
Files: - Modify: MEMORY.md
Steps: - [ ] C1.1 Read MEMORY.md (already known). Add a new dated entry at the TOP of the bullet list (most-recent-first, matching the existing 2026-06-05 -> 2026-06-03 ordering), directly under the One line per decision. line:
- 2026-06-13: **Reversed the 2026-06-05 tatara consolidation.** The `helmfiles/tatara/` bucket is removed from this repo and its include dropped from the root helmfile; the tatara platform's releases (`tatara-operator` 0.0.0-7d45bd9, `tatara-chat` 0.1.0) + the `Project` CR `tatara` + sops secrets now live in the standalone private `github.com/szymonrychu/tatara-helmfile` repo, which deploys itself via GitHub Actions on an in-cluster ARC runner. Rationale: scope the autonomous tatara deploy bot's cluster-admin access to a single dedicated repo instead of this whole 60+ release homelab infra repo. Audit confirmed the bucket carried NO `Repository` CRs (operator discovers repos at runtime via the Project scmSecretRef), so nothing was orphaned. NEW here: `arc-runner-tatara-helmfile` RunnerScaleSet (coding bucket) runs as a dedicated cluster-admin-bound SA `tatara-helmfile-deployer` (NOT the shared kaniko `tatara-ci-dispatcher`); `helmfile apply` uses the pod's in-cluster SA token (no KUBECONFIG). The sops PGP key (D39E...CED8) is shared with tatara-helmfile; key rotation noted out of scope.
Task 4: full verification (superpowers:verification-before-completion)¶
Files: none (validation only). Run every command; capture output as evidence before any "done" claim.
- 4.1 Root + all remaining buckets build clean (tatara gone, deploy runner present): Expected:
mise exec -- helmfile -e default build > /tmp/build-final.txt 2>&1 && echo "BUILD OK" grep -c "namespace: tatara$" /tmp/build-final.txt # expect 0 grep -c "arc-runner-tatara-helmfile" /tmp/build-final.txt # expect >= 1BUILD OK; first grep0; second grep>= 1. - 4.2 Lint the runner releases (remaining buckets unaffected): Expected: lint passes for
arc-runner-szymonrychu, the six per-repo runners, andarc-runner-tatara-helmfile. (OCI chart pull is from ghcr via the proxy; if offline,helmfile buildin 4.1 is the load-bearing render check.) - 4.3
.parse.pyclean + no tatara, no dangling needs (re-assert Task B3):Expected: generator exit 0; first grepmise exec -- ./.parse.py && grep -c "tatara:" .sub.gitlab-ci.yml # expect 0 grep -c "arc-runner-tatara-helmfile" .sub.gitlab-ci.yml # expect >= 10; second>= 1. Then ensure.sub.gitlab-ci.ymlis not staged (Task B3.4). - 4.4 RBAC manifest still dry-run valid: Expected: SA + ClusterRoleBinding both
mise exec -- kubectl apply --dry-run=client -f helmfiles/coding/values/arc-runner-tatara-helmfile/raw/tatara-helmfile-deployer.common.pre.yaml(dry run). - 4.5 pre-commit clean across the worktree: Expected: all hooks pass (trailing-whitespace, end-of-file-fixer, check-yaml --allow-multiple-documents, gitleaks, yamllint [helmfiles excluded], conventional-pre-commit on commit-msg). Fix any reported issue and amend the relevant commit.
- 4.6 Review the full diff against
main: Expected stat: 2 files added (arc-runner-tatara-helmfile/common.yaml,.../raw/tatara-helmfile-deployer.common.pre.yaml),helmfiles/coding/helmfile.yaml.gotmpl+ roothelmfile.yaml.gotmpl+MEMORY.mdmodified, and 6 files underhelmfiles/tatara/deleted. - 4.7 superpowers:requesting-code-review on the worktree diff. Fix any critical/high findings, re-run 4.5, then proceed.
Task 5: push branch + open GitLab MR (HUMAN-GATED merge)¶
Files: none.
- 5.1 Push the branch:
- 5.2 Open the MR via
glab(or print the MR-create URL ifglabis unavailable). Title and description below. Do NOT enable auto-merge. Do NOT merge. The user merges this MR.Ifglab mr create \ --source-branch feat/tatara-deploy-runner \ --target-branch main \ --title "feat: tatara-helmfile deploy runner + remove tatara bucket" \ --description "$(cat <<'EOF' Reverses the 2026-06-05 tatara consolidation. Two workstreams: A. Adds `arc-runner-tatara-helmfile` RunnerScaleSet (coding bucket) running as a dedicated cluster-admin-bound SA `tatara-helmfile-deployer` (NOT the shared kaniko `tatara-ci-dispatcher`) for autonomous `helmfile apply` from the new `github.com/szymonrychu/tatara-helmfile` repo via in-cluster SA token. B. Removes the `helmfiles/tatara/` bucket and its root include. Audit confirmed the bucket carried NO `Repository` CRs (only the `Project` CR, ported to tatara-helmfile by PLAN 1); nothing orphaned. Validation done locally: `helmfile -e default build` clean, `.parse.py` regenerates with no tatara jobs and no dangling needs, `kubectl --dry-run` on the RBAC manifest, `pre-commit run --all-files` green. HUMAN-GATED: reviewer merges. After merge the CI applies the new runner; the first live `kubectl apply` of the deployer SA/ClusterRoleBinding happens via the `.hook.sh` presync on the next `arc-runner-tatara-helmfile` sync. The tatara releases are NOT in this repo anymore - they deploy from tatara-helmfile. EOF )"glabis not configured, output the branch name + a one-line instruction for the user to open the MRmain <- feat/tatara-deploy-runnerand DO NOT proceed further. - 5.3 STOP. Report the MR URL/branch. Merge is the user's action (Decision authority: destructive/prod, human-gated per the design spec's "Human-gated" section item 2).
Handoff to PLAN 1 (cross-plan, CRITICAL)¶
- No
RepositoryCRs to carry from infra. The tatara bucket had ZERO. PLAN 1 authors the new self-enrollRepositoryCR fortatara-helmfilefrom scratch (design spec Sub-system C). Audit command for PLAN 1 to re-confirm post-extract:grep -rl "kind: Repository" ~/Documents/infra/helmfilereturns nothing. - Port the
ProjectCRtataraverbatim (full body in the "Orphaned-Repository-CR audit" section above) intotatara-helmfile'svalues/tatara-operator/raw/project-tatara.tatara-operator.pre.yaml. - Port release pins + values verbatim:
tatara-operator0.0.0-7d45bd9 (image.tag: "7d45bd9"),tatara-chat0.1.0, both nstataracreateNamespace: true; bucketvalues/common.yamlimagePullSecrets: [{name: regcred}]; sopsdefault.secrets.yaml5 keys, same PGP key D39E...CED8 (copy file verbatim, never re-encrypt). - Runner tool delivery is PLAN 1's job. This runner uses the stock
ghcr.io/actions/actions-runner:2.335.1image with NO baked helm/helmfile/kubectl/sops. PLAN 1'sdiff.yaml/apply.yamlMUST install tools first (recommendjdx/mise-actionreadingtatara-helmfile's.mise.toml, matching the inframise install-first pattern).runs-on: arc-runner-tatara-helmfile. - In-cluster SA auth. The runner pod IS the
tatara-helmfile-deployerSA (cluster-admin). PLAN 1's workflows must NOT setKUBECONFIG/KUBE_CONFIG-helmfile applyuses the pod's mounted SA token directly.
Notes / constraints honored¶
- KISS: deploy runner is a verbatim mirror of the existing per-repo runner (same image, same volume, same nodeSelector/fsGroup), differing only in SA + repo URL + maxRunners. No new image, no initContainer, no abstraction.
- No tech-debt: cluster-admin scope is the deliberate, documented choice (operator installs CRDs/ClusterRoles/webhooks); rationale recorded in the manifest comment and
MEMORY.md. sops key rotation flagged out of scope inMEMORY.md. - Charts cluster-agnostic: unaffected (this is the infra side that owns cluster config).
- No plain ENVs/lists in any values.yaml: the runner values file is structured ARC chart config (pod spec), not app ENVs; no ConfigMap mapping needed.
- Branch flow: worktree off fresh
main, develop in worktree, MR back tomain, human merges; build/deploy only frommain(the CI does it post-merge). Worktree cleanup after merge is the user's follow-up. - Independently executable: this plan operates solely in
~/Documents/infra/helmfile(its own worktree); the only cross-plan coupling is the one-directional handoff above (PLAN 1 consumes the ported CRs/values; nothing in PLAN 1 blocks PLAN 2 from completing/merging). ```