File created successfully at: /Users/steve.briggs/projects/churn-renewal-forecast/docs/superpowers/plans/2026-05-18-kustomize-deployment.md (file state is current in your context — no need to Read it back)

# Kustomize Deployment 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:** Create a complete Kustomize deployment under `deploy/kubernetes/` with a base layer and a single cluster overlay for `eks-shared-infra-use1-2`.

**Architecture:** Base layer is split into four sub-directories (bootstrap, init, netpol, services) each with their own `kustomization.yaml`. The cluster overlay at `deploy/kubernetes/deploy/eks-shared-infra-use1-2/` references the base and applies cluster-specific patches (ingress visibility, service account). All resources share a single container image with command overrides per workload.

**Tech Stack:** Kustomize v4+, Kubernetes manifests (apps/v1, batch/v1, networking.k8s.io/v1), external-secrets.io v1beta1 (ExternalSecret), cilium.io/v2 (CiliumNetworkPolicy)

---

## File Map

**Create — base bootstrap:**
- `deploy/kubernetes/base/bootstrap/namespace.yaml`
- `deploy/kubernetes/base/bootstrap/serviceaccount.yaml`
- `deploy/kubernetes/base/bootstrap/secret.yaml`
- `deploy/kubernetes/base/bootstrap/app-config.env`
- `deploy/kubernetes/base/bootstrap/kustomization.yaml`

**Create — base init:**
- `deploy/kubernetes/base/init/job.yaml`
- `deploy/kubernetes/base/init/kustomization.yaml`

**Create — base netpol:**
- `deploy/kubernetes/base/netpol/allow-dns.yaml`
- `deploy/kubernetes/base/netpol/allow-rds.yaml`
- `deploy/kubernetes/base/netpol/allow-external.yaml`
- `deploy/kubernetes/base/netpol/kustomization.yaml`

**Create — base services:**
- `deploy/kubernetes/base/services/web-deployment.yaml`
- `deploy/kubernetes/base/services/worker-deployment.yaml`
- `deploy/kubernetes/base/services/service.yaml`
- `deploy/kubernetes/base/services/ingress.yaml`
- `deploy/kubernetes/base/services/kustomization.yaml`

**Create — base root:**
- `deploy/kubernetes/base/kustomization.yaml`

**Create — overlay:**
- `deploy/kubernetes/deploy/eks-shared-infra-use1-2/kustomization.yaml`
- `deploy/kubernetes/deploy/eks-shared-infra-use1-2/.app.yaml`
- `deploy/kubernetes/deploy/eks-shared-infra-use1-2/ingress-patch.yaml`
- `deploy/kubernetes/deploy/eks-shared-infra-use1-2/serviceaccount.yaml`

---

## Task 1: Bootstrap Resources

Creates the namespace, service account, ExternalSecret, and ConfigMap source.

**Files:**
- Create: `deploy/kubernetes/base/bootstrap/namespace.yaml`
- Create: `deploy/kubernetes/base/bootstrap/serviceaccount.yaml`
- Create: `deploy/kubernetes/base/bootstrap/secret.yaml`
- Create: `deploy/kubernetes/base/bootstrap/app-config.env`
- Create: `deploy/kubernetes/base/bootstrap/kustomization.yaml`

- [ ] **Step 1: Create directory structure**

```bash
mkdir -p deploy/kubernetes/base/bootstrap
```

- [ ] **Step 2: Create namespace.yaml**

```yaml
# deploy/kubernetes/base/bootstrap/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
```

- [ ] **Step 3: Create serviceaccount.yaml**

```yaml
# deploy/kubernetes/base/bootstrap/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: churn-renewal-forecast
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
```

- [ ] **Step 4: Create secret.yaml (ExternalSecret)**

```yaml
# deploy/kubernetes/base/bootstrap/secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: churn-renewal-forecast
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault
    kind: ClusterSecretStore
  target:
    name: churn-renewal-forecast
    creationPolicy: Owner
  data:
    - secretKey: DATABASE_URL
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: DATABASE_URL
    - secretKey: CLERK_SECRET_KEY
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: CLERK_SECRET_KEY
    - secretKey: FRONT_API_TOKEN
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: FRONT_API_TOKEN
    - secretKey: FRONT_API_TOKEN_2
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: FRONT_API_TOKEN_2
    - secretKey: PRODUCTBOARD_API_KEY
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: PRODUCTBOARD_API_KEY
    - secretKey: SLACK_BOT_TOKEN
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: SLACK_BOT_TOKEN
```

- [ ] **Step 5: Create app-config.env**

```env
# deploy/kubernetes/base/bootstrap/app-config.env
APP_URL=https://churn-forecast.infrastructure.immuta.io
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=PLACEHOLDER
NEXT_PUBLIC_PRODUCTBOARD_WORKSPACE=PLACEHOLDER
SLACK_TEST_CHANNEL=
PG_POOL_MAX=10
PG_POOL_IDLE_TIMEOUT_MS=30000
PG_POOL_CONNECTION_TIMEOUT_MS=2000
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
```

- [ ] **Step 6: Create bootstrap/kustomization.yaml**

`configMapGenerator` produces the ConfigMap from `app-config.env`. `disableNameSuffixHash: true` keeps the name stable so `envFrom` references don't need to change on every deploy.

```yaml
# deploy/kubernetes/base/bootstrap/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - namespace.yaml
  - serviceaccount.yaml
  - secret.yaml

configMapGenerator:
  - name: churn-renewal-forecast-config
    envs:
      - app-config.env

generatorOptions:
  disableNameSuffixHash: true
```

- [ ] **Step 7: Validate bootstrap kustomization builds**

```bash
kustomize build deploy/kubernetes/base/bootstrap
```

Expected: YAML output containing Namespace, ServiceAccount, ExternalSecret, and ConfigMap. No errors.

- [ ] **Step 8: Commit**

```bash
git add deploy/kubernetes/base/bootstrap/
git commit -m "feat: add k8s bootstrap resources (namespace, sa, secret, configmap)"
```

---

## Task 2: Init Job

Creates the one-shot Prisma migration Job that runs before the app starts.

**Files:**
- Create: `deploy/kubernetes/base/init/job.yaml`
- Create: `deploy/kubernetes/base/init/kustomization.yaml`

- [ ] **Step 1: Create directory**

```bash
mkdir -p deploy/kubernetes/base/init
```

- [ ] **Step 2: Create job.yaml**

`ttlSecondsAfterFinished: 300` auto-deletes the Job pod 5 minutes after it completes. `backoffLimit: 3` retries up to 3 times on failure. `prisma migrate deploy` is idempotent — safe to re-run.

```yaml
# deploy/kubernetes/base/init/job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: churn-renewal-forecast-migrate
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
    app.kubernetes.io/component: migrate
spec:
  ttlSecondsAfterFinished: 300
  backoffLimit: 3
  template:
    metadata:
      labels:
        app.kubernetes.io/name: churn-renewal-forecast
        app.kubernetes.io/component: migrate
    spec:
      serviceAccountName: churn-renewal-forecast
      restartPolicy: OnFailure
      containers:
        - name: migrate
          image: registry.ic-ops.net/churn-renewal-forecast:latest
          command: ["bunx", "prisma", "migrate", "deploy"]
          envFrom:
            - configMapRef:
                name: churn-renewal-forecast-config
            - secretRef:
                name: churn-renewal-forecast
```

- [ ] **Step 3: Create init/kustomization.yaml**

```yaml
# deploy/kubernetes/base/init/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - job.yaml
```

- [ ] **Step 4: Validate**

```bash
kustomize build deploy/kubernetes/base/init
```

Expected: YAML output containing a single Job. No errors.

- [ ] **Step 5: Commit**

```bash
git add deploy/kubernetes/base/init/
git commit -m "feat: add k8s init job for prisma migrations"
```

---

## Task 3: Network Policies

Creates three CiliumNetworkPolicy resources covering DNS, RDS, and external HTTPS egress.

**Files:**
- Create: `deploy/kubernetes/base/netpol/allow-dns.yaml`
- Create: `deploy/kubernetes/base/netpol/allow-rds.yaml`
- Create: `deploy/kubernetes/base/netpol/allow-external.yaml`
- Create: `deploy/kubernetes/base/netpol/kustomization.yaml`

- [ ] **Step 1: Create directory**

```bash
mkdir -p deploy/kubernetes/base/netpol
```

- [ ] **Step 2: Create allow-dns.yaml**

Allows pods with `app.kubernetes.io/name: churn-renewal-forecast` to reach `kube-dns` on UDP 53. The `rules.dns` section tells Cilium to track DNS responses so FQDN policies in `allow-external.yaml` can resolve correctly.

```yaml
# deploy/kubernetes/base/netpol/allow-dns.yaml
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: churn-renewal-forecast-allow-dns
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
spec:
  endpointSelector:
    matchLabels:
      app.kubernetes.io/name: churn-renewal-forecast
  egress:
    - toEndpoints:
        - matchLabels:
            k8s:io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
          rules:
            dns:
              - matchPattern: "*"
```

- [ ] **Step 3: Create allow-rds.yaml**

Allows outbound TCP 5432 to any IP. Scoped to CIDR 0.0.0.0/0 because RDS runs outside the cluster — you cannot use an endpoint selector for external IPs.

```yaml
# deploy/kubernetes/base/netpol/allow-rds.yaml
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: churn-renewal-forecast-allow-rds
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
spec:
  endpointSelector:
    matchLabels:
      app.kubernetes.io/name: churn-renewal-forecast
  egress:
    - toCIDR:
        - 0.0.0.0/0
      toPorts:
        - ports:
            - port: "5432"
              protocol: TCP
```

- [ ] **Step 4: Create allow-external.yaml**

`toFQDNs` with `matchName` locks to the exact hostname. `matchPattern: "*.clerk.com"` covers Clerk's subdomains (e.g. `accounts.clerk.com`). `*.slack.com` covers Slack API endpoints. Requires `allow-dns.yaml` to be active so Cilium can resolve these FQDNs.

```yaml
# deploy/kubernetes/base/netpol/allow-external.yaml
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: churn-renewal-forecast-allow-external
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
spec:
  endpointSelector:
    matchLabels:
      app.kubernetes.io/name: churn-renewal-forecast
  egress:
    - toFQDNs:
        - matchName: "clerk.com"
        - matchPattern: "*.clerk.com"
        - matchName: "api.frontapp.com"
        - matchName: "api.productboard.com"
        - matchName: "slack.com"
        - matchPattern: "*.slack.com"
      toPorts:
        - ports:
            - port: "443"
              protocol: TCP
```

- [ ] **Step 5: Create netpol/kustomization.yaml**

```yaml
# deploy/kubernetes/base/netpol/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - allow-dns.yaml
  - allow-rds.yaml
  - allow-external.yaml
```

- [ ] **Step 6: Validate**

```bash
kustomize build deploy/kubernetes/base/netpol
```

Expected: YAML output with three CiliumNetworkPolicy resources. No errors.

- [ ] **Step 7: Commit**

```bash
git add deploy/kubernetes/base/netpol/
git commit -m "feat: add cilium network policies (dns, rds, external egress)"
```

---

## Task 4: Services (Deployments, Service, Ingress)

Creates the web Deployment (Next.js), worker Deployment (pg-boss), ClusterIP Service, and Nginx Ingress.

**Files:**
- Create: `deploy/kubernetes/base/services/web-deployment.yaml`
- Create: `deploy/kubernetes/base/services/worker-deployment.yaml`
- Create: `deploy/kubernetes/base/services/service.yaml`
- Create: `deploy/kubernetes/base/services/ingress.yaml`
- Create: `deploy/kubernetes/base/services/kustomization.yaml`

