Review (R2): P0 pal-e-services tf state drifted — prod postgres in blast radius

review-1064-2026-04-20-r2 Review

review approved

Verdict: APPROVED

Round 2 re-review of board item #1064 / forgejo_admin/pal-e-platform#297. Round 1 verdict was NEEDS_REFINEMENT (see review-1064-2026-04-20) with 1 [LABEL] fix and 9 [BODY] fixes. All 10 items verified resolved in the current ticket body and board metadata. No new issues surfaced during re-review.
Decomposition is the next step per round 1 [DECOMPOSE] recommendation, but per the skill, decomposition happens post-approval and is NOT part of this re-review.

Round 1 Fix Verification

# Round 1 Item Type Status Evidence in current ticket
0 Story label key fix: <code>story:superuser-deploy</code> [LABEL] RESOLVED Board item #1064 labels: <code>type:bug,scope:unplanned,severity:p0,story:superuser-deploy,arch:pal-e-services</code>. Story verified on <code>project-pal-e-platform</code> user-stories table (success metric: "<code>tofu plan/apply</code> succeeds without manual intervention" — exact match for what's broken).
1 Phase reference: <code>phase-pal-e-platform-28-keycloak-smtp</code> (not the bad slug); <code>phase-platform-17b-tf-state-governance</code> in Related [BODY] RESOLVED "Why this is P0" #3 cites Phase 28 AC; Related section lists <code>phase-pal-e-platform-28-keycloak-smtp</code> AND <code>phase-platform-17b-tf-state-governance</code> with the standalone-vs-absorption decision called out inline.
2 Execution Repos subsection clarifies pal-e-platform (SOP) vs pal-e-services (code) [BODY] RESOLVED ### Repo section now contains "**Execution Repos:**" subsection naming both repos with their roles and example branch naming (<code>297-tf-drift-cnpg-import</code> on services, <code>297-tf-drift-sop-update</code> on platform).
3 AC #1/#2 reference pal-e-docs note <code>drift-investigation-2026-04-20</code> [BODY] RESOLVED AC #1: "A pal-e-docs note <code>drift-investigation-2026-04-20</code> (note_type: <code>doc</code>, tags: <code>drift,investigation</code>) is published with a per-resource decision table". AC #2: "Per-resource UPDATE decisions recorded in same note." Location is now explicit and unambiguous.
4 AC #3 requires one-at-a-time import with plan gate per resource [BODY] RESOLVED AC #3: "Import plan executed one resource at a time, with verification gate. After EACH <code>tofu import</code>, run <code>tofu plan</code> and confirm the imported resource shows zero diff OR only operator-managed-field drift before moving to the next import. CNPG cluster import goes FIRST and ALONE."
5 "No pod restart on <code>pal-e-postgres-1</code>" AC with sop-postgres-restore dry-run gate [BODY] RESOLVED AC #4: "No pod restart on <code>pal-e-postgres-1</code> during reconciliation. If any step would cause restart, pause and confirm backup restorability via <code>sop-postgres-restore</code> dry-run BEFORE proceeding. This is a hard gate."
6 AC #5 split: BOTH service-onboarding-sop AND sop-platform-tf-changes updates [BODY] RESOLVED Now two separate ACs: "<code>service-onboarding-sop</code> updated. Add a new 'Plan-diff check' row to the Pre-Deploy Validation Checklist..." AND "<code>sop-platform-tf-changes</code> updated. Add a new bullet under 'What NOT to Do'..." Both required, not "or".
7 "Why this is P0" names CNPG operator reconcile semantics as distinct item [BODY] RESOLVED "Why this is P0" #2: "**CNPG operator reconcile semantics are non-trivial.**" Explicitly distinguishes <code>kubernetes_manifest</code> against operator-reconciled CRD from plain k8s import semantics. References cnpg.tf:58-60 header comment. Names the "import worked but we now have a forever-drift loop" risk explicitly.
8 Interim Safety Protocol explicitly says "supersedes <code>sop-platform-tf-changes</code> until zero-diff restored" [BODY] RESOLVED Interim Safety Protocol opens with: "**This protocol supersedes <code>sop-platform-tf-changes</code> for <code>pal-e-services</code> until the zero-diff gate is restored.** <code>sop-platform-tf-changes</code> defines pal-e-services as plan-and-apply-before-merge — DO NOT follow that pattern until this ticket closes."
9 woodpecker-db CNPG cluster flagged in Environment as out-of-scope-pending-verification [BODY] RESOLVED Environment section closes with: "**Sibling cluster to verify is OUT of scope:** <code>clusters.postgresql.cnpg.io/woodpecker-db</code> (in <code>woodpecker</code> ns, 37d old, healthy). Likely managed by pal-e-platform (not pal-e-services). Investigation must confirm this is NOT in the pal-e-services drift list before proceeding."

