Skip to content

refactor(phase-5): dispatcher abstraction + controller dissolution - #58

Closed
grubmeshi wants to merge 2 commits into
refactor/single-go-binary/phase-4-single-binaryfrom
refactor/single-go-binary/phase-5-dispatcher
Closed

refactor(phase-5): dispatcher abstraction + controller dissolution#58
grubmeshi wants to merge 2 commits into
refactor/single-go-binary/phase-4-single-binaryfrom
refactor/single-go-binary/phase-5-dispatcher

Conversation

@grubmeshi

Copy link
Copy Markdown
Collaborator

Phase 5 — Dispatcher Abstraction & In-Process Concurrency

Stacked on phase-4-single-binary. Consolidates four parallel slices into one
always-green commit. All gates pass: task test (-race), task lint
(0 issues), task coverage (dispatch 97.7%, k8sjob 95.7%, all others ≥90%).

What landed

  • internal/dispatch — generalized claim/drain Loop (Dispatcher/
    ClaimedRun/Claimer/StatusApi/ClaimClassifier seam), InProcess
    dispatcher (go-func per run, synchronous in-flight increment before spawn,
    Done() wake, Wait() drain within configurable grace), RunHandler
    interface + in-process secret/auth contract, Capability parsing, the two
    UnhandledTypeError messages (frozen k8sjob text byte-identical, new
    actionable InProcess text), relocated run_controller_* MetricsCollector.
  • internal/k8sjobKubernetesJobDispatcher moved from the former
    controller; clientset widened to kubernetes.Interface (A3) with fresh
    kubernetes/fake goldens; cluster.go real-cluster I/O isolated + excluded.
  • internal/controller deleted; cmd/bbrunner run-controller rewired onto
    dispatch.Loop + k8sjob.KubernetesJobDispatcher — k8s dispatch path
    behavior-verbatim (claim wire, decryption order, Job/Secret/SA manifests,
    registration PUT, metric names all preserved).
  • Concurrency-hazard suite H1–H8 under -race; tf TfBinaries concurrent
    GetTF test (H1). loop.shutdownCalled boolatomic.Bool (B6 fix).
  • Gate extension: dispatch + k8sjob at 90; depguard groups replace the
    controller group; internal/controller prometheus/lint exclusions removed.

Sanctioned deltas (§12)

NOT delivered by the parallel slices (scope gap — see uncertainties)

The dispatcher framework, InProcess, RunHandler, Capability and the k8s
controller dissolution are complete and consumed by the controller persona.
The tf-persona cutover was not delivered (plan steps 5, 6, 8, 9):

  • tf.NewHandler (dispatch.RunHandler) does not exist; runTfPolling
    (cmd/bbrunner/tf.go) and cmd/tf still run the old tf.NewManager polling
    loop + shared SetRunToken/ClearRunToken slot.
  • maxConcurrentRuns config (default 3, delta feat: Improves ENV pass-through #5), the tf registration:
    section, runner_runs_unhandled_total / runner_at_capacity_skips_total
    additive metrics, and cmd/bbrunner dispatcher auto-detect are absent.
  • LoopDeps has no Wake channel field (§17 promise); InProcess.Done()
    exists but is wired into no production Loop.

Consequence: the tf persona is unchanged (safest state — no frozen tf
contract touched), but the phase's headline in-process concurrency is not yet
live. InProcess/RunHandler/Capability ship as tested-but-dormant library
code ready for that cutover. Exit criteria for steps 6/8/9 are therefore not
met; the interface shapes phase 6 builds against (§17) are present.

🤖 Generated with Claude Code

grubmeshi and others added 2 commits July 10, 2026 18:19
…bt, adapt plan

Runs every A1-A12 verification step from PLAN_DETAIL_05_dispatcher.md §1
against the phase-4-single-binary tree. A3 (k8s Job/Secret/SA manifest
goldens via kubernetes/fake), A4 (Engine.Execute unification), A5 (shared
meshapi.DecryptRunDetails/NodeId), A6 (zero package-level mutable state)
and A8 (8-line coverage gate incl. crypto) are materially or partially
false — the same plan-2/3 DDD-refactor debt phase 3's and phase 4's own
STOP-gate checks already found and named, now a third consecutive check
to record it.

Resolved autonomously without halting, triaged per finding rather than
blanket-deferred: A3 is genuinely blocking (kubernetes.go has zero test
coverage and its concrete *kubernetes.Clientset field blocks fake-clientset
tests outright) so step 3 is expanded to interface-ify the clientset field
and author the manifest goldens fresh; A4 is reinterpreted (the tf handler
wraps the existing, correctly-shaped SingleRunWorker.ExecuteRun rather than
requiring a new Engine type); A5's decryption.go package-fate row is
corrected (moves to internal/k8sjob, not meshapi); A6/A8 are accepted,
non-blocking debt carried forward with the rest of the pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dissolve internal/controller into two D11 packages and introduce the
dispatcher framework that the standalone in-process runners will consume:

- internal/dispatch: generalized claim/drain Loop (Dispatcher/ClaimedRun/
  Claimer/StatusApi/ClaimClassifier seam), the InProcess dispatcher
  (go-func per run, synchronous in-flight increment, Done() wake, Wait()
  drain within a configurable grace), the RunHandler interface + in-process
  secret/auth contract, Capability parsing (5 concrete types + ALL), the
  two dispatcher-authored UnhandledTypeError messages (frozen k8sjob text
  byte-identical to controller.go:201; new actionable InProcess text), and
  the relocated run_controller_* MetricsCollector (names/labels frozen).
- internal/k8sjob: KubernetesJobDispatcher (Job/Secret/SA manifests,
  RunTooLargeError, CountActiveJobs, per-run decryption before Job
  creation), clientset widened to kubernetes.Interface (A3), fresh
  kubernetes/fake goldens, cluster I/O isolated in cluster.go (excluded),
  WIF registration DTO builder. Config split per plan §5.
- internal/controller deleted; cmd/bbrunner run-controller rewired onto
  dispatch.Loop + k8sjob.KubernetesJobDispatcher (k8s dispatch path
  behavior-verbatim). controllerConfig embeds k8sjob.Config yaml:",inline".
- Concurrency-hazard suite H1-H8 under -race; tf TfBinaries concurrent-GetTF
  test (H1). loop.shutdownCalled bool -> atomic.Bool (B6 data-race fix).
- Gate extension: dispatch + k8sjob join thresholds at 90 (97.7% / 95.7%);
  k8sjob/cluster.go excluded; depguard groups replace the controller group.

Sanctioned delta (§12): maxConcurrentJobs compiled-in default 20 -> 10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@grubmeshi

Copy link
Copy Markdown
Collaborator Author

Superseded by #65, which consolidates the plan + all phases 0→7 + the phase-3/5 remediation into a single reviewable PR (with all plan/run artifacts and a code-verified FOLLOW_UP.md). Closing in favor of #65.

@grubmeshi grubmeshi closed this Jul 13, 2026
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