- [ ] **Step 1: Create directory**

```bash
mkdir -p deploy/kubernetes/base/services
```

- [ ] **Step 2: Create web-deployment.yaml**

`command: ["bun", "run", "start"]` maps to `next start` from the project's `package.json`. Liveness checks `/api/health/live` to detect a hung process; readiness checks `/api/health/ready` to gate traffic until the app is warm.

```yaml
# deploy/kubernetes/base/services/web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: churn-renewal-forecast-web
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
    app.kubernetes.io/component: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: churn-renewal-forecast
      app.kubernetes.io/component: web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: churn-renewal-forecast
        app.kubernetes.io/component: web
    spec:
      serviceAccountName: churn-renewal-forecast
      containers:
        - name: web
          image: registry.ic-ops.net/churn-renewal-forecast:latest
          command: ["bun", "run", "start"]
          ports:
            - containerPort: 3000
              name: http
          envFrom:
            - configMapRef:
                name: churn-renewal-forecast-config
            - secretRef:
                name: churn-renewal-forecast
          resources:
            requests:
              memory: 256Mi
              cpu: 100m
            limits:
              memory: 512Mi
              cpu: 500m
          livenessProbe:
            httpGet:
              path: /api/health/live
              port: 3000
            initialDelaySeconds: 15
            periodSeconds: 20
          readinessProbe:
            httpGet:
              path: /api/health/ready
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 10
```

- [ ] **Step 3: Create worker-deployment.yaml**

`command: ["bunx", "tsx", "src/worker/index.ts"]` maps to `bun run worker` from `package.json`. No HTTP port or probes — the worker is a queue consumer that runs until killed; Kubernetes monitors the process exit code.

```yaml
# deploy/kubernetes/base/services/worker-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: churn-renewal-forecast-worker
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
    app.kubernetes.io/component: worker
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: churn-renewal-forecast
      app.kubernetes.io/component: worker
  template:
    metadata:
      labels:
        app.kubernetes.io/name: churn-renewal-forecast
        app.kubernetes.io/component: worker
    spec:
      serviceAccountName: churn-renewal-forecast
      containers:
        - name: worker
          image: registry.ic-ops.net/churn-renewal-forecast:latest
          command: ["bunx", "tsx", "src/worker/index.ts"]
          envFrom:
            - configMapRef:
                name: churn-renewal-forecast-config
            - secretRef:
                name: churn-renewal-forecast
          resources:
            requests:
              memory: 256Mi
              cpu: 100m
            limits:
              memory: 512Mi
              cpu: 500m
```

- [ ] **Step 4: Create service.yaml**

ClusterIP routes traffic to web pods only (selector scoped to `component: web`). Port 80 externally → 3000 on the container.

```yaml
# deploy/kubernetes/base/services/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: churn-renewal-forecast
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
    app.kubernetes.io/component: web
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: churn-renewal-forecast
    app.kubernetes.io/component: web
  ports:
    - port: 80
      targetPort: 3000
      name: http
```

- [ ] **Step 5: Create ingress.yaml**

TLS terminates at the ingress; the `churn-renewal-forecast-tls` secret is managed externally (cert-manager or equivalent). The `ingressClassName` field replaces the deprecated `kubernetes.io/ingress.class` annotation in Kubernetes 1.18+.

```yaml
# deploy/kubernetes/base/services/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: churn-renewal-forecast
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - churn-forecast.infrastructure.immuta.io
      secretName: churn-renewal-forecast-tls
  rules:
    - host: churn-forecast.infrastructure.immuta.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: churn-renewal-forecast
                port:
                  name: http
```

- [ ] **Step 6: Create services/kustomization.yaml**

```yaml
# deploy/kubernetes/base/services/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - web-deployment.yaml
  - worker-deployment.yaml
  - service.yaml
  - ingress.yaml
```

- [ ] **Step 7: Validate**

```bash
kustomize build deploy/kubernetes/base/services
```

Expected: YAML output with two Deployments, one Service, one Ingress. No errors.

- [ ] **Step 8: Commit**

```bash
git add deploy/kubernetes/base/services/
git commit -m "feat: add k8s service resources (web, worker deployments, svc, ingress)"
```

---

## Task 5: Base Root Kustomization

Ties all four base sub-directories together into a single root kustomization.

**Files:**
- Create: `deploy/kubernetes/base/kustomization.yaml`

- [ ] **Step 1: Create base/kustomization.yaml**

```yaml
# deploy/kubernetes/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - bootstrap/
  - init/
  - netpol/
  - services/
```

- [ ] **Step 2: Validate full base build**

```bash
kustomize build deploy/kubernetes/base
```

Expected: Combined YAML output with all resources — Namespace, ServiceAccount, ExternalSecret, ConfigMap, Job, three CiliumNetworkPolicies, two Deployments, Service, Ingress. No errors.

- [ ] **Step 3: Commit**

```bash
git add deploy/kubernetes/base/kustomization.yaml
git commit -m "feat: add k8s base kustomization root"
```

---

## Task 6: Cluster Overlay (eks-shared-infra-use1-2)

Creates the overlay that references the base and applies cluster-specific patches: private ingress visibility and a service account patch stub for optional IRSA.

**Files:**
- Create: `deploy/kubernetes/deploy/eks-shared-infra-use1-2/kustomization.yaml`
- Create: `deploy/kubernetes/deploy/eks-shared-infra-use1-2/.app.yaml`
- Create: `deploy/kubernetes/deploy/eks-shared-infra-use1-2/ingress-patch.yaml`
- Create: `deploy/kubernetes/deploy/eks-shared-infra-use1-2/serviceaccount.yaml`

- [ ] **Step 1: Create directory**

```bash
mkdir -p deploy/kubernetes/deploy/eks-shared-infra-use1-2
```

- [ ] **Step 2: Create .app.yaml**

Flux metadata file consumed by the cluster's GitOps tooling. Update the `cluster` field if the cluster name differs.

```yaml
# deploy/kubernetes/deploy/eks-shared-infra-use1-2/.app.yaml
app: churn-renewal-forecast
namespace: churn-renewal-forecast
cluster: eks-shared-infra-use1-2
```

- [ ] **Step 3: Create ingress-patch.yaml**

Strategic merge patch that adds the internal-load-balancer annotation to the base Ingress. This restricts the ALB to private subnets only.

```yaml
# deploy/kubernetes/deploy/eks-shared-infra-use1-2/ingress-patch.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: churn-renewal-forecast
  namespace: churn-renewal-forecast
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-internal: "true"
    nginx.ingress.kubernetes.io/scheme: internal
```

- [ ] **Step 4: Create serviceaccount.yaml**

Strategic merge patch stub. The IRSA annotation is commented out because this app does not need AWS API access. Uncomment and supply the role ARN if that changes.

```yaml
# deploy/kubernetes/deploy/eks-shared-infra-use1-2/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: churn-renewal-forecast
  namespace: churn-renewal-forecast
  # annotations:
  #   eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/churn-renewal-forecast
```

- [ ] **Step 5: Create overlay kustomization.yaml**

```yaml
# deploy/kubernetes/deploy/eks-shared-infra-use1-2/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

patches:
  - path: ingress-patch.yaml
  - path: serviceaccount.yaml
```

- [ ] **Step 6: Validate full overlay build**

This is the final validation — it produces the exact manifests that would be applied to the cluster.

```bash
kustomize build deploy/kubernetes/deploy/eks-shared-infra-use1-2
```

Expected: All base resources with the Ingress now carrying both `service.beta.kubernetes.io/aws-load-balancer-internal: "true"` and `nginx.ingress.kubernetes.io/scheme: internal` annotations. No errors.

- [ ] **Step 7: Commit**

```bash
git add deploy/kubernetes/deploy/
git commit -m "feat: add eks-shared-infra-use1-2 overlay (private ingress, sa stub)"
```

---

## Self-Review Checklist

- [x] Namespace `churn-renewal-forecast` created — Task 1
- [x] ServiceAccount created — Task 1
- [x] ExternalSecret with all 6 vault keys — Task 1
- [x] ConfigMap with all non-sensitive env vars — Task 1
- [x] Prisma migration Job — Task 2
- [x] CiliumNetworkPolicy: DNS UDP 53 — Task 3
- [x] CiliumNetworkPolicy: RDS TCP 5432 — Task 3
- [x] CiliumNetworkPolicy: External TCP 443 (clerk, front, productboard, slack) — Task 3
- [x] Web Deployment with liveness/readiness probes on `/api/health/live` and `/api/health/ready` — Task 4
- [x] Worker Deployment, no probes — Task 4
- [x] ClusterIP Service port 80→3000 — Task 4
- [x] Nginx Ingress for `churn-forecast.infrastructure.immuta.io` — Task 4
- [x] `app.kubernetes.io/name: churn-renewal-forecast` on all resources — Tasks 1–4
- [x] `app.kubernetes.io/component` on Deployments and Service — Tasks 1, 4
- [x] Both Deployments use `envFrom` for ConfigMap and Secret — Tasks 2, 4
- [x] Init Job uses `envFrom` for ConfigMap and Secret — Task 2
- [x] Single image `registry.ic-ops.net/churn-renewal-forecast:latest` across all workloads — Tasks 2, 4
- [x] Overlay adds private ingress annotations — Task 6
- [x] IRSA annotation stub (commented out) — Task 6
- [x] Every task has a `kustomize build` validation step — Tasks 1–6


Create Kustomize deployment for churn-renewal-forecast


Plan complete. Let me also save it to Obsidian directly since the hook only covers `specs/` — I'll ask you about widening it after.


# Kustomize Deployment 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:** Create a complete Kustomize deployment under `deploy/kubernetes/` with a base layer and a single cluster overlay for `eks-shared-infra-use1-2`.

**Architecture:** Base layer is split into four sub-directories (bootstrap, init, netpol, services) each with their own `kustomization.yaml`. The cluster overlay at `deploy/kubernetes/deploy/eks-shared-infra-use1-2/` references the base and applies cluster-specific patches (ingress visibility, service account). All resources share a single container image with command overrides per workload.

**Tech Stack:** Kustomize v4+, Kubernetes manifests (apps/v1, batch/v1, networking.k8s.io/v1), external-secrets.io v1beta1 (ExternalSecret), cilium.io/v2 (CiliumNetworkPolicy)

---

See local file: `docs/superpowers/plans/2026-05-18-kustomize-deployment.md`

### Tasks
- Task 1: Bootstrap Resources (namespace, SA, ExternalSecret, ConfigMap)
- Task 2: Init Job (Prisma migrate deploy)
- Task 3: Network Policies (DNS, RDS, external HTTPS)
- Task 4: Services (web/worker Deployments, ClusterIP Service, Ingress)
- Task 5: Base Root Kustomization
- Task 6: Cluster Overlay (eks-shared-infra-use1-2)


File created successfully

File created successfully


Updated task #5 status


Plan saved to `docs/superpowers/plans/2026-05-18-kustomize-deployment.md` and mirrored to Obsidian at `Claude/churn-renewal-forecast/2026-05-18-kustomize-deployment-plan.md`. No cap, it's a W.

**Side note:** The Obsidian auto-sync hook currently only watches `docs/superpowers/specs/`. Want me to widen the pattern to `docs/superpowers/` so plans get auto-synced too?

---

**Two execution options:**

**1. Subagent-Driven (recommended)** — I dispatch a fresh subagent per task, review between tasks, fast iteration

**2. Inline Execution** — Execute all 6 tasks in this session with checkpoints

Which approach?


afplay ~/.claude/sounds/mario/stop/stomp.wav 2>/dev/null || true


