Enroll tatara (dogfood) Implementation Plan¶
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax.
Goal: Fix the ingester + wrapper image gaps and enroll the tatara platform as the first real operator Project.
Architecture: Three image builds (ingester runtime base swap; tatara-cli container; wrapper bundling cli v0.4.0) then enrollment objects (SCM secret + Project + Repository CRs + GitHub webhooks). Builds publish to Harbor; enrollment applies to the live tatara namespace.
Tech Stack: docker buildx, Harbor OCI, helmfile, kubectl, cnpg, GitHub.
Spec: docs/superpowers/specs/2026-06-07-enroll-tatara-dogfood-design.md. All builds are linux/amd64 (homelab nodes). Use mise for helm/helmfile; local docker daemon for builds (already logged in to harbor).
Task E1: Ingester runtime image (git + go toolchain)¶
Files: - Modify: tatara-memory-repo-ingester/Dockerfile (final stage only)
-
Step 1: Swap the final stage base. In
Dockerfile, change the final runtime stage fromFROM gcr.io/distroless/cc-debian12:nonroottoFROM golang:1.26-bookworm. Keep the builder stage and theCOPY --from=builderof thetatara-ingestbinary and the existingENTRYPOINT/USERas-is, EXCEPT: golang:bookworm has nononrootuser, so drop anyUSER nonrootline in the final stage (the ingest Job pod securityContext sets runAsUser). Ensuregitandgoare on PATH (they are in golang:bookworm). SetENV GOTOOLCHAIN=autoin the final stage so it can build repos pinned to a newer Go. -
Step 2: Build + smoke-test locally.
Expected: prints a git version, a go version, andcd ~/Documents/tatara/tatara-memory-repo-ingester COMMIT=$(git rev-parse --short HEAD); DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) docker buildx build --platform=linux/amd64 --build-arg VERSION=0.2.0 \ --build-arg COMMIT=$COMMIT --build-arg DATE=$DATE \ -t harbor.szymonrichert.pl/containers/tatara-memory-repo-ingester:0.2.0 --load . docker run --rm --entrypoint sh harbor.szymonrichert.pl/containers/tatara-memory-repo-ingester:0.2.0 \ -c 'git --version && go version && tatara-ingest --help >/dev/null 2>&1; echo ingest-exit=$?'ingest-exit=(0 or a flag-usage code, not 127/command-not-found). -
Step 3: Push.
Expected:docker buildx build --platform=linux/amd64 --build-arg VERSION=0.2.0 \ --build-arg COMMIT=$COMMIT --build-arg DATE=$DATE \ -t harbor.szymonrichert.pl/containers/tatara-memory-repo-ingester:0.2.0 --push .naming to ...:0.2.0 done. -
Step 4: Commit the Dockerfile change (local; no push of the repo unless asked).
-
Step 5: Bump the operator's ingester image pin + re-apply.
Expected: ConfigMapcd ~/Documents/infra/helmfile sed -i '' 's#tatara-memory-repo-ingester:0.1.0#tatara-memory-repo-ingester:0.2.0#' helmfiles/tatara/values/tatara-operator/default.yaml git add helmfiles/tatara/values/tatara-operator/default.yaml && git commit -m "chore(tatara): ingesterImage 0.2.0 (git+go runtime)" mise exec -- helmfile -e default -l application=tatara-operator apply kubectl -n tatara get cm tatara-operator -o jsonpath='{.data.INGESTER_IMAGE}{"\n"}'INGESTER_IMAGEshows...:0.2.0.
Task E2: tatara-cli + wrapper images¶
Files: none (builds only).
-
Step 1: Build + push tatara-cli:0.4.0.
Expected:cd ~/Documents/tatara/tatara-cli git checkout v0.4.0 2>/dev/null || git checkout main COMMIT=$(git rev-parse --short HEAD); DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) docker buildx build --platform=linux/amd64 --build-arg VERSION=0.4.0 \ --build-arg COMMIT=$COMMIT --build-arg DATE=$DATE \ -t harbor.szymonrichert.pl/containers/tatara-cli:0.4.0 --push . git checkout main 2>/dev/null || truenaming to ...tatara-cli:0.4.0 done. If the build fails onCOPY go.modmissing go.sum, addgo.sumto that COPY line in the cli Dockerfile, commit it, and retry (note the fix). -
Step 2: Verify the cli image carries the operator tools.
Expected: runs without command-not-found (binary atdocker run --rm --entrypoint sh harbor.szymonrichert.pl/containers/tatara-cli:0.4.0 \ -c 'tatara mcp --help 2>&1 | head -1; tatara version 2>/dev/null || true'/usr/local/bin/tatara). -
Step 3: Build + push the wrapper bundling cli 0.4.0.
Expected:cd ~/Documents/tatara/tatara-claude-code-wrapper COMMIT=$(git rev-parse --short HEAD); DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) docker buildx build --platform=linux/amd64 \ --build-arg VERSION=0.1.0 --build-arg COMMIT=$COMMIT --build-arg DATE=$DATE \ --build-arg TATARA_CLI_VERSION=0.4.0 \ -t harbor.szymonrichert.pl/containers/tatara-claude-code-wrapper:0.1.0 --push .naming to ...tatara-claude-code-wrapper:0.1.0 done. If the go-build stage fails because go.mod requires a newer Go thanGO_VERSION=1.25, add--build-arg GO_VERSION=1.26and retry (note it). -
Step 4: Verify the wrapper bundles tatara + claude + wrapper.
Expected: all four paths printed.
Task E3: Enrollment (needs the GitHub PAT)¶
Files: - Create: ~/Documents/tatara/tatara-operator/deploy-samples/tatara-project.yaml (the Project + Repository manifests, for reproducibility; gitignored-safe, no secrets)
-
Step 1: STOP - request the GitHub PAT from the human. The
tokenvalue cannot be invented. Ask the human to provide a GitHub PAT (scopes:repo,admin:repo_hook) forszymonrychu/*, OR to run thekubectl create secretthemselves. Do not proceed to Step 2 until the token is available (as an env var the agent never prints, or human-created). -
Step 2: Create the SCM secret (agent-safe, value never printed).
Expected: secret has keys# token provided out-of-band in $GH_PAT (exported by the human / op_env); webhookSecret generated WHS=$(openssl rand -hex 24) kubectl -n tatara create secret generic tatara-scm \ --from-literal=token="$GH_PAT" --from-literal=webhookSecret="$WHS" \ --dry-run=client -o yaml | kubectl apply -f - kubectl -n tatara get secret tatara-scm -o go-template='{{range $k,$v := .data}}{{$k}} {{end}}{{"\n"}}' echo "$WHS" > /tmp/tatara-webhook-secret # for the webhook registration step; not committedtoken webhookSecret. (Never echo the values.) -
Step 3: Apply the Project + Repository CRs. Write
deploy-samples/tatara-project.yaml:Expected: project + 6 repositories created.apiVersion: tatara.dev/v1alpha1 kind: Project metadata: {name: tatara, namespace: tatara} spec: scmSecretRef: tatara-scm triggerLabel: tatara maxConcurrentTasks: 2 agent: image: harbor.szymonrichert.pl/containers/tatara-claude-code-wrapper:0.1.0 model: claude-opus-4-8 permissionMode: bypassPermissions memory: {pgInstances: 1, pgStorage: 10Gi, neo4jStorage: 10Gi} --- apiVersion: tatara.dev/v1alpha1 kind: Repository metadata: {name: tatara-memory, namespace: tatara} spec: {projectRef: tatara, url: "https://github.com/szymonrychu/tatara-memory", defaultBranch: main} --- apiVersion: tatara.dev/v1alpha1 kind: Repository metadata: {name: tatara-cli, namespace: tatara} spec: {projectRef: tatara, url: "https://github.com/szymonrychu/tatara-cli", defaultBranch: main} --- apiVersion: tatara.dev/v1alpha1 kind: Repository metadata: {name: tatara-operator, namespace: tatara} spec: {projectRef: tatara, url: "https://github.com/szymonrychu/tatara-operator", defaultBranch: main} --- apiVersion: tatara.dev/v1alpha1 kind: Repository metadata: {name: tatara-chat, namespace: tatara} spec: {projectRef: tatara, url: "https://github.com/szymonrychu/tatara-chat", defaultBranch: main} --- apiVersion: tatara.dev/v1alpha1 kind: Repository metadata: {name: tatara-memory-repo-ingester, namespace: tatara} spec: {projectRef: tatara, url: "https://github.com/szymonrychu/tatara-memory-repo-ingester", defaultBranch: main} --- apiVersion: tatara.dev/v1alpha1 kind: Repository metadata: {name: tatara-claude-code-wrapper, namespace: tatara} spec: {projectRef: tatara, url: "https://github.com/szymonrychu/tatara-claude-code-wrapper", defaultBranch: main} -
Step 4: Wait for the Project memory stack to be Ready (the operator provisions cnpg+neo4j+lightrag+memory; Repositories gate on it).
Expected:for i in $(seq 1 15); do sleep 20; ph=$(kubectl -n tatara get project tatara -o jsonpath='{.status.memory.phase}'); echo "phase=$ph"; [ "$ph" = Ready ] && break; donephase=Ready. -
Step 5: Verify ingest reaches the graph. Each Repository should launch an ingest Job and reach
phase=Ingested.Expected: repositories progress tokubectl -n tatara get repository -o custom-columns=NAME:.metadata.name,PHASE:.status.phase,SHA:.status.lastIngestedCommit kubectl -n tatara get jobs -l app.kubernetes.io/component=ingest # code-graph non-empty in the project's postgres: POD=$(kubectl -n tatara get pods -l cnpg.io/cluster=mem-tatara-pg -o name | head -1) kubectl -n tatara exec "$POD" -c postgres -- psql -U tatara_memory -d tatara_memory -tAc \ 'select count(*) from code_entities;' 2>/dev/nullIngestedwith a SHA;code_entitiescount > 0. (First real ingest may surface edge cases - debug per systematic-debugging; the ingest Job logs are the source of truth.) -
Step 6: Register GitHub webhooks per repo (push + issues -> the operator).
Expected: a hook per repo (or "already exists"). A push toWHS=$(cat /tmp/tatara-webhook-secret); URL="https://tatara.szymonrichert.pl/operator/webhooks/tatara" for r in tatara-memory tatara-cli tatara-operator tatara-chat tatara-memory-repo-ingester tatara-claude-code-wrapper; do gh api -X POST "repos/szymonrychu/$r/hooks" -f name=web -F active=true \ -f events[]=push -f events[]=issues \ -f config[url]="$URL" -f config[content_type]=json -f config[secret]="$WHS" -f config[insecure_ssl]=0 \ && echo "hook: $r" || echo "hook FAILED (may already exist): $r" done rm -f /tmp/tatara-webhook-secretmainthen triggers an incremental re-ingest; atatara-labelled issue creates a Task. -
Step 7: Commit the sample manifest (no secrets in it).
Notes¶
- E1 and E2 are independent and run in parallel (different repos). E3 is sequential after both and needs the human's PAT.
- Image pushes are outward (Harbor) but within the authorized deploy scope.
- The CRD
spec.memory/spec.agentalready exist on the live cluster (applied in the per-project-memory deploy).