Skip to content

Plan for refactoring to single go binary - #51

Closed
grubmeshi wants to merge 32 commits into
mainfrom
refactor/single-go-binary/plan
Closed

Plan for refactoring to single go binary#51
grubmeshi wants to merge 32 commits into
mainfrom
refactor/single-go-binary/plan

Conversation

@grubmeshi

Copy link
Copy Markdown
Collaborator

This is WIP and is never going to be merged. Only there to review the planned refactoring, which is huge!

grubmeshi and others added 19 commits July 8, 2026 22:59
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…kers

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ent coverage, D12-D14, phase 2b)

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…DE contract in D9

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…metric names frozen in D12

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…stry, atomic consolidation) and D12 (no listener in single-run)

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…phase-6 umbrella plan; D9 SPRING_PROFILES_ACTIVE alias, acceptance-gap fallback

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread PLAN_HIGH_LEVEL.md Outdated
Comment thread PLAN_HIGH_LEVEL.md
Comment thread PLAN_HIGH_LEVEL.md
Comment thread PLAN_HIGH_LEVEL.md
Comment thread PLAN_HIGH_LEVEL.md
Comment thread PLAN_HIGH_LEVEL.md
Comment thread PLAN_HIGH_LEVEL.md
Comment thread PLAN_HIGH_LEVEL.md
Comment thread PLAN_HIGH_LEVEL.md
Comment thread PLAN_HIGH_LEVEL.md
Comment thread PLAN_HIGH_LEVEL.md
Comment thread PLAN_HIGH_LEVEL.md
grubmeshi and others added 13 commits July 9, 2026 21:44
Grill round 2 rulings (all marked "RULED (grill r2)"):
- R12 exit non-zero scoped to pre-mutation failures only (plan 02, plan 05 §16.3)
- data-race B6/B10 fixed in phase 2, -race on (D13 exception, plan 02 §5.5)
- phase-2 exit adds polling + single-run runtime smokes (plan 02 §6)
- go-meshapi-client CI leg approved for phase 0 (plan 00)
- config: defaults < shared base YAML < per-impl YAML < env deep-merge (D7, plan 03/04)
- env compat: literal spellings only, fail-fast on unconsumed legacy-prefix vars (D7, umbrella)
- graceful shutdown: cancel sync-polling runs, terminal ABORTED (fallback FAILED, never
  SUCCEEDED), configurable 120s grace; ABORTED added to report.ExecutionStatus (D9, plan 03/05/06C/06D)
- D5 fail-fast reporting uses process credentials (plan 05)
- single-run exit-code tightening fixed in phase 6, old behavior pinned (umbrella, 06A-D)
- composite value formatting -> compact JSON, flagged byte change (umbrella, 06B/06C)
- GitHub App JWT: stdlib RS256, no golang-jwt dep (umbrella, 06D)
- dev key kept verbatim in shared base config, phase-7 removal item (umbrella, 06B, 07)
- L14 controller decrypt-failure FAILED report approved for phase 7 (07)
- slog-native for plans 03/04/05; phase-7 logging migration reduced to tf/tfrun (D15, 03/04/05/07)

PR#51 review reflections (marked "PR#51"):
- P4 pointer rule scoped to composite types; P7/D16 unit-tests-sparingly; P8 cohesive types
- D6 test infra -> httptest server mock + bare-repo testdata (plan 01 keeps fake transport in phase 1)
- D7 typed config struct + ${VAR} YAML interpolation; existing RUNNER_*/TF_* spellings kept (TF_ passed to tofu)
- plan 02 git.LocalRepo + tf Init/Plan/Apply/Destroy domain steer
- plan 05 capacity guard is k8s-independent; RunHandler purity (manual = inputs->outputs, no HTTP)
- retry/backoff confirmed in scope; client unification deferred
- mux obsolescence a downstream goal; removal tracked as separate meshfed-release item

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

Verified two assumptions against the actual repos, findings folded into the plans:

- ABORTED (meshfed-release): the runner-facing PATCH .../status/source/{sourceId}
  endpoint accepts an inbound ABORTED and persists it terminal via the coordinator.
  Accepted transition is IN_PROGRESS->ABORTED (exactly the graceful-shutdown case);
  PENDING is the only runner-forbidden status; an already-aborted run returns
  409 {runAborted:true} (treat as success). FAILED fallback stays valid. Removed the
  "assumption" hedge in D9 / plan 03 enum / plan 05 H7 and pinned the two constraints.

- Smoke coverage (meshstack-smoke-test): tf/manual/github confirmed covered (tf in the
  smoke-test repo; github_workflows + manual e2e modules live in meshstack-hub and are
  discovered/run by the harness); gitlab and azure-devops confirmed NOT covered.
  Corrected the repo name (meshstack-smoke-test, singular) and the hub split in the
  umbrella deletion gate.

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