Scope Question Resolution

Round 1 [SCOPE] item asked Ava: "Does this ticket absorb phase-platform-17b-tf-state-governance, or does 17b stay separate?" The current ticket Related section answers inline: "Likely standalone — 17b's primary scope is remote backend migration, this is import/reconciliation." Documented in body. No further action needed pre-decomposition.

Template Completeness (Re-verified)

  • [x] Type (Bug)
  • [x] Lineage
  • [x] Repo (with Execution Repos subsection — NEW in r2)
  • [x] What Broke (per-resource table)
  • [x] Why This Is P0 (4 items now, was 3 — CNPG operator semantics added as #2)
  • [x] Repro Steps
  • [x] Expected Behavior
  • [x] Environment (with woodpecker-db out-of-scope flag — NEW in r2)
  • [x] Acceptance Criteria (8 items now, was 6 — postgres-restart AC + SOP split)
  • [x] Scope Boundary
  • [x] Interim Safety Protocol (with supersedes clause — NEW in r2)
  • [x] Related (with phase-17b standalone decision documented)

Traceability (Re-verified)

  • [x] story:superuser-deploy label — verified in project-pal-e-platform user-stories table
  • [x] arch:pal-e-services label — pal-e-services is documented as foundational bootstrap repo in project-page architecture; standalone arch note not required (per round 1 finding, unchanged)
  • [x] Forgejo issue #297 — open

File Targets

No new file paths were introduced in r2. All r1-verified paths remain accurate (cnpg.tf:62, cnpg.tf:158, services.tf:21/94/123, k3s.tfvars). Live cluster claims (pal-e-postgres CNPG cluster, pal-e-mail ArgoCD app, etc.) remain accurate per r1 verification.

Repo Placement

Resolved by r1 fix #2: ticket now explicitly names BOTH execution repos with their roles. A dev reading this ticket will not open a branch in the wrong repo.

Dependencies

Unchanged from r1: blocks pal-e-services#58/#59; scope-adjacent to phase-platform-17b-tf-state-governance (resolved as standalone). Interim Safety Protocol's relationship to sop-platform-tf-changes now explicit (supersedes).

Acceptance Criteria

All r1 AC concerns resolved:
  • AC #1/#2 location ambiguity — fixed (drift-investigation-2026-04-20 note)
  • AC #3 dry-run gate — fixed (per-resource verification, CNPG first and alone)
  • AC #5 ambiguity — fixed (split into two explicit ACs)
  • Missing postgres-restart AC — added (AC #4)
The 8 ACs are now per-resource, testable, and have explicit deliverable locations. Strong.

Blast Radius

All r1 blast-radius concerns resolved in body:
  • CNPG operator reconcile semantics — now a distinct P0 item
  • woodpecker-db sibling cluster — flagged in Environment as out-of-scope-pending-verification
  • Interim Safety Protocol supersedes — explicit
No new blast-radius concerns surfaced in r2.

Decomposition Assessment

Decomposition need was confirmed in r1 (>5 min agent work, 6 ACs, 2 repos, CNPG import is non-trivial). The r2 body now has 8 ACs, which strengthens the decomposition case. Decomposition is NOT part of this re-review per the skill — re-review verifies refinement only. Post-approval next step: caller routes #1064 to skill-decompose-ticket with the 6 sub-ticket recommendations from review-1064-2026-04-20.

Recommendation

APPROVED. All round 1 [LABEL] and [BODY] items resolved. No new issues. Ready for the post-approval pipeline:
  • Caller invokes skill-decompose-ticket on board item #1064 to create child board (board-1064-tf-drift-reconcile or similar) per round 1 decomposition recommendation (6 sub-tickets).
  • Parent #1064 stays on board-pal-e-platform as the P0 tracker; sub-tickets execute on the child board.
  • Per feedback_kanban_column_flow, parent #1064 can advance from backlog → todo now that review is APPROVED. Per feedback_todo_means_reviewed, the todo column is the right home until decomposition completes.
No further refinement needed.