lets do subagent driven execution


Launching skill: superpowers:subagent-driven-development


Base directory for this skill: /Users/steve.briggs/.claude/plugins/cache/claude-plugins-official/superpowers/5.1.0/skills/subagent-driven-development

# Subagent-Driven Development

Execute plan by dispatching fresh subagent per task, with two-stage review after each: spec compliance review first, then code quality review.

**Why subagents:** You delegate tasks to specialized agents with isolated context. By precisely crafting their instructions and context, you ensure they stay focused and succeed at their task. They should never inherit your session's context or history — you construct exactly what they need. This also preserves your own context for coordination work.

**Core principle:** Fresh subagent per task + two-stage review (spec then quality) = high quality, fast iteration

**Continuous execution:** Do not pause to check in with your human partner between tasks. Execute all tasks from the plan without stopping. The only reasons to stop are: BLOCKED status you cannot resolve, ambiguity that genuinely prevents progress, or all tasks complete. "Should I continue?" prompts and progress summaries waste their time — they asked you to execute the plan, so execute it.

## When to Use

```dot
digraph when_to_use {
    "Have implementation plan?" [shape=diamond];
    "Tasks mostly independent?" [shape=diamond];
    "Stay in this session?" [shape=diamond];
    "subagent-driven-development" [shape=box];
    "executing-plans" [shape=box];
    "Manual execution or brainstorm first" [shape=box];

    "Have implementation plan?" -> "Tasks mostly independent?" [label="yes"];
    "Have implementation plan?" -> "Manual execution or brainstorm first" [label="no"];
    "Tasks mostly independent?" -> "Stay in this session?" [label="yes"];
    "Tasks mostly independent?" -> "Manual execution or brainstorm first" [label="no - tightly coupled"];
    "Stay in this session?" -> "subagent-driven-development" [label="yes"];
    "Stay in this session?" -> "executing-plans" [label="no - parallel session"];
}
```

**vs. Executing Plans (parallel session):**
- Same session (no context switch)
- Fresh subagent per task (no context pollution)
- Two-stage review after each task: spec compliance first, then code quality
- Faster iteration (no human-in-loop between tasks)

## The Process

```dot
digraph process {
    rankdir=TB;

    subgraph cluster_per_task {
        label="Per Task";
        "Dispatch implementer subagent (./implementer-prompt.md)" [shape=box];
        "Implementer subagent asks questions?" [shape=diamond];
        "Answer questions, provide context" [shape=box];
        "Implementer subagent implements, tests, commits, self-reviews" [shape=box];
        "Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)" [shape=box];
        "Spec reviewer subagent confirms code matches spec?" [shape=diamond];
        "Implementer subagent fixes spec gaps" [shape=box];
        "Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" [shape=box];
        "Code quality reviewer subagent approves?" [shape=diamond];
        "Implementer subagent fixes quality issues" [shape=box];
        "Mark task complete in TodoWrite" [shape=box];
    }

    "Read plan, extract all tasks with full text, note context, create TodoWrite" [shape=box];
    "More tasks remain?" [shape=diamond];
    "Dispatch final code reviewer subagent for entire implementation" [shape=box];
    "Use superpowers:finishing-a-development-branch" [shape=box style=filled fillcolor=lightgreen];

    "Read plan, extract all tasks with full text, note context, create TodoWrite" -> "Dispatch implementer subagent (./implementer-prompt.md)";
    "Dispatch implementer subagent (./implementer-prompt.md)" -> "Implementer subagent asks questions?";
    "Implementer subagent asks questions?" -> "Answer questions, provide context" [label="yes"];
    "Answer questions, provide context" -> "Dispatch implementer subagent (./implementer-prompt.md)";
    "Implementer subagent asks questions?" -> "Implementer subagent implements, tests, commits, self-reviews" [label="no"];
    "Implementer subagent implements, tests, commits, self-reviews" -> "Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)";
    "Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)" -> "Spec reviewer subagent confirms code matches spec?";
    "Spec reviewer subagent confirms code matches spec?" -> "Implementer subagent fixes spec gaps" [label="no"];
    "Implementer subagent fixes spec gaps" -> "Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)" [label="re-review"];
    "Spec reviewer subagent confirms code matches spec?" -> "Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" [label="yes"];
    "Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" -> "Code quality reviewer subagent approves?";
    "Code quality reviewer subagent approves?" -> "Implementer subagent fixes quality issues" [label="no"];
    "Implementer subagent fixes quality issues" -> "Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" [label="re-review"];
    "Code quality reviewer subagent approves?" -> "Mark task complete in TodoWrite" [label="yes"];
    "Mark task complete in TodoWrite" -> "More tasks remain?";
    "More tasks remain?" -> "Dispatch implementer subagent (./implementer-prompt.md)" [label="yes"];
    "More tasks remain?" -> "Dispatch final code reviewer subagent for entire implementation" [label="no"];
    "Dispatch final code reviewer subagent for entire implementation" -> "Use superpowers:finishing-a-development-branch";
}
```

## Model Selection

Use the least powerful model that can handle each role to conserve cost and increase speed.

**Mechanical implementation tasks** (isolated functions, clear specs, 1-2 files): use a fast, cheap model. Most implementation tasks are mechanical when the plan is well-specified.

**Integration and judgment tasks** (multi-file coordination, pattern matching, debugging): use a standard model.

**Architecture, design, and review tasks**: use the most capable available model.

**Task complexity signals:**
- Touches 1-2 files with a complete spec → cheap model
- Touches multiple files with integration concerns → standard model
- Requires design judgment or broad codebase understanding → most capable model

## Handling Implementer Status

Implementer subagents report one of four statuses. Handle each appropriately:

**DONE:** Proceed to spec compliance review.

**DONE_WITH_CONCERNS:** The implementer completed the work but flagged doubts. Read the concerns before proceeding. If the concerns are about correctness or scope, address them before review. If they're observations (e.g., "this file is getting large"), note them and proceed to review.

**NEEDS_CONTEXT:** The implementer needs information that wasn't provided. Provide the missing context and re-dispatch.

**BLOCKED:** The implementer cannot complete the task. Assess the blocker:
1. If it's a context problem, provide more context and re-dispatch with the same model
2. If the task requires more reasoning, re-dispatch with a more capable model
3. If the task is too large, break it into smaller pieces
4. If the plan itself is wrong, escalate to the human

**Never** ignore an escalation or force the same model to retry without changes. If the implementer said it's stuck, something needs to change.

## Prompt Templates

- `./implementer-prompt.md` - Dispatch implementer subagent
- `./spec-reviewer-prompt.md` - Dispatch spec compliance reviewer subagent
- `./code-quality-reviewer-prompt.md` - Dispatch code quality reviewer subagent

## Example Workflow

```
You: I'm using Subagent-Driven Development to execute this plan.

[Read plan file once: docs/superpowers/plans/feature-plan.md]
[Extract all 5 tasks with full text and context]
[Create TodoWrite with all tasks]

Task 1: Hook installation script

[Get Task 1 text and context (already extracted)]
[Dispatch implementation subagent with full task text + context]

Implementer: "Before I begin - should the hook be installed at user or system level?"

You: "User level (~/.config/superpowers/hooks/)"

Implementer: "Got it. Implementing now..."
[Later] Implementer:
  - Implemented install-hook command
  - Added tests, 5/5 passing
  - Self-review: Found I missed --force flag, added it
  - Committed

[Dispatch spec compliance reviewer]
Spec reviewer: ✅ Spec compliant - all requirements met, nothing extra

[Get git SHAs, dispatch code quality reviewer]
Code reviewer: Strengths: Good test coverage, clean. Issues: None. Approved.

[Mark Task 1 complete]

Task 2: Recovery modes

[Get Task 2 text and context (already extracted)]
[Dispatch implementation subagent with full task text + context]

Implementer: [No questions, proceeds]
Implementer:
  - Added verify/repair modes
  - 8/8 tests passing
  - Self-review: All good
  - Committed

[Dispatch spec compliance reviewer]
Spec reviewer: ❌ Issues:
  - Missing: Progress reporting (spec says "report every 100 items")
  - Extra: Added --json flag (not requested)

[Implementer fixes issues]
Implementer: Removed --json flag, added progress reporting

[Spec reviewer reviews again]
Spec reviewer: ✅ Spec compliant now

[Dispatch code quality reviewer]
Code reviewer: Strengths: Solid. Issues (Important): Magic number (100)

[Implementer fixes]
Implementer: Extracted PROGRESS_INTERVAL constant

[Code reviewer reviews again]
Code reviewer: ✅ Approved

[Mark Task 2 complete]

...

[After all tasks]
[Dispatch final code-reviewer]
Final reviewer: All requirements met, ready to merge

Done!
```

## Advantages

