Skip to content

chore(phase-7): cleanup sweep — JVM endgame, Go-only CI, slog finish, docs - #63

Closed
grubmeshi wants to merge 10 commits into
refactor/single-go-binary/phase-6a-manualfrom
refactor/single-go-binary/phase-7-cleanup
Closed

chore(phase-7): cleanup sweep — JVM endgame, Go-only CI, slog finish, docs#63
grubmeshi wants to merge 10 commits into
refactor/single-go-binary/phase-6a-manualfrom
refactor/single-go-binary/phase-7-cleanup

Conversation

@grubmeshi

Copy link
Copy Markdown
Collaborator

Phase 7 — Cleanup & Docs (final phase of the single-Go-binary refactor)

Consolidation of the parallel phase-7 slices into one always-green, reviewable PR. Closes the deferred-item ledger of docs/plans/PLAN_DETAIL_07_cleanup.md.

Base note: opened against phase-6a-manual per the orchestrator instruction; the working branch was stacked on phase-6d-github, so this diff also carries the 06B/06C/06D content that had already landed on the shared worktree.

What landed

  • JVM endgame (step 0): deleted block-runner-core/, the four Kotlin module dirs, the Gradle wrapper/*.gradle, containers/jvm.Dockerfile + entrypoint-jvm.sh, the flake.nix jdk21/ktlint toolchain, and the gradle/kotlin .gitignore/.dockerignore residue.
  • CI reshape (D14, L1/L2/L3): ci.ymllint + test + images; build-images.yml Go-only; new opt-in e2e.yml (real tofu/terraform download) behind the e2e build tag + task test:e2e, so default CI is hermetic for the first time.
  • slog finish (§8/L11): controller bootstrap (cmd/bbrunner) + internal/meshapi/auth.go migrated off stdlib log; internal/* is now log/slog-only, enforced by a new AST guard test (internal/build/loggingstack_test.go).
  • Deprecation audit (§7/L9), config flip (L12), file hygiene (L16/L17/L18): .editorconfig JVM sections removed; k8s manifests → containers/run-controller/k8s/ (with a commented-out healthz probe example); .agents/skills/backend-go rewritten; SPRING_PROFILES_ACTIVEEXECUTION_MODE in the controller sample (old form still honored).
  • Plan-file disposition + docs: PLAN_*.md and the supporting refactor records → docs/plans/; README.md, docs/ARCHITECTURE.md, docs/DEPRECATIONS.md.
  • L6 paid off: fixed the underlying errcheck/forcetypeassert/makezero findings in internal/{meshapi,crypto,report} (behavior-preserving) and removed the temporary .golangci.yml exclusion block.

Sanctioned deltas (§11)

  1. L14 (STOP-D) — controller decrypt-failure fix: a decrypt failure now reports a terminal FAILED with actionable key-mismatch guidance via the already-accepted reportRunFailure wire shape (P5), still incrementing run_controller_decryption_errors_total. The former SilentDispatchFailure silent-timeout quirk and its seam are removed. This is a customer-facing error-path wire-behavior change — STOP-D asks for explicit review of the exact new shape; please confirm meshStack's coordinator is happy with an active FAILED on decrypt failure (it was already silent-until-timeout before). Happy paths are byte-identical.
  2. Log format: slog text-handler + persona=/run= attributes replace the [RUN CONTROLLER]/[TF RUNNER] prefixes (never a wire contract; local-dev-stack readiness markers updated lock-step, tracked in CROSS_REPO_TODO.md).
  3. CI job names/structure (lint/test/images) — branch-protection required-check names change; coordinate at merge.

Deliberately not landed

  • L15 (single-run/handler unification): guarded should-have (STOP-E). The parallel single-run glue in cmd/tf/main.go was kept to hold the frozen single-run wire/exit contract byte-identical; recorded as a follow-up in docs/ARCHITECTURE.md.

Reviewer heads-up (pre-existing finding)

The repo's depguard files globs (internal/X/**/*.go) match nothing (they need a leading **/), so the D11 layering rules are currently not enforced — pre-existing across all phases, out of this phase's scope, flagged for a follow-up. The single-logging-stack invariant is instead enforced by the new AST guard test.

Gates

task test (-race), task lint (0 issues), task coverage (11/11 gated packages ≥ 90%) all green.

🤖 Generated with Claude Code

