Skip to content

feat: plan-centric reconcile foundations (Steps 1-4)#87

Merged
bdchatham merged 3 commits intomainfrom
feat/plan-centric-reconcile-foundations
Apr 15, 2026
Merged

feat: plan-centric reconcile foundations (Steps 1-4)#87
bdchatham merged 3 commits intomainfrom
feat/plan-centric-reconcile-foundations

Conversation

@bdchatham
Copy link
Copy Markdown
Collaborator

@bdchatham bdchatham commented Apr 15, 2026

Summary

Lays the groundwork for making Plan the central reconciliation abstraction across all SeiNode phases. This PR contains the non-behavioral half — all new code is additive and existing controller behavior is unchanged.

  • CRD: Add TargetPhase/FailedPhase to TaskPlan so the executor can drive phase transitions atomically with plan completion/failure
  • noderesource package: Extract StatefulSet, Service, and PVC generators from the controller into internal/noderesource/, breaking the import cycle needed for Step 3
  • New tasks: ensure-data-pvc, apply-statefulset, apply-service — fire-and-forget controller-side tasks registered in the task registry (not yet wired into plans)
  • Executor: Handles TargetPhase/FailedPhase on plan terminal states, nils convergence plans (target == current phase), adds OnPlanComplete/OnPlanFailed callbacks

What's next (separate PR)

Steps 5-6 will wire these foundations into the actual reconcile loop:

  • Phase-aware BuildPlan (init plans include infrastructure tasks, Running phase gets convergence plans)
  • Mutating ForNode that stamps plans directly onto node.Status.Plan
  • Simplified reconcile loop: ForNode → ExecutePlan

Test plan

  • make test — all existing tests pass
  • make lint — zero issues
  • make build — compiles cleanly
  • make manifests generate — CRDs regenerated
  • Review: executor TargetPhase/FailedPhase handling in executor.go
  • Review: new task implementations follow existing patterns (compare with bootstrap_job.go)
  • Review: noderesource extraction preserves all behavior (95.5% coverage)

🤖 Generated with Claude Code

Lay the groundwork for making Plan the central reconciliation abstraction
across all SeiNode phases. This is the non-behavioral half of the refactor
— all new code is additive and existing behavior is unchanged.

Step 1: Add TargetPhase/FailedPhase to TaskPlan CRD
- Executor sets node phase atomically with plan completion/failure
- Both fields are optional; empty means no transition (backward compatible)

Step 2: Extract resource generators to internal/noderesource/
- Moves StatefulSet, Service, PVC generation out of the controller package
- Breaks the import cycle that would exist between task and controller
- Controller now calls noderesource.* directly (no delegate wrappers)

Step 3: New controller-side tasks (ensure-data-pvc, apply-statefulset, apply-service)
- Fire-and-forget tasks that complete synchronously in Execute
- Registered in the task registry, not yet wired into any plan

Step 4: Executor handles TargetPhase and convergence plan cleanup
- On plan completion: applies TargetPhase to node status in same patch
- On plan failure: applies FailedPhase to node status in same patch
- Convergence plans (target == current phase): nils the plan on completion
- OnPlanComplete/OnPlanFailed callbacks for metrics/events

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread internal/planner/executor.go Outdated
Comment thread internal/planner/executor.go Outdated
bdchatham and others added 2 commits April 15, 2026 14:41
Remove PhaseTransition struct and OnPlanComplete/OnPlanFailed callbacks.
The executor directly owns phase transitions via TargetPhase/FailedPhase
without needing callback indirection.

On plan failure, set a PlanFailed Condition on the node with the failed
task type as the Reason and a message describing what went wrong. This
follows the standard K8s Conditions pattern already used by monitor tasks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two fixes from expert review:

1. PlanFailed Condition Reason now uses "TaskFailed" (CamelCase) instead
   of the kebab-case task type string. K8s convention requires Reasons to
   be CamelCase machine-readable tokens. The task type is already in the
   Message field.

2. nilPlanIfConvergence now captures the pre-mutation phase and compares
   against it, so only convergence plans (same phase before and after) get
   nilled. Init plans (Initializing → Running) keep their completed plan
   visible in status for observability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bdchatham bdchatham merged commit b93d623 into main Apr 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant