Rewrite/reconcile manager - #337
Open
Tanker2020 wants to merge 34 commits into
Open
Conversation
- Node: name, optional NodeFunc, directed edges to upstream deps - Edge: carries optional EdgeFunc to gate dependent start - ResourceNode: embeds Node, adds Manifest/VerifyFunc/DeployMethod - Graph: root-anchored container; AddNode, AddDependency, Topology - Cycle detection on every AddChild call (DFS reachability) - Topology() returns DFS post-order (dependency-first deploy order) Files: rewrite/dag/node.go, rewrite/dag/graph.go Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
- CompletionState: Verified/Unverified/Failed/Unstarted node buckets
- DeployCompleted(), VerifyCompleted(), AnyFailed() predicates
- HaltError{Fatal bool}: returned by NodeFunc to signal runner halt
Fatal=true → node lands in Failed, downstreams become Unstarted
Fatal=false → node lands in Unverified (deployed, not yet ready)
Files: rewrite/dag/completion_state.go
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Python used ThreadPoolExecutor + time.sleep(0.05) busy-poll loop. Go port uses goroutines + buffered results channel; scheduler blocks on select — zero CPU busy-polling. - NewRunner(graph, opts...) with functional options - WithConcurrency(0): serial topology walk, no goroutines (dry-run/test) - WithConcurrency(n): semaphore-capped parallel execution - WithVerifyUpstream(bool): gate dependent start on EdgeFunc result - context.Context cancellation: drains in-flight, marks rest Unstarted - Independent graph branches continue executing after sibling failure (matches Python oper8 intended behaviour; Python had a bug where the serial loop broke early on fatalErr) - stateMap protected by sync.Mutex; scheduler is single writer Files: rewrite/dag/runner.go Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
23 test cases covering:
Graph/Node: empty graph, duplicate node, empty name, cycle detection,
self-loop, topology order, String()
Runner serial: all succeed, empty graph, fatal halt (independent branch
still runs), unverified halt, disabled node, execution order
Runner concurrent: all succeed, fatal halt, independent nodes verified
parallel via start-time spread, race detector stress test
(20 nodes, atomic counter), context cancellation
EdgeFunc: blocks dependent when returns false, allows when true
CompletionState: all predicate combinations
ResourceNode: construction and field access
Concurrency test uses start-time recording rather than wall-clock
total elapsed — CI-safe on slow runners.
Files: rewrite/dag/runner_test.go
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
- Matrix: Go 1.22 and 1.23 - go test -race -count=1 -timeout=60s ./dag/... - go build ./... and go vet ./dag/... - golangci-lint on dag/ package - Triggered on push to rewrite/DAG_Runner and PRs targeting main - working-directory: rewrite (module root) Files: ./.github/workflows/pr1-dag-runner.yml Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Defines the core abstraction all cluster interactions go through. Python (bool, bool) return tuples → Go (changed bool, err error): - success bool dropped; errors are returned as error values - callers use idiomatic `if err != nil` instead of checking two booleans watch_objects Python generator → Go channel: - Watch() returns <-chan WatchEvent; caller ranges over it - Cancelled via context.Context; channel is closed on cancel New types vs Python: - ListOptions struct (replaces positional label_selector/field_selector args) - EventType string constants (ADDED/MODIFIED/DELETED) - WatchEvent struct with Timestamp Files: rewrite/deploymanager/deploymanager.go Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Ports deploy_manager/owner_references.py. - OwnerRef(ownerCR) builds a single ownerReference map entry - ApplyOwnerRef(owner, child) stamps the reference onto child.metadata - No-op when owner == child (same UID) - No-op for cross-namespace references (K8s does not support them) - Idempotent: will not add duplicate entries - blockOwnerDeletion: true; controller field intentionally omitted (matches Python behaviour and StackOverflow rationale in source) Files: rewrite/deploymanager/ownerref.go Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Ports deploy_manager/dry_run_deploy_manager.py. Primary use: unit-testing controllers without a live cluster. Key differences from Python: - Python used nested defaultdict; Go uses typed clusterStore (map[ns][kind][apiVersion][name] → object) - Python RLock on class level; Go sync.RWMutex per instance - Python watch callbacks were registered functions; Go uses buffered channels — consumers range over the channel, cancel via context - Watch channel is closed when ctx is cancelled (no explicit Unregister) - deepCopy via JSON marshal/unmarshal (simple, correct for map[string]any) - matchSelector implements = == != existence operators (sufficient for dry-run tests; full set-based selector is future work) Extra test helpers (not in Python): - GetStored(ns, kind, av, name) — direct store access for assertions - ObjectCount() — total objects in store Files: rewrite/deploymanager/dryrun.go Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
19 test cases covering:
Deploy: create, idempotent re-deploy, field update, owner ref stamping
Get: not found returns nil, found returns deep copy (mutation check)
Delete: existing object, non-existent no-op
List: all objects, label selector filtering
SetStatus: sets status, returns changed=true; error on missing object
Watch: receives ADDED on deploy, DELETED on delete, channel closes
on context cancel (race-detector safe)
OwnerRef: stamps reference, idempotent, cross-namespace skipped
Files: rewrite/deploymanager/dryrun_test.go
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
- Matrix: Go 1.22 and 1.23 - go test -race -count=1 -timeout=60s ./deploymanager/... - go build ./... and go vet ./deploymanager/... - golangci-lint on deploymanager/ package - Triggered on push to rewrite/Deploy_Manager and PRs targeting main Files: .github/workflows/pr2-deploy-manager.yml Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…licationStatus Ports oper8 Python status.py to Go. Reason types: ReadyReason, UpdatingReason, ServiceStatus string constants. MakeApplicationStatus(Options) builds a complete status map: - Ready + Updating conditions from reason/message pairs - External conditions preserved alongside oper8-managed ones - ComponentStatus block from dag.CompletionState (sorted node names) - versions.reconciled / versions.available.versions (IBM CloudPak paths) - <kind>Status field (e.g. customerStatus) when Kind is set UpdateApplicationStatus merges new Options onto existing status, carrying forward current reasons and external conditions when not overridden. GetCondition, GetVersion, StatusChanged helper functions included. Python translation notes: - deepdiff library dropped; StatusChanged uses recursive JSON comparison after stripping lastTransactionTime keys — zero external dependencies - **kwargs replaced by Options struct (compile-time field checking) - aconfig nested_set/nested_get replaced by nestedSet/nestedGet dot-path helpers Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
MakeApplicationStatus: Ready/Updating condition status values for all reason combinations, empty options, external conditions, version fields, componentStatus deployed/verified counts and dependencyGraph, IBM CloudPak <kind>Status (Completed/Failed/InProgress/custom preserved) UpdateApplicationStatus: preserves existing reasons when not overridden, overrides when provided, preserves external conditions and top-level fields StatusChanged: same content + different timestamps not changed, different reason changed, nil inputs, added field GetCondition, GetVersion: found/missing cases UpdatingReason active/inactive matrix (all 6 reasons) ComponentStatus node names sorted alphabetically Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Matrix Go 1.22 and 1.23, race detector, golangci-lint v1.64.8 Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…load config Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…ondition Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…rity tests Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…r, ReconcileManager
Ports oper8 Python session.py / component.py / controller.py /
rollout_manager.py / reconcile.py to Go.
New packages
rewrite/session/ per-reconcile context (CR manifest, DAG, status)
rewrite/component/ Component interface (Setup / Deploy / Verify)
rewrite/controller/ Controller interface + BaseController no-op embed
rewrite/rolloutmanager/ 4-phase loop: deploy→after_deploy→verify→after_verify
rewrite/reconcilemanager/ top-level orchestrator: ID gen, session init,
preconditions, rollout, status writes, finalizers
dag/node.go Node.SetFunc / SetData / Data (needed by RolloutManager)
dag/runner.go Runner.CompletionState(); remove unused inFlight int64 field
gofmt dag/runner_test.go, deploymanager/dryrun.go
reconcilemanager tests (9 cases, -race):
EmptyGraph, SingleComponentVerified, SetupError, DeployError,
VerifyNotReady, Precondition, TwoComponentsOrdered, InvalidCR, Finalizer
CI: .github/workflows/pr5-reconcile.yml — go test -race ./... + golangci-lint
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
dag/runner.go: Remove unused struct field — this was the field golangci-lint flagged. The concurrent scheduler uses a local variable inside runConcurrent; the struct field was never read or written and should never have been there. session/session_test.go: 21 tests (lost in branch switch, recreated) controller/controller_test.go: 14 tests (lost in branch switch, recreated) rolloutmanager/rolloutmanager_test.go: 18 tests (lost in branch switch, recreated) Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…0:00) nishanthk ttys002 Thu Jul 30 11:30 - 11:30 (00:00) nishanthk ttys002 Thu Jul 30 11:30 - 11:30 (00:00) nishanthk ttys002 Thu Jul 30 11:30 - 11:30 (00:00) nishanthk ttys002 Thu Jul 30 11:30 - 11:30 (00:00) nishanthk ttys002 Thu Jul 30 11:29 - 11:29 (00:00) nishanthk ttys002 Thu Jul 30 11:29 - 11:29 (00:00) nishanthk ttys002 Thu Jul 30 11:28 - 11:28 (00:00) nishanthk ttys002 Thu Jul 30 11:23 - 11:23 (00:00) nishanthk ttys001 Thu Jul 30 11:19 still logged in nishanthk ttys000 Thu Jul 30 11:19 still logged in nishanthk console Thu Jul 30 11:19 still logged in reboot time Thu Jul 30 11:00 shutdown time Thu Jul 30 10:59 nishanthk ttys001 Fri Jul 24 15:26 - 15:26 (00:00) nishanthk ttys000 Fri Jul 24 15:26 - 15:26 (00:00) nishanthk console Fri Jul 24 15:26 - 10:59 (5+19:33) reboot time Fri Jul 24 15:25 shutdown time Fri Jul 24 15:24 nishanthk ttys001 Wed Jul 22 00:37 - 00:37 (00:00) nishanthk ttys000 Wed Jul 22 00:37 - 00:37 (00:00) nishanthk console Wed Jul 22 00:37 - 15:24 (2+14:47) reboot time Wed Jul 22 00:35 shutdown time Wed Jul 22 00:31 root console Wed Jul 22 00:30 - shutdown (00:00) nishanthk ttys001 Fri Jul 17 16:28 - 16:28 (00:00) nishanthk ttys001 Thu Jul 9 13:25 - 13:25 (00:00) nishanthk ttys001 Mon Jul 6 14:14 - 14:14 (00:00) nishanthk ttys005 Mon Jul 6 14:14 - 14:14 (00:00) nishanthk ttys006 Mon Jul 6 14:14 - 14:14 (00:00) nishanthk ttys003 Mon Jun 29 14:47 - 14:47 (00:00) nishanthk ttys002 Mon Jun 29 14:47 - 14:47 (00:00) nishanthk ttys001 Mon Jun 29 14:47 - 14:47 (00:00) nishanthk ttys006 Mon Jun 29 14:21 - 14:21 (00:00) nishanthk ttys006 Mon Jun 29 14:21 - 14:21 (00:00) nishanthk ttys003 Mon Jun 29 13:36 - 13:36 (00:00) nishanthk ttys005 Mon Jun 22 02:32 - 02:32 (00:00) nishanthk ttys004 Mon Jun 22 02:30 - 02:30 (00:00) nishanthk ttys003 Mon Jun 22 01:16 - 01:16 (00:00) nishanthk ttys002 Thu Jun 18 16:23 - 16:23 (00:00) nishanthk ttys001 Thu Jun 18 16:23 - 16:23 (00:00) nishanthk ttys000 Thu Jun 18 16:23 - 16:23 (00:00) nishanthk console Thu Jun 18 16:23 - 00:30 (33+08:07) reboot time Thu Jun 18 16:23 nishanthk ttys002 Wed Jun 17 12:32 - crash (1+03:50) nishanthk ttys001 Mon Jun 15 13:30 - crash (3+02:52) nishanthk ttys000 Mon Jun 15 13:30 - crash (3+02:53) nishanthk ttys003 Mon Jun 15 13:17 - 13:17 (00:00) nishanthk ttys003 Mon Jun 15 13:17 - 13:17 (00:00) nishanthk ttys002 Mon Jun 15 13:16 - 13:16 (00:00) nishanthk ttys003 Mon Jun 15 13:14 - 13:14 (00:00) nishanthk ttys002 Mon Jun 15 13:12 - 13:12 (00:00) nishanthk ttys002 Mon Jun 15 13:11 - 13:11 (00:00) nishanthk ttys003 Mon Jun 15 13:07 - 13:07 (00:00) nishanthk ttys002 Fri Jun 12 13:02 - 13:02 (00:00) nishanthk ttys002 Thu Jun 11 12:44 - 12:44 (00:00) nishanthk ttys004 Mon Jun 8 13:56 - 13:56 (00:00) nishanthk ttys002 Mon Jun 8 13:56 - 13:56 (00:00) nishanthk ttys003 Mon Jun 8 13:56 - 13:56 (00:00) nishanthk ttys004 Fri Jun 5 09:34 - 09:34 (00:00) nishanthk ttys002 Fri Jun 5 09:34 - 09:34 (00:00) nishanthk ttys003 Fri Jun 5 09:34 - 09:34 (00:00) nishanthk ttys020 Thu Jun 4 11:20 - 11:20 (00:00) nishanthk ttys004 Wed Jun 3 16:42 - 16:42 (00:00) nishanthk ttys003 Wed Jun 3 00:06 - 00:06 (00:00) nishanthk ttys002 Wed Jun 3 00:06 - 00:06 (00:00) nishanthk ttys001 Wed Jun 3 00:06 - 00:06 (00:00) nishanthk ttys000 Wed Jun 3 00:06 - 00:06 (00:00) nishanthk console Wed Jun 3 00:06 - crash (15+16:17) reboot time Wed Jun 3 00:04 shutdown time Tue Jun 2 23:59 root console Tue Jun 2 23:57 - shutdown (00:02) nishanthk ttys003 Mon Jun 1 21:50 - 21:50 (00:00) nishanthk ttys002 Mon Jun 1 21:50 - 21:50 (00:00) nishanthk ttys001 Mon Jun 1 21:50 - 21:50 (00:00) nishanthk ttys000 Mon Jun 1 21:50 - 21:50 (00:00) nishanthk console Mon Jun 1 21:50 - 23:57 (1+02:07) reboot time Mon Jun 1 21:49 shutdown time Mon Jun 1 21:49 root console Mon Jun 1 21:49 - shutdown (00:00) nishanthk ttys003 Mon Jun 1 10:57 - 10:57 (00:00) nishanthk ttys009 Mon Jun 1 10:21 - 10:21 (00:00) nishanthk ttys000 Mon Jun 1 10:15 - 10:15 (00:00) nishanthk ttys004 Fri May 29 15:11 - 15:11 (00:00) nishanthk ttys003 Fri May 29 12:56 - 12:56 (00:00) nishanthk ttys002 Wed May 27 16:07 - 16:07 (00:00) nishanthk ttys001 Wed May 27 16:05 - 16:05 (00:00) nishanthk ttys001 Wed May 27 11:31 - 11:32 (00:00) nishanthk ttys001 Wed May 27 11:31 - 11:31 (00:00) nishanthk ttys002 Tue May 26 17:36 - 17:36 (00:00) nishanthk ttys001 Tue May 26 17:24 - 17:24 (00:00) nishanthk ttys001 Tue May 26 15:48 - 15:48 (00:00) nishanthk ttys001 Tue May 26 15:47 - 15:47 (00:00) nishanthk ttys000 Tue May 26 15:46 - 15:46 (00:00) nishanthk ttys001 Tue May 26 15:42 - 15:42 (00:00) nishanthk ttys001 Tue May 26 15:31 - 15:31 (00:00) nishanthk ttys000 Tue May 26 14:44 - 14:44 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys002 Tue May 26 14:43 - 14:43 (00:00) nishanthk ttys001 Tue May 26 14:42 - 14:42 (00:00) nishanthk ttys000 Tue May 26 14:31 - 14:31 (00:00) nishanthk console Tue May 26 13:42 - 21:49 (6+08:07) _mbsetupuser console Tue May 26 13:27 - 13:42 (00:14) root console Tue May 26 13:27 - 13:27 (00:00) reboot time Tue May 26 13:26 shutdown time Thu May 21 02:12 reboot time Thu May 21 02:03 reboot time Tue Mar 3 22:23 reboot time Tue Mar 3 22:18 wtmp begins Tue Mar 3 22:18:14 CST 2026 struct field to pass golangci-lint The field on Runner was written but never read externally — Run() already returns *CompletionState directly. The linter correctly flags any struct field that is never read. Removing it fixes the CI lint failure. Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…efault - Add Disabled() bool to Component interface; RolloutManager skips Setup/Deploy/Verify for disabled components (no-op DAG success) - Fix requeue: ReconcileManager now uses !VerifyCompleted()||ShouldRequeue instead of ShouldRequeue alone; BaseController.ShouldRequeue → false - Fix addFinalizer/removeFinalizer: DeployMethodUpdate → DeployMethodDefault (existing-wins merge was silently dropping the mutated finalizer list) - Expand reconcilemanager tests 9→24 cases; add 2 disabled-component tests to rolloutmanager Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
… dead code
- rolloutmanager: fix append aliasing bug in CompletionState assembly;
deployedAll now uses make+copy instead of append on a shared backing array
- rolloutmanager: remove redundant second unverifiedNodes loop (verifyState.Unverified
is a strict subset of deployedAll; second loop could never add anything)
- rolloutmanager: correct stale package doc (Disabled() is now first-class)
- reconcilemanager: fix nil-map panic in updateCompletionStatus; GetCondition
returns nil on a fresh CR — guard before map index
- reconcilemanager: fix ManageStatus doc comment ("Default true" → zero value is false)
- controller: fix ShouldRequeue interface doc (claimed "(true, 0)" multi-return
for a method that returns bool)
- reconcilemanager_test: collapse two duplicate RequeueAfter tests into one;
remove time import kept alive only by _ = time.Second
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
- session: add GetComponent(name) (*dag.Node, bool) — thin wrapper over Graph.GetNode so controller hook implementations can look up components without reaching into the DAG API directly; 2 tests added - reconcilemanager: add PauseAnnotation constant (oper8.org/pause-reconciliation) and isPaused() check as step 2 of Reconcile — if the annotation is present and non-empty, return immediately with Requeue=false, skipping finalizers, status, preconditions, and rollout; 3 tests added (present, empty, absent) - reconcilemanager: renumber inline step comments 1–9 to reflect the new pause check sitting between session construction and finalizer management Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Tanker2020
marked this pull request as ready for review
August 13, 2026 16:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR-5 — Reconcile Manager
Summary
This PR ports the core reconciliation engine from Python oper8 to Go, wiring all four previously-merged packages (
dag,deploymanager,status,verify) into a working end-to-end reconcile loop.Files added:
session/session.goDeployManager, componentGraph, and current cluster statuscomponent/component.goComponentinterface:Name(),Setup(),Deploy(),Verify()controller/controller.goControllerinterface +BaseControllerembed with no-op defaults for all optional hooksrolloutmanager/rolloutmanager.goreconcilemanager/reconcilemanager.goFiles modified:
dag/node.goNode.SetFunc(),Node.SetData(),Node.Data()— needed by RolloutManager to wire component functions onto graph nodesdag/runner.golast/inFlightfields (fixes lint failure from previous push); gofmtdeploymanager/dryrun.goDesign decisions vs Python
Sessionowned byReconcileManager, passed everywhereSessionis a plain struct created once perReconcile()callComponentABC with auto-registration in__init__sess.AddComponent()explicitly inSetupComponentsControllerabstract class properties (group,version,kind)GVK()method returns a plain struct@preconditiondecorator[]PreconditionFuncslice onOptionsThreadPoolExecutor+ sleep-polldag.Runnergoroutines withcontext.ContextcancellationRolloutManagerstored 4 separate callback varscontroller.Controllerdirectly; calls hook methods on itReconcileManagerreimported controllers dynamicallycontroller.Controller— no reflectionRollout phases
Phase 1 — Deploy graph runner.Run(ctx) → Setup + Deploy each component in DAG order
Phase 2 — AfterDeploy ctrl.AfterDeploy / ctrl.AfterDeployUnsuccessful hook
Phase 3 — Verify graph runner.Run(ctx) → Verify each deployed component
Phase 4 — AfterVerify ctrl.AfterVerify / ctrl.AfterVerifyUnsuccessful hook
Context cancellation propagates into both
Runnerexecutions. A fatal error in Phase 1 skips Phase 3 entirely. Phase 2 returningnot-OKpreventsVerifyCompleted()from being true even if all nodes verified.Tests
sessionAddComponent/AddDependency(ok + error),ScopedName,TruncateName(passthrough, exact boundary, over boundary, uniqueness)controllerGVKfields,HookResult, allBaseControllerdefaults (no-op / OK / false), override behaviour forShouldRequeue,HasFinalizer,FinalizerrolloutmanagerreconcilemanagerShouldRequeuetrue/false, precondition fail/multi-stop/all-pass, finalizer calls finalize-not-setup / error / stamps object, invalid CR (missing kind/apiVersion/metadata), two-component ordering, failed-blocks-dependent, status stable / error / verify-wait,ManageStatus=falsewrites nothing,ReconcileResultdefaultsTotal new tests: 77 across 4 packages. All run with
-race.Merge order
PRs must be merged in sequence:
PR-1 (DAG Runner) → PR-2 (Deploy Manager) → PR-3 (Status Management) → PR-4 (Resource Verification) → PR-5 (this PR)
What this PR does NOT include
controller-runtimeadapter (Reconcile(ctx, req) (ctrl.Result, error)) — that is a thin wrapper and can be added as a follow-up or in PR-6watch_managerport — tracked as PR-6main.go/ operator binary — see the smoke-test plan in the thread context for how to wire one up once this lands