Resolved the deferred RunHandler-purity design question via a grill interview,
grounded by verifying runner reporting shapes (Kotlin sources) and meshfed step-merge
semantics. Threaded the decision across all plans (marker "Grill r3 (RunHandler purity)"):

- Handler purity boundary: a handler MAY read the meshapi client's DTOs and consume its
  use-case/domain API; purity = it never assembles its own HTTP transport/auth. The
  reporter is injected as a use-case-level port (D4, plan 05 §4.2).
- ONE unified Reporter interface consumed by all five runners:
    type Reporter interface { Register(RunStatus) error; Report(RunStatus) (abort bool, err error) }
  The four event-driven ports (manual/gitlab/azdevops/github) discard the abort return
  and run no Observer; tf alone drives the Progress+Observer 10s ticker and honors abort.
- Report(RunStatus) sends only the changed/new steps present; the meshfed endpoint upserts
  steps by id (verified: BlockRunSourceUpdateService merges by id, never replaces). Step
  messages stay cumulative-replace (full per-step text; backend overwrites by assignment,
  not append). The lean SourceUpdateDTO/StepUpdateDTO remain the wire body.
- tf switches from full-snapshot to changed-steps-only sends in PHASE 3 — a deliberate,
  flagged wire change, backend-result-identical, so acceptance stays green while tf's
  phase-1 HTTP transcript pins are updated. Phase 2 keeps tf's full-snapshot send.
- Replaced plan 06A's bespoke report.SourceReporter{Register,Update} with the unified
  Reporter, and reconciled all 06B/06C/06D references (heaviest dedup consumer: azdevops)
  to it; impl-secret decryption stays handler-owned (manual none), external clients+Clock
  via per-runner constructors, Execute(ctx, ClaimedRun) unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…unner superset (grill r4)

Challenged and reversed the single-argv[0]-binary design (D2/D8). Grounded in the
dependency reality: run-controller links k8s client-go and NOT go-git/tofu; tf-block-runner
links go-git+terraform-exec and NOT k8s — disjoint heavy trees, so one fat binary shipped
each into images that never use it (bloat + SBOM/CVE noise on public images).