grubmeshi and others added 10 commits July 10, 2026 20:18
Ports PLAN_DETAIL_06B_gitlab.md, reusing the 06A manual template unchanged (handler
shape, event-driven report.Reporter seam, config compat, persona wiring, Dockerfile
pattern). Additive and always-green: the Kotlin gitlab-block-runner module and its
Gradle/CI legs are untouched (removal + the acceptance gate + the meshfed-release
touch-points are deferred to the flip PR, see CROSS_REPO_TODO.md — they need a live
GitLab/meshStack this environment can't provide).

Umbrella-assigned shared artifacts shipped here (first consumer, per 06A §4.4/§17):
- meshapi.Decryptor + CertDecryptor/NoopDecryptor: the shared decrypt seam every
  phase-6 port injects, with the Kotlin decrypt("") == "" empty-string guard (T8/G-P11)
  the underlying crypto lacked.
- meshapi.DecryptInputs: decrypts ONLY sensitive STRING/CODE/FILE input values
  (Kotlin's decryptBlockRunInputs asymmetry, umbrella §7.6) — the structural fix so an
  outbound payload can never carry a decrypted implementation secret. Table-driven
  tests incl. a real-keypair cross-check and the secret-hygiene leak assertion.
- gitlab.ExternalCallError: the MeshHttpException equivalent (06A §4.4 shape), with the
  four-way GitLab error classification (404 / identity-verification / generic /
  undeserializable body) collapsing into the one wire-visible FAILED update Kotlin sends.

Gitlab persona (internal/gitlab, cmd/gitlab, bbrunner gitlab subcommand):
- One external POST (multipart trigger payload: token/ref/MESHSTACK_BEHAVIOR/
  MESHSTACK_RUN + env/non-env inputs + the four callback URLs, missing-link ⇒ omit),
  redirects disabled at the client (G-P10), base-URL sanitization ported from
  UrlSanitizerService.
- Always-async handover (D9): register one PENDING gl-trigger step, then exactly one
  final update — IN_PROGRESS+SUCCEEDED on a successful trigger, FAILED+FAILED on any
  trigger-path failure — never a terminal SUCCEEDED from the runner itself.
- Polling (dispatch.Loop + InProcess, mgmt on 8103, PORT alias) and single-run (file
  source, NoOp decryptor — the k8s controller pre-decrypted both inputs and the trigger
  token, §2.6 caveat pinned as-is — R12 exit rule).
- New shared top-level base containers/runner-config.yml (deep-merged under the
  per-impl file, base < per-impl < env): carries the well-known dev private key shared
  by gitlab/azdevops/github, re-wrapped into standard multi-line PEM (Go's stdlib
  encoding/pem rejects the Kotlin classpath's unbroken single-line form; DER bytes are
  byte-identical, verified in internal/config/basekey_test.go).

Coverage gate: internal/gitlab joins thresholds.txt at 90 (measures 92.1%, no
exclusions) via the scenario suite (meshapitest + a fake GitLab httptest server) plus
keep-as-unit tables for the pure mappings (valueString, sanitizeBaseUrl, error
classification). Manually verified end-to-end via `docker build` + `docker run` against
a hand-rolled mock meshfed API: claim → register → trigger attempt → FAILED report all
observed with the exact pinned message shapes.

Kotlin pin tests (G-P1–G-P13) were not added — gradle is not runnable in this
environment and the module isn't being modified/removed in this commit; recorded as a
flagged follow-up in CROSS_REPO_TODO.md rather than silently skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
06A specified these for 06B (gitlab) to ship as first consumer, but
phase-6b-gitlab had not landed them at authoring time (git diff
phase-6a-manual..phase-6b-gitlab was empty). Ships them here to the
06A-specified contract instead, per PLAN_DETAIL_06C_azdevops.md's STOP-C4
resolution -- flagged in CROSS_REPO_TODO.md for reconciliation if 06B lands
its own copy in parallel.

Mirrors internal/tf.Decryptor's existing shape (Decryptor interface,
certDecryptor, NoopDecryptor) and the Kotlin MeshCertDecryptionService
input-only decrypt asymmetry (STRING/CODE/FILE decrypted, other sensitive
types left as ciphertext, impl secrets untouched).
Kotlin's hand-rolled PrivateKeyLoader/MeshCertDecryptionService parser
strips the BEGIN/END markers and every newline by hand, so every Kotlin
runner image's classpath runner-config.yml ships its baked dev privateKey
with no newline after "-----BEGIN PRIVATE KEY-----". Go's stdlib
encoding/pem.Decode rejects that shape outright.

azure-devops is the first Go port to actually decrypt (manual never does),
so this was the first real exercise of config.ResolvePrivateKey against a
real Kotlin-shipped key -- without this fix, both this port's own baked dev
key and any customer's existing Kotlin runner-config.yml would silently
fail to decrypt on the new Go image (a config-compat break, D7/D10).

normalizePEM is a pure additive fallback: only tried when a bare pem.Decode
already failed, so an already-valid multi-line PEM is unaffected. Verified
end-to-end against the actual containers/azure-devops-block-runner baked
key via both a unit test and a docker-run smoke.
…s.md)