**vs. Manual execution:**
- Subagents follow TDD naturally
- Fresh context per task (no confusion)
- Parallel-safe (subagents don't interfere)
- Subagent can ask questions (before AND during work)

**vs. Executing Plans:**
- Same session (no handoff)
- Continuous progress (no waiting)
- Review checkpoints automatic

**Efficiency gains:**
- No file reading overhead (controller provides full text)
- Controller curates exactly what context is needed
- Subagent gets complete information upfront
- Questions surfaced before work begins (not after)

**Quality gates:**
- Self-review catches issues before handoff
- Two-stage review: spec compliance, then code quality
- Review loops ensure fixes actually work
- Spec compliance prevents over/under-building
- Code quality ensures implementation is well-built

**Cost:**
- More subagent invocations (implementer + 2 reviewers per task)
- Controller does more prep work (extracting all tasks upfront)
- Review loops add iterations
- But catches issues early (cheaper than debugging later)

## Red Flags

**Never:**
- Start implementation on main/master branch without explicit user consent
- Skip reviews (spec compliance OR code quality)
- Proceed with unfixed issues
- Dispatch multiple implementation subagents in parallel (conflicts)
- Make subagent read plan file (provide full text instead)
- Skip scene-setting context (subagent needs to understand where task fits)
- Ignore subagent questions (answer before letting them proceed)
- Accept "close enough" on spec compliance (spec reviewer found issues = not done)
- Skip review loops (reviewer found issues = implementer fixes = review again)
- Let implementer self-review replace actual review (both are needed)
- **Start code quality review before spec compliance is ✅** (wrong order)
- Move to next task while either review has open issues

**If subagent asks questions:**
- Answer clearly and completely
- Provide additional context if needed
- Don't rush them into implementation

**If reviewer finds issues:**
- Implementer (same subagent) fixes them
- Reviewer reviews again
- Repeat until approved
- Don't skip the re-review

**If subagent fails task:**
- Dispatch fix subagent with specific instructions
- Don't try to fix manually (context pollution)

## Integration

**Required workflow skills:**
- **superpowers:using-git-worktrees** - Ensures isolated workspace (creates one or verifies existing)
- **superpowers:writing-plans** - Creates the plan this skill executes
- **superpowers:requesting-code-review** - Code review template for reviewer subagents
- **superpowers:finishing-a-development-branch** - Complete development after all tasks

**Subagents should use:**
- **superpowers:test-driven-development** - Subagents follow TDD for each task

**Alternative workflow:**
- **superpowers:executing-plans** - Use for parallel session instead of same-session execution


I'm using Subagent-Driven Development to execute this plan.

Let me read the prompt templates, then create the task list and start dispatching.


1	# Implementer Subagent Prompt Template
2
3	Use this template when dispatching an implementer subagent.
4
5	```
6	Task tool (general-purpose):
7	  description: "Implement Task N: [task name]"
8	  prompt: |
9	    You are implementing Task N: [task name]
10
11	    ## Task Description
12
13	    [FULL TEXT of task from plan - paste it here, don't make subagent read file]
14
15	    ## Context
16
17	    [Scene-setting: where this fits, dependencies, architectural context]
18
19	    ## Before You Begin
20
21	    If you have questions about:
22	    - The requirements or acceptance criteria
23	    - The approach or implementation strategy
24	    - Dependencies or assumptions
25	    - Anything unclear in the task description
26
27	    **Ask them now.** Raise any concerns before starting work.
28
29	    ## Your Job
30
31	    Once you're clear on requirements:
32	    1. Implement exactly what the task specifies
33	    2. Write tests (following TDD if task says to)
34	    3. Verify implementation works
35	    4. Commit your work
36	    5. Self-review (see below)
37	    6. Report back
38
39	    Work from: [directory]
40
41	    **While you work:** If you encounter something unexpected or unclear, **ask questions**.
42	    It's always OK to pause and clarify. Don't guess or make assumptions.
43
44	    ## Code Organization
45
46	    You reason best about code you can hold in context at once, and your edits are more
47	    reliable when files are focused. Keep this in mind:
48	    - Follow the file structure defined in the plan
49	    - Each file should have one clear responsibility with a well-defined interface
50	    - If a file you're creating is growing beyond the plan's intent, stop and report
51	      it as DONE_WITH_CONCERNS — don't split files on your own without plan guidance
52	    - If an existing file you're modifying is already large or tangled, work carefully
53	      and note it as a concern in your report
54	    - In existing codebases, follow established patterns. Improve code you're touching
55	      the way a good developer would, but don't restructure things outside your task.
56
57	    ## When You're in Over Your Head
58
59	    It is always OK to stop and say "this is too hard for me." Bad work is worse than
60	    no work. You will not be penalized for escalating.
61
62	    **STOP and escalate when:**
63	    - The task requires architectural decisions with multiple valid approaches
64	    - You need to understand code beyond what was provided and can't find clarity
65	    - You feel uncertain about whether your approach is correct
66	    - The task involves restructuring existing code in ways the plan didn't anticipate
67	    - You've been reading file after file trying to understand the system without progress
68
69	    **How to escalate:** Report back with status BLOCKED or NEEDS_CONTEXT. Describe
70	    specifically what you're stuck on, what you've tried, and what kind of help you need.
71	    The controller can provide more context, re-dispatch with a more capable model,
72	    or break the task into smaller pieces.
73
74	    ## Before Reporting Back: Self-Review
75
76	    Review your work with fresh eyes. Ask yourself:
77
78	    **Completeness:**
79	    - Did I fully implement everything in the spec?
80	    - Did I miss any requirements?
81	    - Are there edge cases I didn't handle?
82
83	    **Quality:**
84	    - Is this my best work?
85	    - Are names clear and accurate (match what things do, not how they work)?
86	    - Is the code clean and maintainable?
87
88	    **Discipline:**
89	    - Did I avoid overbuilding (YAGNI)?
90	    - Did I only build what was requested?
91	    - Did I follow existing patterns in the codebase?
92
93	    **Testing:**
94	    - Do tests actually verify behavior (not just mock behavior)?
95	    - Did I follow TDD if required?
96	    - Are tests comprehensive?
97
98	    If you find issues during self-review, fix them now before reporting.
99
100	    ## Report Format
101
102	    When done, report:
103	    - **Status:** DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
104	    - What you implemented (or what you attempted, if blocked)
105	    - What you tested and test results
106	    - Files changed
107	    - Self-review findings (if any)
108	    - Any issues or concerns
109
110	    Use DONE_WITH_CONCERNS if you completed the work but have doubts about correctness.
111	    Use BLOCKED if you cannot complete the task. Use NEEDS_CONTEXT if you need
112	    information that wasn't provided. Never silently produce work you're unsure about.
113	```
114

# Implementer Subagent Prompt Template

Use this template when dispatching an implementer subagent.

```
Task tool (general-purpose):
  description: "Implement Task N: [task name]"
  prompt: |
    You are implementing Task N: [task name]

    ## Task Description

    [FULL TEXT of task from plan - paste it here, don't make subagent read file]

    ## Context

    [Scene-setting: where this fits, dependencies, architectural context]

    ## Before You Begin

    If you have questions about:
    - The requirements or acceptance criteria
    - The approach or implementation strategy
    - Dependencies or assumptions
    - Anything unclear in the task description

    **Ask them now.** Raise any concerns before starting work.

    ## Your Job

    Once you're clear on requirements:
    1. Implement exactly what the task specifies
    2. Write tests (following TDD if task says to)
    3. Verify implementation works
    4. Commit your work
    5. Self-review (see below)
    6. Report back

    Work from: [directory]

    **While you work:** If you encounter something unexpected or unclear, **ask questions**.
    It's always OK to pause and clarify. Don't guess or make assumptions.

    ## Code Organization

    You reason best about code you can hold in context at once, and your edits are more
    reliable when files are focused. Keep this in mind:
    - Follow the file structure defined in the plan
    - Each file should have one clear responsibility with a well-defined interface
    - If a file you're creating is growing beyond the plan's intent, stop and report
      it as DONE_WITH_CONCERNS — don't split files on your own without plan guidance
    - If an existing file you're modifying is already large or tangled, work carefully
      and note it as a concern in your report
    - In existing codebases, follow established patterns. Improve code you're touching
      the way a good developer would, but don't restructure things outside your task.

    ## When You're in Over Your Head

    It is always OK to stop and say "this is too hard for me." Bad work is worse than
    no work. You will not be penalized for escalating.

    **STOP and escalate when:**
    - The task requires architectural decisions with multiple valid approaches
    - You need to understand code beyond what was provided and can't find clarity
    - You feel uncertain about whether your approach is correct
    - The task involves restructuring existing code in ways the plan didn't anticipate
    - You've been reading file after file trying to understand the system without progress

    **How to escalate:** Report back with status BLOCKED or NEEDS_CONTEXT. Describe
    specifically what you're stuck on, what you've tried, and what kind of help you need.
    The controller can provide more context, re-dispatch with a more capable model,
    or break the task into smaller pieces.

    ## Before Reporting Back: Self-Review

    Review your work with fresh eyes. Ask yourself:

    **Completeness:**
    - Did I fully implement everything in the spec?
    - Did I miss any requirements?
    - Are there edge cases I didn't handle?

    **Quality:**
    - Is this my best work?
    - Are names clear and accurate (match what things do, not how they work)?
    - Is the code clean and maintainable?

    **Discipline:**
    - Did I avoid overbuilding (YAGNI)?
    - Did I only build what was requested?
    - Did I follow existing patterns in the codebase?

    **Testing:**
    - Do tests actually verify behavior (not just mock behavior)?
    - Did I follow TDD if required?
    - Are tests comprehensive?

    If you find issues during self-review, fix them now before reporting.

    ## Report Format

    When done, report:
    - **Status:** DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
    - What you implemented (or what you attempted, if blocked)
    - What you tested and test results
    - Files changed
    - Self-review findings (if any)
    - Any issues or concerns

    Use DONE_WITH_CONCERNS if you completed the work but have doubts about correctness.
    Use BLOCKED if you cannot complete the task. Use NEEDS_CONTEXT if you need
    information that wasn't provided. Never silently produce work you're unsure about.
```


1	# Spec Compliance Reviewer Prompt Template
2
3	Use this template when dispatching a spec compliance reviewer subagent.
4
5	**Purpose:** Verify implementer built what was requested (nothing more, nothing less)
6
7	```
8	Task tool (general-purpose):
9	  description: "Review spec compliance for Task N"
10	  prompt: |
11	    You are reviewing whether an implementation matches its specification.
12
13	    ## What Was Requested
14
15	    [FULL TEXT of task requirements]
16
17	    ## What Implementer Claims They Built
18
19	    [From implementer's report]
20
21	    ## CRITICAL: Do Not Trust the Report
22
23	    The implementer finished suspiciously quickly. Their report may be incomplete,
24	    inaccurate, or optimistic. You MUST verify everything independently.
25
26	    **DO NOT:**
27	    - Take their word for what they implemented
28	    - Trust their claims about completeness
29	    - Accept their interpretation of requirements
30
31	    **DO:**
32	    - Read the actual code they wrote
33	    - Compare actual implementation to requirements line by line
34	    - Check for missing pieces they claimed to implement
35	    - Look for extra features they didn't mention
36
37	    ## Your Job
38
39	    Read the implementation code and verify:
40
41	    **Missing requirements:**
42	    - Did they implement everything that was requested?
43	    - Are there requirements they skipped or missed?
44	    - Did they claim something works but didn't actually implement it?
45
46	    **Extra/unneeded work:**
47	    - Did they build things that weren't requested?
48	    - Did they over-engineer or add unnecessary features?
49	    - Did they add "nice to haves" that weren't in spec?
50
51	    **Misunderstandings:**
52	    - Did they interpret requirements differently than intended?
53	    - Did they solve the wrong problem?
54	    - Did they implement the right feature but wrong way?
55
56	    **Verify by reading code, not by trusting report.**
57
58	    Report:
59	    - ✅ Spec compliant (if everything matches after code inspection)
60	    - ❌ Issues found: [list specifically what's missing or extra, with file:line references]
61	```
62

# Spec Compliance Reviewer Prompt Template

Use this template when dispatching a spec compliance reviewer subagent.

**Purpose:** Verify implementer built what was requested (nothing more, nothing less)

```
Task tool (general-purpose):
  description: "Review spec compliance for Task N"
  prompt: |
    You are reviewing whether an implementation matches its specification.

    ## What Was Requested

    [FULL TEXT of task requirements]

    ## What Implementer Claims They Built

    [From implementer's report]

    ## CRITICAL: Do Not Trust the Report

    The implementer finished suspiciously quickly. Their report may be incomplete,
    inaccurate, or optimistic. You MUST verify everything independently.

    **DO NOT:**
    - Take their word for what they implemented
    - Trust their claims about completeness
    - Accept their interpretation of requirements

    **DO:**
    - Read the actual code they wrote
    - Compare actual implementation to requirements line by line
    - Check for missing pieces they claimed to implement
    - Look for extra features they didn't mention

    ## Your Job

    Read the implementation code and verify:

    **Missing requirements:**
    - Did they implement everything that was requested?
    - Are there requirements they skipped or missed?
    - Did they claim something works but didn't actually implement it?

    **Extra/unneeded work:**
    - Did they build things that weren't requested?
    - Did they over-engineer or add unnecessary features?
    - Did they add "nice to haves" that weren't in spec?

    **Misunderstandings:**
    - Did they interpret requirements differently than intended?
    - Did they solve the wrong problem?
    - Did they implement the right feature but wrong way?

    **Verify by reading code, not by trusting report.**

    Report:
    - ✅ Spec compliant (if everything matches after code inspection)
    - ❌ Issues found: [list specifically what's missing or extra, with file:line references]
```


1	# Code Quality Reviewer Prompt Template
2
3	Use this template when dispatching a code quality reviewer subagent.
4
5	**Purpose:** Verify implementation is well-built (clean, tested, maintainable)
6
7	**Only dispatch after spec compliance review passes.**
8
9	```
10	Task tool (general-purpose):
11	  Use template at requesting-code-review/code-reviewer.md
12
13	  DESCRIPTION: [task summary, from implementer's report]
14	  PLAN_OR_REQUIREMENTS: Task N from [plan-file]
15	  BASE_SHA: [commit before task]
16	  HEAD_SHA: [current commit]
17	```
18
19	**In addition to standard code quality concerns, the reviewer should check:**
20	- Does each file have one clear responsibility with a well-defined interface?
21	- Are units decomposed so they can be understood and tested independently?
22	- Is the implementation following the file structure from the plan?
23	- Did this implementation create new files that are already large, or significantly grow existing files? (Don't flag pre-existing file sizes — focus on what this change contributed.)
24
25	**Code reviewer returns:** Strengths, Issues (Critical/Important/Minor), Assessment
26

# Code Quality Reviewer Prompt Template

Use this template when dispatching a code quality reviewer subagent.

**Purpose:** Verify implementation is well-built (clean, tested, maintainable)

**Only dispatch after spec compliance review passes.**

```
Task tool (general-purpose):
  Use template at requesting-code-review/code-reviewer.md

  DESCRIPTION: [task summary, from implementer's report]
  PLAN_OR_REQUIREMENTS: Task N from [plan-file]
  BASE_SHA: [commit before task]
  HEAD_SHA: [current commit]
```

**In addition to standard code quality concerns, the reviewer should check:**
- Does each file have one clear responsibility with a well-defined interface?
- Are units decomposed so they can be understood and tested independently?
- Is the implementation following the file structure from the plan?
- Did this implementation create new files that are already large, or significantly grow existing files? (Don't flag pre-existing file sizes — focus on what this change contributed.)

**Code reviewer returns:** Strengths, Issues (Critical/Important/Minor), Assessment


1	# Code Reviewer Prompt Template
2
3	Use this template when dispatching a code reviewer subagent.
4
5	**Purpose:** Review completed work against requirements and code quality standards before it cascades into more work.
6
7	```
8	Task tool (general-purpose):
9	  description: "Review code changes"
10	  prompt: |
11	    You are a Senior Code Reviewer with expertise in software architecture,
12	    design patterns, and best practices. Your job is to review completed work
13	    against its plan or requirements and identify issues before they cascade.
14
15	    ## What Was Implemented
16
17	    {DESCRIPTION}
18
19	    ## Requirements / Plan
20
21	    {PLAN_OR_REQUIREMENTS}
22
23	    ## Git Range to Review
24
25	    **Base:** {BASE_SHA}
26	    **Head:** {HEAD_SHA}
27
28	    ```bash
29	    git diff --stat {BASE_SHA}..{HEAD_SHA}
30	    git diff {BASE_SHA}..{HEAD_SHA}
31	    ```
32
33	    ## What to Check
34
35	    **Plan alignment:**
36	    - Does the implementation match the plan / requirements?
37	    - Are deviations justified improvements, or problematic departures?
38	    - Is all planned functionality present?
39
40	    **Code quality:**
41	    - Clean separation of concerns?
42	    - Proper error handling?
43	    - Type safety where applicable?
44	    - DRY without premature abstraction?
45	    - Edge cases handled?
46
47	    **Architecture:**
48	    - Sound design decisions?
49	    - Reasonable scalability and performance?
50	    - Security concerns?
51	    - Integrates cleanly with surrounding code?
52
53	    **Testing:**
54	    - Tests verify real behavior, not mocks?
55	    - Edge cases covered?
56	    - Integration tests where they matter?
57	    - All tests passing?
58
59	    **Production readiness:**
60	    - Migration strategy if schema changed?
61	    - Backward compatibility considered?
62	    - Documentation complete?
63	    - No obvious bugs?
64
65	    ## Calibration
66
67	    Categorize issues by actual severity. Not everything is Critical.
68	    Acknowledge what was done well before listing issues — accurate praise
69	    helps the implementer trust the rest of the feedback.
70
71	    If you find significant deviations from the plan, flag them specifically
72	    so the implementer can confirm whether the deviation was intentional.
73	    If you find issues with the plan itself rather than the implementation,
74	    say so.
75
76	    ## Output Format
77
78	    ### Strengths
79	    [What's well done? Be specific.]
80
81	    ### Issues
82
83	    #### Critical (Must Fix)
84	    [Bugs, security issues, data loss risks, broken functionality]
85
86	    #### Important (Should Fix)
87	    [Architecture problems, missing features, poor error handling, test gaps]
88
89	    #### Minor (Nice to Have)
90	    [Code style, optimization opportunities, documentation polish]
91
92	    For each issue:
93	    - File:line reference
94	    - What's wrong
95	    - Why it matters
96	    - How to fix (if not obvious)
97
98	    ### Recommendations
99	    [Improvements for code quality, architecture, or process]
100
101	    ### Assessment
102
103	    **Ready to merge?** [Yes | No | With fixes]
104
105	    **Reasoning:** [1-2 sentence technical assessment]
106
107	    ## Critical Rules
108
109	    **DO:**
110	    - Categorize by actual severity
111	    - Be specific (file:line, not vague)
112	    - Explain WHY each issue matters
113	    - Acknowledge strengths
114	    - Give a clear verdict
115
116	    **DON'T:**
117	    - Say "looks good" without checking
118	    - Mark nitpicks as Critical
119	    - Give feedback on code you didn't actually read
120	    - Be vague ("improve error handling")
121	    - Avoid giving a clear verdict
122	```
123
124	**Placeholders:**
125	- `{DESCRIPTION}` — brief summary of what was built
126	- `{PLAN_OR_REQUIREMENTS}` — what it should do (plan file path, task text, or requirements)
127	- `{BASE_SHA}` — starting commit
128	- `{HEAD_SHA}` — ending commit
129
130	**Reviewer returns:** Strengths, Issues (Critical / Important / Minor), Recommendations, Assessment
131
132	## Example Output
133
134	```
135	### Strengths
136	- Clean database schema with proper migrations (db.ts:15-42)
137	- Comprehensive test coverage (18 tests, all edge cases)
138	- Good error handling with fallbacks (summarizer.ts:85-92)
139
140	### Issues
141
142	#### Important
143	1. **Missing help text in CLI wrapper**
144	   - File: index-conversations:1-31
145	   - Issue: No --help flag, users won't discover --concurrency
146	   - Fix: Add --help case with usage examples
147
148	2. **Date validation missing**
149	   - File: search.ts:25-27
150	   - Issue: Invalid dates silently return no results
151	   - Fix: Validate ISO format, throw error with example
152
153	#### Minor
154	1. **Progress indicators**
155	   - File: indexer.ts:130
156	   - Issue: No "X of Y" counter for long operations
157	   - Impact: Users don't know how long to wait
158
159	### Recommendations
160	- Add progress reporting for user experience
161	- Consider config file for excluded projects (portability)
162
163	### Assessment
164
165	**Ready to merge: With fixes**
166
167	**Reasoning:** Core implementation is solid with good architecture and tests. Important issues (help text, date validation) are easily fixed and don't affect core functionality.
168	```
169

# Code Reviewer Prompt Template

Use this template when dispatching a code reviewer subagent.

**Purpose:** Review completed work against requirements and code quality standards before it cascades into more work.

```
Task tool (general-purpose):
  description: "Review code changes"
  prompt: |
    You are a Senior Code Reviewer with expertise in software architecture,
    design patterns, and best practices. Your job is to review completed work
    against its plan or requirements and identify issues before they cascade.

    ## What Was Implemented

    {DESCRIPTION}

    ## Requirements / Plan

    {PLAN_OR_REQUIREMENTS}

    ## Git Range to Review

    **Base:** {BASE_SHA}
    **Head:** {HEAD_SHA}

    ```bash
    git diff --stat {BASE_SHA}..{HEAD_SHA}
    git diff {BASE_SHA}..{HEAD_SHA}
    ```

    ## What to Check

    **Plan alignment:**
    - Does the implementation match the plan / requirements?
    - Are deviations justified improvements, or problematic departures?
    - Is all planned functionality present?

    **Code quality:**
    - Clean separation of concerns?
    - Proper error handling?
    - Type safety where applicable?
    - DRY without premature abstraction?
    - Edge cases handled?

    **Architecture:**
    - Sound design decisions?
    - Reasonable scalability and performance?
    - Security concerns?
    - Integrates cleanly with surrounding code?

    **Testing:**
    - Tests verify real behavior, not mocks?
    - Edge cases covered?
    - Integration tests where they matter?
    - All tests passing?

    **Production readiness:**
    - Migration strategy if schema changed?
    - Backward compatibility considered?
    - Documentation complete?
    - No obvious bugs?

    ## Calibration

    Categorize issues by actual severity. Not everything is Critical.
    Acknowledge what was done well before listing issues — accurate praise
    helps the implementer trust the rest of the feedback.

    If you find significant deviations from the plan, flag them specifically
    so the implementer can confirm whether the deviation was intentional.
    If you find issues with the plan itself rather than the implementation,
    say so.

    ## Output Format

    ### Strengths
    [What's well done? Be specific.]

    ### Issues

    #### Critical (Must Fix)
    [Bugs, security issues, data loss risks, broken functionality]

    #### Important (Should Fix)
    [Architecture problems, missing features, poor error handling, test gaps]

    #### Minor (Nice to Have)
    [Code style, optimization opportunities, documentation polish]

    For each issue:
    - File:line reference
    - What's wrong
    - Why it matters
    - How to fix (if not obvious)

    ### Recommendations
    [Improvements for code quality, architecture, or process]

    ### Assessment

    **Ready to merge?** [Yes | No | With fixes]

    **Reasoning:** [1-2 sentence technical assessment]

    ## Critical Rules

    **DO:**
    - Categorize by actual severity
    - Be specific (file:line, not vague)
    - Explain WHY each issue matters
    - Acknowledge strengths
    - Give a clear verdict

    **DON'T:**
    - Say "looks good" without checking
    - Mark nitpicks as Critical
    - Give feedback on code you didn't actually read
    - Be vague ("improve error handling")
    - Avoid giving a clear verdict
```

**Placeholders:**
- `{DESCRIPTION}` — brief summary of what was built
- `{PLAN_OR_REQUIREMENTS}` — what it should do (plan file path, task text, or requirements)
- `{BASE_SHA}` — starting commit
- `{HEAD_SHA}` — ending commit

**Reviewer returns:** Strengths, Issues (Critical / Important / Minor), Recommendations, Assessment

## Example Output

```
### Strengths
- Clean database schema with proper migrations (db.ts:15-42)
- Comprehensive test coverage (18 tests, all edge cases)
- Good error handling with fallbacks (summarizer.ts:85-92)

### Issues

#### Important
1. **Missing help text in CLI wrapper**
   - File: index-conversations:1-31
   - Issue: No --help flag, users won't discover --concurrency
   - Fix: Add --help case with usage examples

2. **Date validation missing**
   - File: search.ts:25-27
   - Issue: Invalid dates silently return no results
   - Fix: Validate ISO format, throw error with example

#### Minor
1. **Progress indicators**
   - File: indexer.ts:130
   - Issue: No "X of Y" counter for long operations
   - Impact: Users don't know how long to wait

### Recommendations
- Add progress reporting for user experience
- Consider config file for excluded projects (portability)

### Assessment

**Ready to merge: With fixes**

**Reasoning:** Core implementation is solid with good architecture and tests. Important issues (help text, date validation) are easily fixed and don't affect core functionality.
```


Now I'll create the task list and get the base SHA before dispatching.


Bash

Get base commit SHA


ToolSearch

Question: select:TaskCreate,TaskUpdate


Question: select:TaskCreate,TaskUpdate


TaskCreate

Task 1: Bootstrap resources

namespace.yaml, serviceaccount.yaml, secret.yaml, app-config.env, bootstrap/kustomization.yaml


Task #6 created successfully: Task 1: Bootstrap resources

Task 1: Bootstrap resources


Task 2: Init Job

job.yaml, init/kustomization.yaml — Prisma migrate deploy one-shot job


Task #7 created successfully: Task 2: Init Job

Task 2: Init Job


Task 3: Network Policies

allow-dns.yaml, allow-rds.yaml, allow-external.yaml, netpol/kustomization.yaml


Task #8 created successfully: Task 3: Network Policies

Task 3: Network Policies


Task 4: Services

web-deployment.yaml, worker-deployment.yaml, service.yaml, ingress.yaml, services/kustomization.yaml


Task #9 created successfully: Task 4: Services

Task 4: Services


Task 5: Base root kustomization

base/kustomization.yaml tying all sub-dirs together


Task #10 created successfully: Task 5: Base root kustomization

Task 5: Base root kustomization


Task 6: Cluster overlay

eks-shared-infra-use1-2: kustomization.yaml, .app.yaml, ingress-patch.yaml, serviceaccount.yaml


Task #11 created successfully: Task 6: Cluster overlay

Task 6: Cluster overlay


Updated task #6 status


Dispatching Task 1 implementer.


Agent

Implement Task 1: Bootstrap resources

Prompt:
You are implementing Task 1: Bootstrap Resources for a Kustomize Kubernetes deployment.

## Task Description

### Task 1: Bootstrap Resources

Creates the namespace, service account, ExternalSecret, and ConfigMap source.

**Files:**
- Create: `deploy/kubernetes/base/bootstrap/namespace.yaml`
- Create: `deploy/kubernetes/base/bootstrap/serviceaccount.yaml`
- Create: `deploy/kubernetes/base/bootstrap/secret.yaml`
- Create: `deploy/kubernetes/base/bootstrap/app-config.env`
- Create: `deploy/kubernetes/base/bootstrap/kustomization.yaml`

- [ ] **Step 1: Create directory structure**

```bash
mkdir -p deploy/kubernetes/base/bootstrap
```

- [ ] **Step 2: Create namespace.yaml**

```yaml
# deploy/kubernetes/base/bootstrap/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
```

- [ ] **Step 3: Create serviceaccount.yaml**

```yaml
# deploy/kubernetes/base/bootstrap/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: churn-renewal-forecast
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
```

- [ ] **Step 4: Create secret.yaml (ExternalSecret)**

```yaml
# deploy/kubernetes/base/bootstrap/secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: churn-renewal-forecast
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault
    kind: ClusterSecretStore
  target:
    name: churn-renewal-forecast
    creationPolicy: Owner
  data:
    - secretKey: DATABASE_URL
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: DATABASE_URL
    - secretKey: CLERK_SECRET_KEY
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: CLERK_SECRET_KEY
    - secretKey: FRONT_API_TOKEN
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: FRONT_API_TOKEN
    - secretKey: FRONT_API_TOKEN_2
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: FRONT_API_TOKEN_2
    - secretKey: PRODUCTBOARD_API_KEY
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: PRODUCTBOARD_API_KEY
    - secretKey: SLACK_BOT_TOKEN
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: SLACK_BOT_TOKEN
```

- [ ] **Step 5: Create app-config.env**

```env
# deploy/kubernetes/base/bootstrap/app-config.env
APP_URL=https://churn-forecast.infrastructure.immuta.io
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=PLACEHOLDER
NEXT_PUBLIC_PRODUCTBOARD_WORKSPACE=PLACEHOLDER
SLACK_TEST_CHANNEL=
PG_POOL_MAX=10
PG_POOL_IDLE_TIMEOUT_MS=30000
PG_POOL_CONNECTION_TIMEOUT_MS=2000
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
```

- [ ] **Step 6: Create bootstrap/kustomization.yaml**

`configMapGenerator` produces the ConfigMap from `app-config.env`. `disableNameSuffixHash: true` keeps the name stable so `envFrom` references don't need to change on every deploy.

```yaml
# deploy/kubernetes/base/bootstrap/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - namespace.yaml
  - serviceaccount.yaml
  - secret.yaml

configMapGenerator:
  - name: churn-renewal-forecast-config
    envs:
      - app-config.env

generatorOptions:
  disableNameSuffixHash: true
```

- [ ] **Step 7: Validate bootstrap kustomization builds**

```bash
kustomize build deploy/kubernetes/base/bootstrap
```

Expected: YAML output containing Namespace, ServiceAccount, ExternalSecret, and ConfigMap. No errors.

- [ ] **Step 8: Commit**

```bash
git add deploy/kubernetes/base/bootstrap/
git commit -m "feat: add k8s bootstrap resources (namespace, sa, secret, configmap)"
```

## Context

This is the first task of a 6-task Kustomize deployment build for the `churn-renewal-forecast` app. You are creating the foundational bootstrap resources — namespace, service account, secrets from Vault, and non-sensitive config. Later tasks depend on the namespace and named resources (ConfigMap `churn-renewal-forecast-config`, Secret `churn-renewal-forecast`) existing.

- **App:** `churn-renewal-forecast` — Next.js + Prisma + pg-boss worker
- **Namespace:** `churn-renewal-forecast`
- **Vault path:** `/internal/churn-renewal-forecast`
- **Secret store:** `ClusterSecretStore` named `vault`
- **Working directory:** `/Users/steve.briggs/projects/churn-renewal-forecast`
- **Kustomize:** available at `kustomize` in PATH (v4.5.7)

## Your Job

Create exactly the files specified — no more, no less. Validate with `kustomize build`, then commit. There are no unit tests to write; for Kubernetes YAML the validation IS the test.

Note: `app-config.env` must NOT have comment lines (lines starting with `#`) — Kustomize's `configMapGenerator` does not support comments in `.env` files and will fail the build.

## Report Format

When done:
- **Status:** DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
- What you implemented
- Files changed
- Validation result (`kustomize build` output summary)
- Any concerns


That's a W, Sir Charles of St Cheese.

---

**Status:** DONE

**What was implemented:**
Created all 5 bootstrap resource files under `deploy/kubernetes/base/bootstrap/`:

- `namespace.yaml` — Namespace `churn-renewal-forecast` with `app.kubernetes.io/name` label
- `serviceaccount.yaml` — ServiceAccount in that namespace with matching label
- `secret.yaml` — ExternalSecret pulling 6 keys from Vault path `/internal/churn-renewal-forecast` via `ClusterSecretStore/vault`
- `app-config.env` — Non-sensitive config (no comment lines, Kustomize-safe)
- `kustomization.yaml` — Wires resources + configMapGenerator with `disableNameSuffixHash: true`

**Files changed:** 5 new files, all in `/Users/steve.briggs/projects/churn-renewal-forecast/deploy/kubernetes/base/bootstrap/`

**Validation result:** `kustomize build` produced clean YAML output with all 4 resource types — Namespace, ServiceAccount, ConfigMap (with all 11 env vars), and ExternalSecret (with all 6 secret keys). Zero errors.

**Concerns:** None. The ConfigMap name `churn-renewal-forecast-config` and Secret name `churn-renewal-forecast` are stable for downstream tasks to reference.

agentId: a7a3a60daa8402441 (use SendMessage with to: 'a7a3a60daa8402441' to continue this agent)
<usage>total_tokens: 18898
tool_uses: 8
duration_ms: 2009953</usage>

Prompt:
You are implementing Task 1: Bootstrap Resources for a Kustomize Kubernetes deployment.

## Task Description

### Task 1: Bootstrap Resources

Creates the namespace, service account, ExternalSecret, and ConfigMap source.

**Files:**
- Create: `deploy/kubernetes/base/bootstrap/namespace.yaml`
- Create: `deploy/kubernetes/base/bootstrap/serviceaccount.yaml`
- Create: `deploy/kubernetes/base/bootstrap/secret.yaml`
- Create: `deploy/kubernetes/base/bootstrap/app-config.env`
- Create: `deploy/kubernetes/base/bootstrap/kustomization.yaml`

- [ ] **Step 1: Create directory structure**

```bash
mkdir -p deploy/kubernetes/base/bootstrap
```

- [ ] **Step 2: Create namespace.yaml**

```yaml
# deploy/kubernetes/base/bootstrap/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
```

- [ ] **Step 3: Create serviceaccount.yaml**

```yaml
# deploy/kubernetes/base/bootstrap/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: churn-renewal-forecast
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
```

- [ ] **Step 4: Create secret.yaml (ExternalSecret)**

```yaml
# deploy/kubernetes/base/bootstrap/secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: churn-renewal-forecast
  namespace: churn-renewal-forecast
  labels:
    app.kubernetes.io/name: churn-renewal-forecast
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault
    kind: ClusterSecretStore
  target:
    name: churn-renewal-forecast
    creationPolicy: Owner
  data:
    - secretKey: DATABASE_URL
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: DATABASE_URL
    - secretKey: CLERK_SECRET_KEY
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: CLERK_SECRET_KEY
    - secretKey: FRONT_API_TOKEN
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: FRONT_API_TOKEN
    - secretKey: FRONT_API_TOKEN_2
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: FRONT_API_TOKEN_2
    - secretKey: PRODUCTBOARD_API_KEY
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: PRODUCTBOARD_API_KEY
    - secretKey: SLACK_BOT_TOKEN
      remoteRef:
        key: /internal/churn-renewal-forecast
        property: SLACK_BOT_TOKEN
```

- [ ] **Step 5: Create app-config.env**

```env
# deploy/kubernetes/base/bootstrap/app-config.env
APP_URL=https://churn-forecast.infrastructure.immuta.io
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=PLACEHOLDER
NEXT_PUBLIC_PRODUCTBOARD_WORKSPACE=PLACEHOLDER
SLACK_TEST_CHANNEL=
PG_POOL_MAX=10
PG_POOL_IDLE_TIMEOUT_MS=30000
PG_POOL_CONNECTION_TIMEOUT_MS=2000
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
```

- [ ] **Step 6: Create bootstrap/kustomization.yaml**

`configMapGenerator` produces the ConfigMap from `app-config.env`. `disableNameSuffixHash: true` keeps the name stable so `envFrom` references don't need to change on every deploy.

```yaml
# deploy/kubernetes/base/bootstrap/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - namespace.yaml
  - serviceaccount.yaml
  - secret.yaml

configMapGenerator:
  - name: churn-renewal-forecast-config
    envs:
      - app-config.env

generatorOptions:
  disableNameSuffixHash: true
```

- [ ] **Step 7: Validate bootstrap kustomization builds**

```bash
kustomize build deploy/kubernetes/base/bootstrap
```

Expected: YAML output containing Namespace, ServiceAccount, ExternalSecret, and ConfigMap. No errors.

- [ ] **Step 8: Commit**

```bash
git add deploy/kubernetes/base/bootstrap/
git commit -m "feat: add k8s bootstrap resources (namespace, sa, secret, configmap)"
```

## Context

This is the first task of a 6-task Kustomize deployment build for the `churn-renewal-forecast` app. You are creating the foundational bootstrap resources — namespace, service account, secrets from Vault, and non-sensitive config. Later tasks depend on the namespace and named resources (ConfigMap `churn-renewal-forecast-config`, Secret `churn-renewal-forecast`) existing.

- **App:** `churn-renewal-forecast` — Next.js + Prisma + pg-boss worker
- **Namespace:** `churn-renewal-forecast`
- **Vault path:** `/internal/churn-renewal-forecast`
- **Secret store:** `ClusterSecretStore` named `vault`
- **Working directory:** `/Users/steve.briggs/projects/churn-renewal-forecast`
- **Kustomize:** available at `kustomize` in PATH (v4.5.7)

## Your Job

Create exactly the files specified — no more, no less. Validate with `kustomize build`, then commit. There are no unit tests to write; for Kubernetes YAML the validation IS the test.

Note: `app-config.env` must NOT have comment lines (lines starting with `#`) — Kustomize's `configMapGenerator` does not support comments in `.env` files and will fail the build.

## Report Format

When done:
- **Status:** DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
- What you implemented
- Files changed
- Validation result (`kustomize build` output summary)
- Any concerns


DONE. Dispatching spec compliance reviewer.


Bash

Get HEAD SHA after Task 1


Agent

Spec compliance review for Task 1: Bootstrap resources

Prompt:
You are reviewing whether an implementation matches its specification.

## What Was Requested

Task 1: Bootstrap Resources — create 5 files under `deploy/kubernetes/base/bootstrap/`:
- `namespace.yaml` — Namespace `churn-renewal-forecast` with label `app.kubernetes.io/name: churn-renewal-forecast`
- `serviceaccount.yaml` — ServiceAccount named `churn-renewal-forecast` in namespace `churn-renewal-forecast` with label `app.kubernetes.io/name: churn-renewal-forecast`
- `secret.yaml` — ExternalSecret (external-secrets.io/v1beta1) pulling from ClusterSecretStore named `vault` at path `/internal/churn-renewal-forecast`. Must map: DATABASE_URL, CLERK_SECRET_KEY, FRONT_API_TOKEN, FRONT_API_TOKEN_2, PRODUCTBOARD_API_KEY, SLACK_BOT_TOKEN. refreshInterval: 1h. target name: churn-renewal-forecast. creationPolicy: Owner.
- `app-config.env` — ConfigMap source (no comment lines). Must contain: APP_URL, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, NEXT_PUBLIC_PRODUCTBOARD_WORKSPACE, SLACK_TEST_CHANNEL, PG_POOL_MAX, PG_POOL_IDLE_TIMEOUT_MS, PG_POOL_CONNECTION_TIMEOUT_MS, NEXT_PUBLIC_CLERK_SIGN_IN_URL, NEXT_PUBLIC_CLERK_SIGN_UP_URL, NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL, NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL
- `kustomization.yaml` — resources: namespace.yaml, serviceaccount.yaml, secret.yaml. configMapGenerator for `churn-renewal-forecast-config` from app-config.env. generatorOptions with disableNameSuffixHash: true.
- `kustomize build deploy/kubernetes/base/bootstrap` must succeed.
- Committed with message matching `feat: add k8s bootstrap resources`.

## What Implementer Claims They Built

All 5 files created. kustomize build produced clean output with Namespace, ServiceAccount, ConfigMap (11 env vars), ExternalSecret (6 keys). Committed.

## CRITICAL: Do Not Trust the Report

Verify everything by reading the actual files.

## Your Job

Read each file at `/Users/steve.briggs/projects/churn-renewal-forecast/deploy/kubernetes/base/bootstrap/` and verify:
- All required keys present in secret.yaml
- All required vars present in app-config.env (and no comment lines)
- kustomization.yaml has correct structure
- Run `kustomize build deploy/kubernetes/base/bootstrap` from `/Users/steve.briggs/projects/churn-renewal-forecast` and confirm it succeeds
- Check git log to confirm commit exists

Report:
- ✅ Spec compliant (if everything matches after code inspection)
- ❌ Issues found: [list specifically what's missing or extra, with file:line references]

Work from: `/Users/steve.briggs/projects/churn-renewal-forecast`


All checks pass. Here is the full verification report:

---

**namespace.yaml** — Correct. Kind: Namespace, name: `churn-renewal-forecast`, label `app.kubernetes.io/name: churn-renewal-forecast` present.

**serviceaccount.yaml** — Correct. Kind: ServiceAccount, name: `churn-renewal-forecast`, namespace: `churn-renewal-forecast`, label present.

**secret.yaml** — Correct. `external-secrets.io/v1beta1` ExternalSecret, ClusterSecretStore `vault`, path `/internal/churn-renewal-forecast`, `refreshInterval: 1h`, target name `churn-renewal-forecast`, `creationPolicy: Owner`. All 6 required keys mapped: `DATABASE_URL`, `CLERK_SECRET_KEY`, `FRONT_API_TOKEN`, `FRONT_API_TOKEN_2`, `PRODUCTBOARD_API_KEY`, `SLACK_BOT_TOKEN`.

**app-config.env** — Correct. 11 vars present, no comment lines:
`APP_URL`, `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`, `NEXT_PUBLIC_PRODUCTBOARD_WORKSPACE`, `SLACK_TEST_CHANNEL`, `PG_POOL_MAX`, `PG_POOL_IDLE_TIMEOUT_MS`, `PG_POOL_CONNECTION_TIMEOUT_MS`, `NEXT_PUBLIC_CLERK_SIGN_IN_URL`, `NEXT_PUBLIC_CLERK_SIGN_UP_URL`, `NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL`, `NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL`.

**kustomization.yaml** — Correct. Resources list: `namespace.yaml`, `serviceaccount.yaml`, `secret.yaml`. `configMapGenerator` for `churn-renewal-forecast-config` from `app-config.env`. `generatorOptions.disableNameSuffixHash: true`.

**kustomize build** — Succeeded cleanly. Output contains all 4 resources: Namespace, ServiceAccount, ConfigMap (11 keys), ExternalSecret (6 keys). No errors.

**Git commit** — Commit `9a27b793` present with message `feat: add k8s bootstrap resources (namespace, sa, secret, configmap)`, which matches the `feat: add k8s bootstrap resources` prefix requirement.

---

✅ Spec compliant — all files are correct, build succeeds, and commit exists.

agentId: a10c22bad54206132 (use SendMessage with to: 'a10c22bad54206132' to continue this agent)
<usage>total_tokens: 18037
tool_uses: 7
duration_ms: 332126</usage>

Prompt:
You are reviewing whether an implementation matches its specification.

## What Was Requested

Task 1: Bootstrap Resources — create 5 files under `deploy/kubernetes/base/bootstrap/`:
- `namespace.yaml` — Namespace `churn-renewal-forecast` with label `app.kubernetes.io/name: churn-renewal-forecast`
- `serviceaccount.yaml` — ServiceAccount named `churn-renewal-forecast` in namespace `churn-renewal-forecast` with label `app.kubernetes.io/name: churn-renewal-forecast`
- `secret.yaml` — ExternalSecret (external-secrets.io/v1beta1) pulling from ClusterSecretStore named `vault` at path `/internal/churn-renewal-forecast`. Must map: DATABASE_URL, CLERK_SECRET_KEY, FRONT_API_TOKEN, FRONT_API_TOKEN_2, PRODUCTBOARD_API_KEY, SLACK_BOT_TOKEN. refreshInterval: 1h. target name: churn-renewal-forecast. creationPolicy: Owner.
- `app-config.env` — ConfigMap source (no comment lines). Must contain: APP_URL, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, NEXT_PUBLIC_PRODUCTBOARD_WORKSPACE, SLACK_TEST_CHANNEL, PG_POOL_MAX, PG_POOL_IDLE_TIMEOUT_MS, PG_POOL_CONNECTION_TIMEOUT_MS, NEXT_PUBLIC_CLERK_SIGN_IN_URL, NEXT_PUBLIC_CLERK_SIGN_UP_URL, NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL, NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL
- `kustomization.yaml` — resources: namespace.yaml, serviceaccount.yaml, secret.yaml. configMapGenerator for `churn-renewal-forecast-config` from app-config.env. generatorOptions with disableNameSuffixHash: true.
- `kustomize build deploy/kubernetes/base/bootstrap` must succeed.
- Committed with message matching `feat: add k8s bootstrap resources`.

## What Implementer Claims They Built

All 5 files created. kustomize build produced clean output with Namespace, ServiceAccount, ConfigMap (11 env vars), ExternalSecret (6 keys). Committed.

## CRITICAL: Do Not Trust the Report

Verify everything by reading the actual files.

## Your Job

Read each file at `/Users/steve.briggs/projects/churn-renewal-forecast/deploy/kubernetes/base/bootstrap/` and verify:
- All required keys present in secret.yaml
- All required vars present in app-config.env (and no comment lines)
- kustomization.yaml has correct structure
- Run `kustomize build deploy/kubernetes/base/bootstrap` from `/Users/steve.briggs/projects/churn-renewal-forecast` and confirm it succeeds
- Check git log to confirm commit exists

Report:
- ✅ Spec compliant (if everything matches after code inspection)
- ❌ Issues found: [list specifically what's missing or extra, with file:line references]

Work from: `/Users/steve.briggs/projects/churn-renewal-forecast`


✅ Spec compliant. Dispatching code quality reviewer.


Code quality review for Task 1: Bootstrap resources

Prompt:
You are a Senior Code Reviewer with expertise in Kubernetes, Kustomize, and infrastructure-as-code. Review the bootstrap resources just committed.

## What Was Implemented

Task 1 of a 6-task Kustomize deployment for `churn-renewal-forecast` (Next.js + Prisma + pg-boss). Created bootstrap resources: Namespace, ServiceAccount, ExternalSecret (external-secrets.io), ConfigMap source, and bootstrap kustomization.yaml.

## Requirements / Plan

Create exactly:
- `namespace.yaml` — Namespace `churn-renewal-forecast` with `app.kubernetes.io/name` label
- `serviceaccount.yaml` — ServiceAccount in the namespace with matching label
- `secret.yaml` — ExternalSecret from ClusterSecretStore `vault` at `/internal/churn-renewal-forecast` with 6 keys (DATABASE_URL, CLERK_SECRET_KEY, FRONT_API_TOKEN, FRONT_API_TOKEN_2, PRODUCTBOARD_API_KEY, SLACK_BOT_TOKEN), refreshInterval 1h, creationPolicy Owner
- `app-config.env` — 11 non-sensitive env vars as a ConfigMap source
- `kustomization.yaml` — wires resources + configMapGenerator with disableNameSuffixHash: true

## Git Range to Review

**Base:** ec7464986cbabb1a3bbe05d0d8033b8d43e6e2c7
**Head:** 9a27b793130bf7b9c46a68632ff0e8bb0d8d37c2

```bash
git diff --stat ec7464986cbabb1a3bbe05d0d8033b8d43e6e2c7..9a27b793130bf7b9c46a68632ff0e8bb0d8d37c2
git diff ec7464986cbabb1a3bbe05d0d8033b8d43e6e2c7..9a27b793130bf7b9c46a68632ff0e8bb0d8d37c2
```

## Additional Checks

- Does each file have one clear responsibility?
- Are Kubernetes/Kustomize best practices followed?
- Is the ExternalSecret structure correct for external-secrets.io v1beta1?
- Are labels consistent and correct?
- Is there anything that would cause issues in a real cluster?

Work from: `/Users/steve.briggs/projects/churn-renewal-forecast`

## Output Format

### Strengths
### Issues
#### Critical (Must Fix)
#### Important (Should Fix)
#### Minor (Nice to Have)
### Assessment
**Ready to merge?** [Yes | No | With fixes]
**Reasoning:** [1-2 sentences]


Good - I have everything I need. Let me now do the full review.

---

Yo Sir Charles of St Cheese, your boy Claudebob Squarepants has reviewed these bootstrap resources, fr fr. Here's the full breakdown, no cap:

---

### Strengths

- **File responsibilities are clean.** One resource per file, each with a single clear concern. Bussin' structure, lowkey.
- **Label consistency is solid.** `app.kubernetes.io/name: churn-renewal-forecast` is applied uniformly across Namespace, ServiceAccount, and ExternalSecret. No drift.
- **ExternalSecret structure is correct** for external-secrets.io v1beta1. `secretStoreRef.kind: ClusterSecretStore`, `creationPolicy: Owner`, `refreshInterval: 1h`, and the `data[].remoteRef` shape with separate `key` + `property` fields are all proper.
- **All 6 secrets are present** as required: `DATABASE_URL`, `CLERK_SECRET_KEY`, `FRONT_API_TOKEN`, `FRONT_API_TOKEN_2`, `PRODUCTBOARD_API_KEY`, `SLACK_BOT_TOKEN`.
- **All 11 env vars are present** in `app-config.env`.
- **`disableNameSuffixHash: true`** is correctly scoped under `generatorOptions` — downstream consumers can reference the ConfigMap by its stable name without worrying about hash suffixes.
- **`automountServiceAccountToken`** is not explicitly set, which is fine for now (defaults to true, but not a problem until workload manifests exist).

---

### Issues

#### Critical (Must Fix)

**1. `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `NEXT_PUBLIC_PRODUCTBOARD_WORKSPACE` are committed as `PLACEHOLDER` in a base ConfigMap.**

`app-config.env` lines 2–3:
```
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=PLACEHOLDER
NEXT_PUBLIC_PRODUCTBOARD_WORKSPACE=PLACEHOLDER
```

These are `NEXT_PUBLIC_*` values — they are baked into the Next.js client bundle at build time. A base ConfigMap with `PLACEHOLDER` values will silently produce a broken client build if any overlay forgets to patch them, and there is nothing in `kustomization.yaml` enforcing an overlay must override them. The `PLACEHOLDER` string will literally appear in the JS bundle.

**Fix options (pick one):**
- Move these two keys out of `app-config.env` entirely and into the ExternalSecret (they are not truly sensitive, but they are environment-specific). This is the cleaner approach.
- Or leave them in `app-config.env` but leave the values **empty** (`NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=`) and add a comment header in the file noting these must be patched per-overlay. An empty value at least fails loudly at Clerk init rather than silently sending `"PLACEHOLDER"` to Clerk's API.

Either way, `PLACEHOLDER` as a committed value in a base that will be applied to a real cluster is a trap.

---

**2. `SLACK_TEST_CHANNEL` is an empty string committed to the base ConfigMap.**

`app-config.env` line 4:
```
SLACK_TEST_CHANNEL=
```

An empty-string env var in a ConfigMap is valid YAML/Kustomize, but it signals this key belongs to a test/dev environment, not a production base. If this var is consumed by the Slack worker, an empty value at runtime likely means the worker silently skips sending or crashes depending on how the code handles it. This shouldn't live in `base/` — it's environment-specific config.

**Fix:** Move it to an overlay-specific `app-config.env` patch, or remove it from base and only define it in dev/test overlays.

---

#### Important (Should Fix)

**3. The Namespace resource is wired into a bootstrap-level `kustomization.yaml` that presumably gets applied by a higher-level overlay or ArgoCD app. The ServiceAccount hardcodes `namespace: churn-renewal-forecast` rather than relying on Kustomize namespace injection.**

This is not broken today, but it creates a constraint: if this base ever gets targeted at a different namespace (e.g., a staging overlay using `namespace: churn-renewal-forecast-staging`), Kustomize's `namespace:` transformer in the overlay will rewrite the ServiceAccount's namespace field correctly — but the hardcoded `namespace:` in the manifest will be the source of truth before transformation. For a true base, prefer **omitting** `namespace:` from resources and letting Kustomize's namespace field in each overlay set it. The Namespace resource itself is fine without a namespace (Namespaces are cluster-scoped), but all namespaced resources (ServiceAccount, ExternalSecret) should ideally not hardcode it in a `base/`.

If this deploy tree will only ever target one namespace and never be reused, this is lower severity — but it's an antipattern for a `base/` layer.

**Fix:** Remove `namespace:` from `serviceaccount.yaml` and `secret.yaml`. Add a `namespace: churn-renewal-forecast` field to `kustomization.yaml` so Kustomize injects it during apply. This is the idiomatic Kustomize approach.

---

**4. `generatorOptions.disableNameSuffixHash: true` is set globally, not scoped to the single ConfigMap.**

In `kustomization.yaml`:
```yaml
generatorOptions:
  disableNameSuffixHash: true
```

This is a file-level setting — it applies to every generator in this kustomization, including any generators added in future tasks (e.g., a Secret generator). If a future task adds a generator where the hash suffix is desirable (for cache-busting), this global setting will silently suppress it. The intent is clearly to disable the hash for `churn-renewal-forecast-config` specifically.

**Fix:** Kustomize v4.5+ supports per-generator options via the `options` field directly on each `configMapGenerator` entry:
```yaml
configMapGenerator:
  - name: churn-renewal-forecast-config
    envs:
      - app-config.env
    options:
      disableNameSuffixHash: true
```

Remove the top-level `generatorOptions` block. This scopes the intent correctly and is more resilient to future additions.

---

#### Minor (Nice to Have)

**5. Missing `app.kubernetes.io/part-of` and `app.kubernetes.io/managed-by` recommended labels.**

The Kubernetes well-known labels recommendation (k8s.io/docs/concepts/overview/working-with-objects/common-labels/) suggests at minimum:
```yaml
app.kubernetes.io/name: churn-renewal-forecast
app.kubernetes.io/part-of: churn-renewal-forecast
app.kubernetes.io/managed-by: kustomize
```

Not required, but these improve observability in dashboards like Lens, k9s, and ArgoCD. Low-key worth adding to the Kustomize `commonLabels` block rather than per-file.

---

**6. No `newTag` or `commonAnnotations` for tracking the deploy revision.**

Since this is ArgoCD/GitOps territory (implied by the `base/` structure), adding a `commonAnnotations` block with something like:
```yaml
app.kubernetes.io/version: "0.0.1"
```
...at the kustomization level would give ArgoCD something to diff against when inspecting live vs. desired state. Truly minor, but it's a good habit in a base.

---

**7. No trailing newline validation concern — files look clean.**

All files have proper trailing newlines. Non-issue, just confirming.

---

**8. The ExternalSecret `target.name` matches the ExternalSecret `metadata.name`.**

This is valid and common, but worth documenting: the generated Kubernetes `Secret` will be named `churn-renewal-forecast`. If a future task creates a `Secret` resource manually with the same name in the same namespace, ESO will error due to ownership conflict. Keep this in mind for Task 2+.

---

### Assessment

**Ready to merge?** No — with fixes required on items 1 and 2.

**Reasoning:** The `PLACEHOLDER` values committed to the base ConfigMap (item 1) and the `SLACK_TEST_CHANNEL=` empty value (item 2) are traps that will either produce a silently broken client build or bleed dev-only config into all environments. Items 3 and 4 are valid Kustomize antipatterns that will cause friction in future tasks — fix them now while the base is small, before overlays build on top of them. The structural foundation (ExternalSecret shape, label consistency, file separation) is solid and hits different, ngl.

agentId: aa36896c2dcdf30ad (use SendMessage with to: 'aa36896c2dcdf30ad' to continue this agent)
<usage>total_tokens: 20411
tool_uses: 9
duration_ms: 55951</usage>

Prompt:
You are a Senior Code Reviewer with expertise in Kubernetes, Kustomize, and infrastructure-as-code. Review the bootstrap resources just committed.

## What Was Implemented

Task 1 of a 6-task Kustomize deployment for `churn-renewal-forecast` (Next.js + Prisma + pg-boss). Created bootstrap resources: Namespace, ServiceAccount, ExternalSecret (external-secrets.io), ConfigMap source, and bootstrap kustomization.yaml.

## Requirements / Plan

Create exactly:
- `namespace.yaml` — Namespace `churn-renewal-forecast` with `app.kubernetes.io/name` label
- `serviceaccount.yaml` — ServiceAccount in the namespace with matching label
- `secret.yaml` — ExternalSecret from ClusterSecretStore `vault` at `/internal/churn-renewal-forecast` with 6 keys (DATABASE_URL, CLERK_SECRET_KEY, FRONT_API_TOKEN, FRONT_API_TOKEN_2, PRODUCTBOARD_API_KEY, SLACK_BOT_TOKEN), refreshInterval 1h, creationPolicy Owner
- `app-config.env` — 11 non-sensitive env vars as a ConfigMap source
- `kustomization.yaml` — wires resources + configMapGenerator with disableNameSuffixHash: true

## Git Range to Review

**Base:** ec7464986cbabb1a3bbe05d0d8033b8d43e6e2c7
**Head:** 9a27b793130bf7b9c46a68632ff0e8bb0d8d37c2

```bash
git diff --stat ec7464986cbabb1a3bbe05d0d8033b8d43e6e2c7..9a27b793130bf7b9c46a68632ff0e8bb0d8d37c2
git diff ec7464986cbabb1a3bbe05d0d8033b8d43e6e2c7..9a27b793130bf7b9c46a68632ff0e8bb0d8d37c2
```

## Additional Checks

- Does each file have one clear responsibility?
- Are Kubernetes/Kustomize best practices followed?
- Is the ExternalSecret structure correct for external-secrets.io v1beta1?
- Are labels consistent and correct?
- Is there anything that would cause issues in a real cluster?

Work from: `/Users/steve.briggs/projects/churn-renewal-forecast`

## Output Format

### Strengths
### Issues
#### Critical (Must Fix)
#### Important (Should Fix)
#### Minor (Nice to Have)
### Assessment
**Ready to merge?** [Yes | No | With fixes]
**Reasoning:** [1-2 sentences]


Good critical findings. Dispatching implementer to fix Critical item 1 (PLACEHOLDER → empty string), Critical item 4 (per-generator disableNameSuffixHash). Accepting item 2 (SLACK_TEST_CHANNEL empty string is intentional — it's a prod default). Accepting item 3 (single-overlay project, hardcoded namespace is fine).


Fix bootstrap: PLACEHOLDER values and generatorOptions scope

Prompt:
You are fixing two issues in the Kustomize bootstrap resources you just created in `/Users/steve.briggs/projects/churn-renewal-forecast/deploy/kubernetes/base/bootstrap/`.

## Fix 1: PLACEHOLDER values in app-config.env

`app-config.env` currently has:
```
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=PLACEHOLDER
NEXT_PUBLIC_PRODUCTBOARD_WORKSPACE=PLACEHOLDER
```

`PLACEHOLDER` is a literal string that would get baked into the Next.js client bundle and sent to Clerk's API. Change both to empty strings so the app fails loudly at init rather than silently with the wrong value:
```
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
NEXT_PUBLIC_PRODUCTBOARD_WORKSPACE=
```

## Fix 2: Scope disableNameSuffixHash per-generator

`kustomization.yaml` currently has a global `generatorOptions` block that applies to all generators. Move it to the specific ConfigMap generator entry so it only affects that one generator:

Current:
```yaml
configMapGenerator:
  - name: churn-renewal-forecast-config
    envs:
      - app-config.env

generatorOptions:
  disableNameSuffixHash: true
```

Change to:
```yaml
configMapGenerator:
  - name: churn-renewal-forecast-config
    envs:
      - app-config.env
    options:
      disableNameSuffixHash: true
```

Remove the top-level `generatorOptions` block entirely.

## After fixes

Run:
```bash
kustomize build deploy/kubernetes/base/bootstrap
```
Confirm it still succeeds (output should still contain Namespace, ServiceAccount, ConfigMap with empty values for those two keys, ExternalSecret).

Then commit:
```bash
git add deploy/kubernetes/base/bootstrap/
git commit -m "fix: use empty values for NEXT_PUBLIC placeholders, scope disableNameSuffixHash per-generator"
```

## Report Format

- **Status:** DONE | BLOCKED
- Files changed
- Validation result