New shape (marked "Grill r4"):
- One Go module ./runner, shared internal/* packages (unchanged — the refactor's value).
- Fit per-persona binaries cmd/<persona>/main.go (cmd/tf + the four runner personas),
  each linking only its deps; direct image entrypoints, no argv[0]/symlink multiplexing.
- cmd/bbrunner = the superset AND the controller (amended: controller ≡ superset): links
  all handlers + both dispatchers and auto-detects the in-cluster k8s API
  (rest.InClusterConfig()/KUBERNETES_SERVICE_HOST) → KubernetesJobDispatcher in-cluster,
  InProcessDispatcher otherwise; RUNNER_DISPATCHER overrides. Shipped AS the run-controller
  image (one adaptive image; saves a whole persona). No separate cmd/controller.
- Sequencing: phase 4 creates cmd/tf + cmd/bbrunner (controller, k8s dispatch); phase 5
  adds InProcessDispatcher + auto-detect; handlers accrete through phase 6.
- Threaded across D1/D2/D8/D11, §1, phase-4/§7, and plans 02-07 (cmd/ layout, Dockerfile
  matrix, CI/release, dispatcher story, phase-6 template, cleanup).

Terminology: the concept is now "single Go module" (title + prose); branch names
(refactor/single-go-binary/*) and the PLAN_DETAIL_04_single_binary.md filename kept as-is
(open PR references them).

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

Sweep across all plans removing the plan-creation history (grill-round tags, PR#51
refinement labels, "RULED"/"VERIFIED"/"self-grilled" framing, "this replaced/amended/
superseded an earlier design" narration, and resolved reviewer-may-veto/open-question
scaffolding). Every technical decision, guideline (P1-P8, D1-D16), behavior pin, interface,
contract, STOP marker, and assumptions-table is preserved as plain current spec; redundant
restatements collapsed to precise cross-references.

Net ~157 lines removed (622 ins / 779 del across 13 files); process tags now 0.

Also reconciled two internal inconsistencies surfaced during the sweep:
- plan 02: removed a stray "-race turns on in phase 2" clause that contradicted plan 02
  §5.5 pt3 / 2b R1 / plan 03 A5+A7 (races fixed structurally in phase 2; -race gate flips
  in phase 2b). Substantive decision unchanged.
- umbrella §5.5/§5.6/A7/rollback: rewrote leftover single-runner.Dockerfile+target-stage
  base prose to the per-persona containers/<persona>/Dockerfile model already stated in
  06A/06B (it had contradicted the sub-plans after the r4 pivot).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cess 3, k8s 10)

Module layout (Change 1): the single consolidated Go module now lives at the
repo root, not a ./runner/ subdir. Module path becomes
github.com/meshcloud/building-block-runner (packages internal/*, binaries
cmd/*). The Go workspace is removed entirely at phase 4 — go.work + go.work.sum
are deleted outright in the consolidation step; there is no workspace end-state
and no intermediate `use ./runner`. Threaded through git-mv destinations,
Dockerfile (COPY go.mod go.sum, root build context, ldflags .../internal/build),
CI (go-version-file: go.mod, test leg at root over ./...), Taskfile (no dir:
runner/), depguard prefixes, coverage thresholds/exclusions, and the cross-repo
local-dev-stack SKILL. Added the note that the root module coexists with the
still-Kotlin/Gradle runner dirs (separate build systems, deleted phase 6/7).

Default concurrency (sanctioned behavior changes):
- Standalone in-process maxConcurrentRuns default 1 -> 3 (Change 2): the tf and
  all phase-6 in-process personas run up to 3 concurrent runs by default; =1
  restores today's exact serial cadence. Makes the H1-H8 concurrency paths
  default-on.
- k8s controller maxConcurrentJobs default 20 -> 10 (Change 3): sole exception
  to the run-controller persona's bit-identical guarantee.

Consistency sweep: reconciled the module READMEs to a single root README.md;
propagated default-3 into all phase-6 plans (they still cited "default 1 (plan
05)") incl. the azdevops sync-poll blocking argument; fixed plan 05 A1 which
listed the four runner mains as phase-4 deliverables (they arrive in phase 6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Docker model (task a): phase 4 now creates two standalone per-app Dockerfiles
(containers/run-controller/Dockerfile builds ./cmd/bbrunner,
containers/tf-block-runner/Dockerfile builds ./cmd/tf) — no shared
containers/runner.Dockerfile, no --target, direct ENTRYPOINT — uniform with the
per-app model phases 6/7 already assume. Updated STOP-D, §4.4, §4.5, §5 step 5,
§7.2, §11; reframed the 06C/06D step-image lines off their leftover
`docker build --target …` residue.

Ref-integrity sweep (5 partitioned audits): fixed 9 dangling/mis-resolving
cross-references — plan 02 "§4.1 item 3"→"§3.1 item 3"; plan 04 "(§5.4)"→"(§6)"
and "flag §10.6"→"flag §10.8"; plan 05 "depguard §5.3"→"depguard §5"; umbrella A8
"Plans 00/02/04 §7.1"→"plan 00 §5.4, plan 02 §7, plan 04 §7.1"; and dropped the
stale open-question Q2/Q3/Q4 pointers left by the de-historicize sweep (plans
01/02), keeping their valid companion refs. The bare umbrella-§ shorthand in the
06 sub-plans is confirmed established house style (present in untouched siblings),
left as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… smokes + opt-in controller kind e2e

Closes the gap where the high-level plan mandated a reusable httptest meshfed-API
mock but no detail plan built it, and where "does the runner actually work" and the
controller's k8s dispatch were never exercised end-to-end.

(a) Shared mock server: new plan 03 §5.7 defines `meshapitest` — a net/http/httptest
meshfed-API mock server (claim/register/PATCH/artifact, real HTTP, seedable runs +
captured requests), phase-3 deliverable at go-meshapi-client/meshapitest → phase-4
internal/meshapitest. High-level D6 and plan 01's deferral now point at it; it is the
single owner. Reused by phases 4–7.

(b) Mock-backed container smoke: plan 04 step 5 and every phase-6 §8 image step now
docker-run the built image against a meshapitest server (host networking,
RUNNER_API_URL→mock) and assert a full claim→execute→report cycle — not just healthz.
Rule stated once in umbrella §5.6, inherited by all four sub-plans; per-persona
integration tests use meshapitest for the meshStack side (external-service httptest
fakes unchanged).

(c) Opt-in controller kind e2e (plan 07 §5.4): a //go:build e2e test that stands up a
kind cluster, deploys run-controller, points it at a meshapitest coordinator, seeds a
claimed run, and asserts a real k8s Job is created (dispatched tf image runs
single-run). Runs under task test:e2e:controller, never a PR gate; closes the
plan 03 §12.3 / plan 05 §13 controller-e2e gap. meshfed-release has no turnkey
minikube (docker-compose local-dev); harness-shape pattern = replicator fabric8
KubernetesTestData. Plan 05 §7 concurrency suite also moves onto meshapitest.

Refs threaded consistently (plan 03 §5.7, plan 07 §5.4 qualified everywhere; umbrella's
own §5.7 Acceptance left intact).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…client's Logger seam

LOG_LEVEL (debug|info|warn|error, default info) sets the process slog handler level for
every persona. config.LogLevel helper ships in plan 03 §5.3; main wires the handler level
in plan 04 §4 / plan 07 §8.1. Plan 04 scope note updated (two new env vars now:
MANAGEMENT_PORT + LOG_LEVEL).

DEBUG HTTP wire logging (plan 03 §5.2.6): at LOG_LEVEL=debug the shared meshapi client
logs full request/response headers AND bodies including sensitive values (runToken bearer,
claim payloads, decrypted inputs, PATCH messages) — unredacted by design (opt-in
diagnostic, off by default; §8 records the trade-off). Artifact-download bodies are
excepted (metadata only) so DEBUG can't blow up on a 128MiB stream.

Reuse from terraform-provider-meshstack (D3, §7): the provider already implements this in
client/internal/{logging.go,http_client.go} — a pluggable Logger interface
(Debug/Info/Warn(ctx,msg,args...), noopLogger default) + request/response Debug logging
with sorted headers and pretty-JSON body via lazy fmt.Stringer wrappers. We copy the
interface + loggedHeaders/loggedBody/bytesToPrettyJson verbatim into meshapi so the future
shared meshstack-go-sdk merges with NO logging-seam delta. The meshapi client takes this
Logger interface (not *slog.Logger); main injects a meshapi.SlogLogger adapter, keeping the
app slog-native while the SDK-bound client stays portable.

Two conflicts flagged and resolved:
- The provider REDACTS Authorization ([REDACTED], logging.go:47-50); we drop that redaction
  per the no-obfuscation requirement (one-line policy diff, recorded in §3.5/§7/§8).
- The provider is JSON-only (no streaming download); our DownloadArtifact stream is kept
  out of the body logger (our addition).

Recorded in §3.5 (adopt list), §7 (alignment: logging merge cost now "none"), and the §5
slog-native note (meshapi's Logger seam is the carve-out from *slog.Logger).

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

Reverses the earlier deviation: instead of dropping the provider's Authorization-header
redaction, we keep meshapi's copied loggedHeaders verbatim — Authorization stays
[REDACTED] as the single sanctioned exception to the "don't obfuscate secrets in logs"
policy. Request/response bodies remain fully unredacted at LOG_LEVEL=debug (a runToken in
a claim body is still shown); only the Authorization header value is masked. Net effect:
zero deviation from the provider code, so the §7 SDK-merge logging cost is genuinely nil.

Updated plan 03 §5.2.6/§3.5/§7/§8 and the wording in plan 04 §4, plan 07 §8.1, high-level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PLAN_IMPL_GUIDE.md (orchestration docs only — no planned gate is changed)
plus .claude/workflows/implement-refactor.js: a full-autonomous, per-phase
Claude Code workflow that verifies each phase's assumptions, fans out parallel
sub-agents partitioned to disjoint files (haiku/sonnet), then reconciles with an
opus consolidation agent into one green branch + draft PR.

Model tiering keeps ~73% of ~66 agents off opus; opus is reserved for the 9
consolidators, the run-log, and the 8 hard implementer slots. The run never
halts: it resolves assumption failures, conflicts and the phase-6 rebase-stack
itself, recording only genuinely uncertain calls to PLAN_IMPL_RUN_LOG.md for
human review. Agents never edit outside this repo; cross-repo changes are handed
off via CROSS_REPO_TODO.md.

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

Human-facing reading of the decision ledger from the phases 0→7 autonomous run
(wf_8826f73f-1bb). Leads with a "Review this first" list: 5 BLOCKER items (the
deferred phase-2/3 DDD debt carried across five phases; phase-3 steps 6–11 +
missing NewMetricsCollector(reg) seam; phase-5 tf-persona concurrency cutover not
delivered; phase-7 L14 decrypt-FAILED wire change without STOP-D sign-off; the
infeasible `log` depguard + unenforced D11 layering), 5 judgment calls, the
cross-plan inconsistencies agents could not fix on the plan branch, a per-phase
summary with PR links, and an honest "what merely made it fit" section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The recurring failure across the run was consolidators shipping green-but-INCOMPLETE
PRs — reconciling only the slices the fan-out produced and deferring the phase's
remaining plan steps to the uncertainty log instead of finishing them (4 phases
missed their own exit criteria this way). Instruct every phase's consolidator to
IMPLEMENT the missing steps so the stated exit criteria actually hold before opening
the PR, keeping the tree green (task test / task lint / -race / coverage), and to
record an uncertainty only when it genuinely cannot be completed (needs live
meshStack/cluster/Gradle or a frozen contract) with a precise reason. Sets
exitCriteriaMet=true only when the criteria truly hold.

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