The heaviest of the four Kotlin ports (AzureDevOpsPipelinePoller/
StatusUpdater/StatusMapper had zero direct Kotlin tests): trigger a
pipeline run, then either hand over IN_PROGRESS (async) or poll to a
terminal report with per-stage steps (sync), on the 06A template
(dispatch.RunHandler, the unified report.Reporter, config compat helpers,
StandaloneClaimClassifier).

- statusmapper.go: pure S-P1-S-P6 mapping-table twins of
  AzureDevOpsStatusMapper.kt, incl. the pinned else-holes
  (completed+succeededWithIssues/absent -> IN_PROGRESS).
- client.go: the ADO wire client (trigger/get-run/get-timeline), the
  ExternalCallError type (per-package, per umbrella §4 row 14 -- not a
  shared meshapi type), renderValue (UseNumber-fidelity stringification,
  flagged JSON-not-toString delta for composite values, §16.6), redirect
  policy, explicit timeouts (§16.10), no base-URL sanitization (§16.7).
- updates.go: RunStatus builders for every U-P1-U-P8 update shape
  (trigger-success, state-only, stage-batch with the one-way COMPLETED
  dedup, final, failed, terminal abort/failed).
- handler.go: the failure ladder (register -> parse impl -> decrypt PAT ->
  decrypt inputs -> trigger), byte-identical U-P8 message pair selection.
- poll.go: the sync poll loop, ctx-aware (plan-05 H7 amendment: reports a
  TERMINAL status on shutdown cancellation, ABORTED falling back to FAILED,
  never SUCCEEDED) with escalation semantics verified line-by-line against
  the Kotlin nested try/catch (a PATCH failure during a stage/fallback
  update is silently absorbed like a GET failure; only the timeout and
  final-update reports get one escalation attempt before propagating).
- config.go/loadconfig.go/reporterfactory.go/singlerun.go: persona config,
  the R12 single-run exit tail with the sanctioned K-P2 delta, single-run
  mode wiring.

Test suite (D16 scenario style, D6 gate ≥90%, actual 93.2%, -race clean):
reproduces all 28 planned pins (6 S + 8 U + 5 P + 6 A + 2 K + 1 F) via a
fake Azure DevOps httptest transport, the shared meshapitest mock, and an
injected Clock (sleep-free, no goroutine races -- After() fires instantly
and Now() only advances via an explicit per-test bump, so even the 30-min
timeout pin runs in milliseconds).

No new Kotlin JUnit tests were added (gradle is not runnable in this
environment, matching the 6a precedent) -- the Go scenario suite is the
surviving pin; see CROSS_REPO_TODO.md.
- cmd/azdevops/main.go + polling.go: the fit azure-devops-block-runner
  binary (D1/D8) -- single-run vs polling mode selection, mgmt listener on
  8101 (PORT alias), cert-based decryptor wiring.
- cmd/bbrunner: register the azdevops persona subcommand
  (`bbrunner azdevops`) alongside tf/manual, for local-dev / mux
  replacement (§4.1).
- containers/azure-devops-block-runner/{Dockerfile,runner-config.yml}: the
  06A per-app-Dockerfile pattern, direct entrypoint, the baked local-dev
  private key (umbrella §10.5) -- verified with a real `docker build` +
  `docker run` smoke (config loads, private key resolves and parses,
  dispatch loop starts, /healthz answers OK).
- tools/coverage/thresholds.txt: gate internal/azdevops at 90% (actual
  93.2%, no exclusions -- whole package hermetically testable).
- .golangci.yml: azdevops/azdevops-test depguard groups mirroring the
  manual persona's (no k8s, no go-git, no tofu).
Adds PLAN_DETAIL_06C_azdevops.md (from the plan branch) to this stacked
branch, and records the phase-6c deferrals in CROSS_REPO_TODO.md: the
Kotlin module removal / CI flip / meshfed-release edit all wait on the §11
acceptance gate (live Azure DevOps org + side-by-side transcript
equivalence), which this environment cannot execute -- same rationale as
phase-6a. Also flags the two STOP-C4/crypto template-gap fixes this slice
made for the next port to be aware of.
Port PLAN_DETAIL_06D_github.md: the GITHUB_WORKFLOW persona in Go.

- App-JWT auth chain, stdlib RS256 (crypto/rsa SignPKCS1v15 + x509.ParsePKCS1PrivateKey),
  no JWT dependency (§4.2/§16.13); whitespace-tolerant PKCS#1 parsing matching Kotlin (G-P2).
- githubClient: 5 REST calls, frozen headers, actions=write permission gate, 422
  unsupported-input heuristic (pure classify fn), validated run/job status enums.
- Dual input modes: Mode A base64-JSON run object (impl stripped to {type}, G-P10 leak
  hygiene, json.Number fidelity D8), Mode B url+token+conditional-endpoint table.
- In-handler sync poller: heuristic find-window (5 newest, -30s, 12x10s), 30m poll budget,
  job-step batches (trigger step in first batch, completed-job re-report quirk G-P4),
  terminal update from conclusion; ctx-cancel reports terminal ABORTED (never SUCCEEDED).
- Config compat (blockrunner: block, private-key order, SPRING_PROFILES_ACTIVE alias),
  cert/NoOp decryptor, event-driven reporter seam, R12 single-run exit (G-P11 tightening).
- Persona wiring: cmd/github (+ bbrunner github subcommand), mgmt on 8102, per-app
  Dockerfile + runner-config.yml (baked dev key, umbrella §10.5).
- Gate: thresholds += internal/github 90 (91.3%, no exclusions), depguard github group.

Template fit-check (§4.6) passed unchanged. Because 06B is not landed in this base
(phase-6a-manual), the ExternalCallError/DecryptInputs shapes it promised are implemented
package-locally for now and should be reconciled to the shared meshapi twins at
consolidation. JVM teardown (steps 10-11) and CI flips are deferred to consolidation to
keep this slice disjoint from the parallel 6b/6c siblings.

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

Ports the phase-7 detail plan from the planning branch so phase-7-cleanup
carries it; this branch is rebuilt on phase-6d-github (not phase-6a-manual)
since 06B/06C/06D already landed in sibling worktrees and are prerequisites
per the plan's own header.
Runs every A1-A14 verification step against phase-6d-github (fast-forwarded
in, since 06B/06C/06D had already landed in sibling worktrees). task test
(-race), task lint and task coverage are all green.

A2 (JVM/Gradle gone) fails structurally, not as residue: PLAN_DETAIL_06D's
own step-11 "JVM endgame" was never executed on phase-6d-github, and every
downstream phase-7 ledger row/migration step assumes it already happened.
Resolved by folding that catch-up into step 0 of this plan's migration
sequence rather than rewriting scope. A5/A9/A13 also fail today but are
direct consequences of A2 or already-anticipated ledger/cross-repo items
(L6, L21/L22, CROSS_REPO_TODO.md) needing no plan change. A1/A3/A4/A6/A7/
A8/A10/A11/A12/A14 verify cleanly (A3/A10 with expected path/package drift
from earlier phases' own sanctioned decisions).

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

Final phase of the single-Go-binary refactor (PLAN_DETAIL_07_cleanup.md). Consolidates
the parallel slices and closes the deferred-item ledger.

JVM endgame (step 0): delete the Gradle/Kotlin tree (block-runner-core/, the four Kotlin
module dirs, gradle wrapper, build.gradle/settings.gradle/gradle.properties),
containers/jvm.Dockerfile + entrypoint-jvm.sh, the flake.nix jdk21/ktlint toolchain, and the
gradle/kotlin .gitignore + .dockerignore residue.

CI reshape (D14, L1/L2/L3): ci.yml -> lint + test + images jobs; build-images.yml Go-only;
new opt-in e2e.yml (real tofu/terraform download) gated behind the `e2e` build tag +
`task test:e2e`, keeping default CI hermetic.

slog (§8/L11): controller persona bootstrap (cmd/bbrunner) and meshapi/auth.go migrated off
stdlib log; internal/* is now log/slog-only, enforced by a new AST guard test
(internal/build/loggingstack_test.go) that stands in for the infeasible depguard "log" deny.

Deprecation audit (§7/L9), config flip (L12: run-controller sample job templates
SPRING_PROFILES_ACTIVE -> EXECUTION_MODE, old form still honored), file hygiene
(.editorconfig JVM sections, k8s manifests -> containers/run-controller/k8s/,
.agents/skills/backend-go rewrite), verification sweeps, plan-file disposition to
docs/plans/, README + docs/ARCHITECTURE.md + docs/DEPRECATIONS.md.

Paid off the temporary .golangci.yml legacy-exclusion block (L6): fixed the underlying
errcheck/forcetypeassert/makezero findings in internal/{meshapi,crypto,report} behavior-
preservingly, then removed the block.

Sanctioned behavior delta (L14, §11 change #1): the controller decrypt-failure silent hang
is fixed — a decrypt failure now reports a terminal FAILED with actionable key-mismatch
guidance via the already-accepted reportRunFailure wire shape (P5), still incrementing
run_controller_decryption_errors_total; the SilentDispatchFailure seam is removed.
L15 (single-run/handler unification) deliberately not landed (guarded should-have; glue kept
to hold the frozen single-run contract byte-identical).

task test (-race), task lint, task coverage (11/11 packages >= 90%) all green.

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