Skip to content

fix: bug-wave — 8 backlog bug fixes (trino P0, proxy teardown, k3s remount, stress/spark otel, start guards) - #852

Merged
rustyrazorblade merged 13 commits into
mainfrom
bug-wave
Jul 22, 2026
Merged

fix: bug-wave — 8 backlog bug fixes (trino P0, proxy teardown, k3s remount, stress/spark otel, start guards)#852
rustyrazorblade merged 13 commits into
mainfrom
bug-wave

Conversation

@rustyrazorblade

@rustyrazorblade rustyrazorblade commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Backlog bugs fixed as one wave — one commit per fix, review commit-by-commit. Being live-validated end-to-end on a real cluster; fixes for anything surfaced during that test are added here.

Closes #827, closes #738, closes #741, closes #823, closes #733, closes #564, closes #162, closes #200, closes #854, closes #855.

Fixes

Invariant

#738/#741 never touch socksProxyHost/socksProxyPort — path resolution, readiness filtering, process lifecycle only (CLAUDE.md / #725).

Validation

Unit + integration tests per fix; CI green (incl. detekt on JDK 21). Live-cluster validation in progress — reboot remount (#823), orphan-tunnel cleanup (#738/#741), stress pod start (#733), trino workers Ready + SELECT 1 (#827); EMR role tags (#564) need a separate spark cluster.

Scope notes

Live validation — 7/8 PASS, $0 (bug-wave @ 4429972)

End-to-end on a real cluster built from this branch. 7 of 8 fixes proven live; #564 (EMR role tagging) code-verified only (unit tests + review; separate EMR run to follow, non-gating).

…738)

Down.cleanupSocks5Proxy() resolved the proxy state file against the process
cwd via a bare relative path, while ProcessSocksProxyService writes and reads
it against context.workingDirectory. The two agree only when cwd happens to be
the workspace directory. When workingDirectory is set explicitly rather than
inherited from cwd (long-running Server/Repl processes, tests), down finds no
state file, returns early, and the ssh -N -D tunnel process survives teardown
as an orphan holding a local port.

Resolve the state file against context.workingDirectory, matching the writer,
so teardown reliably finds and kills the tunnel. Add a regression test that
spawns a real process, records it in a state file under workingDirectory (never
the process cwd), and asserts cleanup kills the process and removes the file.
…ed SOCKS tunnels (#741)

Two narrow follow-ups from the #739 review:

1. Control-node SSH readiness was silently skipped under `up --hosts <alias>`.
   checkSshReady reused the --hosts filter for both the control and db checks.
   A scale-out run like `up --hosts db2` set the filter to {"db2"}, which never
   matches the control alias, so HostOperationsService.withHosts no-op'd and the
   control readiness check verified nothing — reopening the D10 gap the control
   check was added to close. The control node is never what --hosts scopes, so
   run its readiness check with an empty filter; only the db/app check honors
   hosts.hostList.

2. A superseded zombie SOCKS tunnel was never killed. ensureRunning correctly
   rejects a PID-alive/port-not-accepting zombie and starts a replacement, but
   never terminated the stale PID; startNewProxy then overwrote the state file
   with the new PID, leaving the old process unrecorded. Down reads only the
   current state file, so it could never kill the superseded process — it leaked
   and survived teardown. Terminate the stale PID before starting the
   replacement. The kill is best-effort: it never destroys this JVM and never
   throws, so cleanup can't break starting the replacement.

Add regression tests: control readiness still covers control0 under a
--hosts db0 filter, and a real superseded zombie process is killed when a
replacement is started.
The k3s data dir (/var/lib/rancher/k3s) is a symlink onto the instance-store
NVMe mounted at /mnt/db1, but the mount was only established at provision time
with no fstab entry or mount unit. Instance-store data persists across a reboot,
but the mount does not, so after any reboot (kernel update, spot rebalance,
manual recovery) k3s could not find its data dir and crash-looped with
"extracting data: no such file or directory", requiring a manual remount.

Persist the mount and tie k3s to it:

- setup_instance.sh: after mounting, write an /etc/fstab entry keyed on the
  stable XFS filesystem UUID (instance-store device names can change across
  reboots) with nofail + x-systemd.device-timeout so a wiped instance store
  (stop/terminate) never blocks boot. The entry is rewritten idempotently and
  a daemon-reload publishes the generated mnt-db1.mount unit.

- start_k3s_server.sh / start_k3s_agent.sh: install a systemd drop-in with
  RequiresMountsFor=/mnt/db1 so k3s waits for the (nofail) mount before
  starting on every boot, closing the race the nofail option otherwise allows.

Closes #823
The stress Job's otel-sidecar init container read CLUSTER_NAME via a
non-optional configMapKeyRef to a `cluster-config` ConfigMap. That
ConfigMap is created by the separate `grafana update-config` command
and is not guaranteed to exist when a stress job starts, so the kubelet
raised CreateContainerConfigError, the init container looped, and the
Job was marked Failed (backoffLimit=0) — blocking all stress writes.

Inject the value directly from cluster state (`clusterLabelName()`)
instead, removing the fragile cross-command dependency. The value still
matches the `cluster` label used across the observability stack, so
stress metrics line up with Grafana dashboards.
…role

The OTel collector already runs on every EMR node via the bootstrap
action, but the collector config hardcoded node_role=spark for all
nodes, so master and worker telemetry were indistinguishable. It looked
like only the master was reporting because there was no way to tell the
workers apart.

Give each node's collector its own role, consistent with how the K8s
OTel collector derives node_role per node type (db/app/control):

- otel-collector-config.yaml: replace the hardcoded node_role value with
  an unresolved __NODE_ROLE__ placeholder (TemplateService leaves it
  intact at S3 upload time).
- bootstrap-otel.sh: detect the node's role from EMR's
  /mnt/var/lib/info/instance.json (isMaster), resolve spark-master or
  spark-worker, and sed-patch __NODE_ROLE__ in the collector config
  before the collector starts. Every metric/log/trace forwarded from
  that node is now tagged with its role.
- spark-emr spec: document the per-node role labeling scenario.
- EMRProvisioningServiceTest: regression test asserting the uploaded
  bootstrap script resolves the control node IP but leaves __NODE_ROLE__
  for per-node patching, and includes the role detection + sed.
…start

`cassandra start` previously proceeded even when a targeted node had never
been assigned a version via `cassandra use`. On such a node
`/usr/local/cassandra/current` points at nothing, so the systemd start
failed far downstream with a confusing error.

Add a pre-flight check in Start.execute() that inspects every node targeted
by the command (respecting the --hosts filter) and aborts up front with a
clear, actionable message naming the node(s) missing a version, before
touching any node.

Extract the host-filter selection logic from HostOperationsService.withHosts
into a reusable filteredHosts() so the check targets exactly the same set the
start loop would act on.
Previously the restart-and-wait flow could not detect a failed startup. If an
invalid JVM/config was pushed, the nodetool wait loop would break on timeout and
report success, and the port 9042 wait loop had no timeout at all, so it looped
forever. Either way the operator got no error.

The script now polls the systemd unit state (systemctl is-active) on every
iteration and fails fast the moment the cassandra unit enters a failed/exited
state, dumping recent journalctl output so the operator sees the actual cause.
Both wait loops are bounded and exit non-zero on timeout or detected failure,
which the SSH layer surfaces as a RemoteException through CassandraService.restart.
@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review: bug-wave (#852)

Reviewed commit-by-commit as requested. Overall solid — real regression tests (spawned processes, not mock-echo), fabric8/typed-object usage for the K8s env var change, and the SOCKS invariant from CLAUDE.md is respected (only ProcessHandle operations, no socksProxyHost/socksProxyPort touched). One finding looks like it would leave a closed issue still reproducing in production, and one is a real (if narrow) logic bug.

1. #823 fix likely doesn't take effect — wrong start_k3s_*.sh files were patched (high)

The systemd drop-in half of this fix was added to:

  • packer/base/install/start_k3s_server.sh
  • packer/base/install/start_k3s_agent.sh

But neither of these appears to be part of the actual runtime path:

  • packer/base/base.pkr.hcl's provisioner list only runs install/install_k3s.sh (which downloads k3s but explicitly does not start it — "installation happens at runtime"). It never references start_k3s_server.sh/start_k3s_agent.sh.
  • Nothing under src/main/kotlin references start_k3s_server.sh or start_k3s_agent.sh (underscore names) at all.

The scripts actually executed on real nodes are the separate, hyphenated copies under resources:

  • src/main/resources/com/rustyrazorblade/easydblab/services/start-k3s-server.sh
  • src/main/resources/com/rustyrazorblade/easydblab/services/start-k3s-agent.sh

...loaded via K3sService.SERVER_SCRIPT_RESOURCE / the agent equivalent in K3sAgentService.kt, uploaded, and run over SSH. I checked both — neither has the RequiresMountsFor=/mnt/db1 drop-in added in this PR. So after this merges, a real reboot will still race k3s startup against the /mnt/db1 remount and crash-loop with "extracting data: no such file or directory," i.e. the scenario #823 is closed against still reproduces.

The setup_instance.sh half of the fix (fstab UUID entry) is correct — that file is genuinely loaded at runtime (Init.ktextractResourceFile, executed via SetupInstance.kt). It's only the drop-in half that landed in dead files.

Suggested fix: port the RequiresMountsFor block (and matching comment) into src/main/resources/.../services/start-k3s-server.sh and start-k3s-agent.sh instead of (or in addition to, if the packer copies are meant to stay in sync for some reason I'm not seeing) the packer/base/install/ versions.

2. restart-cassandra-and-wait: the two wait loops share one timeout budget (medium)

start_time is set once, before the nodetool status loop, and reused unmodified for the ss port-9042 loop's elapsed-time check:

start_time=$(date +%s)
while true; do            # nodetool wait
    ...
    elapsed=$(( $(date +%s) - start_time ))
    if [[ "${elapsed}" -ge "${READY_TIMEOUT}" ]]; then fail ...; fi
done
...
while ! ss -tulwn | grep -q ':9042'; do   # port wait — same start_time, same 240s budget
    elapsed=$(( $(date +%s) - start_time ))
    if [[ "${elapsed}" -ge "${READY_TIMEOUT}" ]]; then fail ...; fi
done

If nodetool status takes, say, 220s of the 240s budget to succeed (well within its own timeout), the port-wait loop inherits only ~20s before it fails — even though the error message says "Timed out after 240s waiting for CQL port 9042 to open", which is misleading (it didn't wait 240s for the port) and could produce a false failure on an otherwise-healthy, just-slow-starting node. Given the PR's stated intent is "bounded wait loops" (plural, each meaningfully bounded), this reads like an oversight rather than intentional shared budgeting. Resetting start_time before the second loop would fix it.

Minor

  • ProcessSocksProxyService.terminateStaleProxy KDoc states "A no-op if the PID is non-positive, already gone, or this JVM" twice (once in the second paragraph, once repeated at the end of the third). Not a functional issue, just a bit of redundant doc text worth trimming.

What looks good

…tart

web-ui.authentication.type and web-ui.user were set under the shared
additionalConfigProperties, which the trinodb/trino Helm chart applies to
both coordinator and worker config.properties. web-ui.* are coordinator-only
properties (the web UI runs only on the coordinator), so workers flagged them
as unused and crashed on startup with exit 100 / CrashLoopBackOff.

Move both properties into server.coordinatorExtraConfig, the chart's
coordinator-only config key, which is appended solely to the coordinator's
config.properties. The web-UI auth config is preserved; workers now start
clean.

Closes #827
@rustyrazorblade rustyrazorblade changed the title fix: bug-wave — 7 backlog bug fixes (proxy teardown, k3s remount, stress/spark otel, start guards) fix: bug-wave — 8 backlog bug fixes (trino P0, proxy teardown, k3s remount, stress/spark otel, start guards) Jul 22, 2026
@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review of #852

Reviewed commit-by-commit as requested. Solid set of fixes overall — good regression-test discipline (real spawned processes for the proxy/tunnel tests, real Koin-wired state for Start/Up). One fix appears to not actually take effect at runtime; flagging that as the main thing to check before merge.

🔴 #823 fix (NVMe remount) — RequiresMountsFor drop-in lands in dead files, not the live boot path

The fstab/UUID half of the fix (src/main/resources/.../commands/setup_instance.sh) is wired up correctly — SetupInstance.kt/Init.kt confirm it's live.

But the systemd drop-in half (RequiresMountsFor=/mnt/db1 on k3s.service/k3s-agent.service) was added to:

  • packer/base/install/start_k3s_server.sh
  • packer/base/install/start_k3s_agent.sh

These two files are not referenced anywhere — not in packer/base/base.pkr.hcl (only install_k3s.sh runs there, and it installs but leaves k3s disabled), not in packer/docker-compose.yml's local test target, and not by any Kotlin code. The scripts that actually run k3s on the instance are separate, divergent files:

  • src/main/resources/com/rustyrazorblade/easydblab/services/start-k3s-server.sh (uploaded/executed by K3sService.kt via SERVER_SCRIPT_RESOURCE/uploadScript)
  • src/main/resources/com/rustyrazorblade/easydblab/services/start-k3s-agent.sh (uploaded/executed by K3sAgentService.kt)

Diffing the two pairs confirms they've already drifted (the resource versions have --flannel-backend=none handling the packer versions don't; the packer versions have a /var/log/pods NVMe-relocation block from a prior change that never made it into the resource versions either). So:

  1. The new RequiresMountsFor drop-in never gets installed on real instances — the race between /mnt/db1 remounting and k3s starting after a reboot (the actual thing bug: k3s data dir on instance-store NVMe is not remounted on reboot → k3s crash-loops #823 is about) is still open.
  2. This also means the earlier k8s-pod-log-collection change's /var/log/pods NVMe relocation is silently not applied on real clusters either, since it lives in the same dead files — worth checking whether that one is actually working today.

Suggest porting the RequiresMountsFor drop-in (and reconciling the log-relocation block) into the two src/main/resources/.../services/start-k3s-*.sh files, and considering whether packer/base/install/start_k3s_*.sh should be deleted to prevent this divergence from happening again.

Everything else looks good

Minor

  • ProcessSocksProxyService.terminateStaleProxy KDoc has a redundant sentence (the "no-op if the PID is non-positive..." caveat appears twice, once inline in the "Best-effort by design" paragraph and once as its own final sentence).

No CLAUDE.md concerns (no backwards-compat handling added, no socksProxyHost/Port touched, no raw YAML strings built for K8s configs).

@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review

Went through this commit-by-commit as suggested in the description. Overall this is a well-executed bug-wave: each fix is scoped tightly, comes with a real test (not mock-echo), and the code comments consistently explain why rather than what — matches the repo's conventions well.

Strengths worth calling out

  • Down.cleanupSocks5Proxy() resolves proxy state file against cwd, not context.workingDirectory — orphans the ssh tunnel #738/Two follow-ups from #739 review: control-node readiness gap under --hosts; leaked zombie SOCKS ssh survives down #741 (proxy path resolution / control-node readiness)Down.cleanupSocks5Proxy() now resolves the state file against context.workingDirectory, which is exactly how ProcessSocksProxyService writes it (verified both call sites use the same File(context.workingDirectory, Constants.Vpc.SOCKS5_PROXY_STATE_FILE) construction). The terminateStaleProxy() addition only fires after isValidProxy() has already determined the recorded proxy is unusable, so it wont kill a genuinely healthy tunnel. Good use of real spawned ProcessBuilder("sleep", "60") processes in the tests instead of mocks — actually proves the kill happens.
  • Up.checkSshReady — making the control-node check always use an unfiltered host list (hostFilter = "") rather than the --hosts scale-out filter is the right call; otherwise HostOperationsService.withHosts silently no-ops on zero matches and the control check becomes a no-op. The new test (ssh readiness still covers the control host under a scale-out --hosts filter) directly exercises this.
  • cassandra start version guard — clean extraction of HostOperationsService.filteredHosts() as shared logic between withHosts and the new pre-flight validation, avoiding duplicated filter logic. Three focused tests cover the fail/pass/filtered-subset cases.
  • k3s NVMe remount (bug: k3s data dir on instance-store NVMe is not remounted on reboot → k3s crash-loops #823) — the fstab-by-UUID + nofail + per-service RequiresMountsFor drop-in combo is solid: the drop-in is written once (gated on mountpoint -q) but persists as a static systemd unit file, so it keeps protecting against the race on every future reboot without needing the start script to run again. Good call using UUID instead of device path since instance-store device names can shift across reboots.
  • Trino web-ui fix — moving web-ui.* into server.coordinatorExtraConfig instead of the shared additionalConfigProperties is a correct read of why workers were crash-looping (exit 100 on unrecognized worker-side config), and the PR notes it was checked against helm template output.
  • Stress otel sidecar CLUSTER_NAME — good fix; removes a hard dependency on an optional cluster-config ConfigMap thats created by a separate command (grafana update-config) and may not exist yet. clusterLabelName() has a single call site, so no risk of drift elsewhere.
  • EMR node-role tagging — sensible avoidance of jq (not guaranteed present on the EMR AMI, and a missing-jq failure under set -e would abort the whole bootstrap) in favor of a grep -E check against instance.jsons isMaster field.

Minor observations (non-blocking)

  • restart-cassandra-and-wait: check_service_alive treats active and activating as alive, which is correct for Type=simple. The script now has non-trivial state-machine logic (two independent timeout budgets, fail() invoking journalctl), but theres no packer/docker test exercising it — the existing testPacker* Gradle tasks target packer/*/install/* scripts, not this one. Given the PR is doing live-cluster validation anyway thats probably fine, just flagging that a regression here would only be caught live rather than in CI.
  • bootstrap-otel.shs grep -Eq isMaster check assumes instance.json keeps the field on a single line without a newline between key and value — true for EMRs actual format today, just noting the assumption since theres no test on the shell parsing logic itself (EMRProvisioningServiceTest only asserts the uploaded script contains the right markers, not that the parsing behaves correctly against a sample instance.json).
  • Nice touch keeping Down.cleanupSocks5Proxy and ProcessSocksProxyService.terminateStaleProxy scoped internal (not private) purely for testability, with KDoc explaining the reasoning — consistent with how the rest of the codebase handles this tradeoff.

No security, backwards-compatibility, or storage-sizing concerns applicable here per this projects conventions (ephemeral clusters, local-storage PVCs). Didnt spot wildcard imports, System.out usage, or missing class-level KDoc in the touched files.

…PollInterval Duration default applies (fixes stress + status)
@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Reviewed all 12 commits. This is a well-executed batch of bug fixes — good adherence to repo conventions (never touches socksProxyHost/socksProxyPort, fail-fast validation, real-process regression tests instead of mock-echo tests, KDoc explaining why not what). A few notes below, nothing blocking.

Solid fixes, no concerns

Worth a follow-up (non-blocking)

  • Commit 3/12 (bug: k3s data dir on instance-store NVMe is not remounted on reboot → k3s crash-loops #823) initially patched dead files. packer/base/install/start_k3s_agent.sh / start_k3s_server.sh aren't wired into packer/base/base.pkr.hcl, aren't run by testPackerBase/testPackerCassandra (per docker-compose.yml's test-base service list), and aren't what ships to instances — that's src/main/resources/.../services/start-k3s-{agent,server}.sh, embedded as classpath resources and pushed via K3sService/K3sAgentService. Commit 9/12 correctly relocates the fix to the live files and reverts the dead ones, so the final state is correct, but it's worth deleting the orphaned packer/base/install/start_k3s_*.sh files in a follow-up — they've already caused real confusion once (an openspec/changes/k8s-pod-log-collection proposal edited these same orphaned copies under the belief they were live). Leaving duplicate near-identical scripts around invites the same mistake again.

Minor

  • bootstrap-otel.sh role detection falls back to spark-worker if /mnt/var/lib/info/instance.json is missing or unreadable (2>/dev/null), which would silently mislabel a master node rather than fail loudly. Given EMR reliably provides this file, this is a very unlikely edge case — not asking for a change, just flagging for awareness.

No security or performance concerns. Test coverage is thorough throughout (real spawned processes for the process-lifecycle bugs, a real Koin container for the DI bug, targeted fixture updates rather than loosened assertions).

@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review

Reviewed commit-by-commit as requested. Overall this is a well-scoped bug-wave: each fix is narrow, has a root-cause explanation in the commit body, and (for the Kotlin fixes) comes with a real regression test that exercises actual process/state behavior rather than mock-echo assertions. Nice adherence to the project's "minimal mocking / real TestContainers-or-real-process" testing philosophy (ProcessSocksProxyServiceTest, DownTest both spawn real sleep processes and assert they're actually killed).

Correctness

  • ProcessSocksProxyService.terminateStaleProxy (proxy/ProcessSocksProxyService.kt:311) — this is the one place I'd push back on before merging. It force-kills loaded.pid purely by PID number via ProcessHandle.of(stalePid.toLong()).ifPresent { it.destroyForcibly() }, with no check that the PID still refers to the same ssh process it was recorded for. PIDs get recycled by the OS; if the original tunnel process has already exited and enough process churn has happened since, ProcessHandle.of(stalePid) can resolve to a completely unrelated, currently-running process, and this now force-kills it. Before this PR the code only ever read liveness (isAlive) — this PR is the first place that acts destructively on a bare recycled PID. Given ensureRunning can run repeatedly over a long-lived Server/Repl process, the window for this is small but non-zero.
    Suggest gating the kill on handle.info().command() containing "ssh" (or matching the tunnel's expected argv) before calling destroyForcibly(), so a PID collision degrades to a no-op instead of killing an arbitrary process. Cheap to add and matches the "best-effort, must never do harm" spirit already documented in the KDoc.

  • Up.checkSshReady / --hosts fix (commands/Up.kt:603) — correct and the regression test (UpTest: "ssh readiness still covers the control host under a scale-out --hosts filter") genuinely exercises the bug (asserts control0 is checked even when the CLI filter is db0), not just that a mock was called.

  • Start.validateVersionsAssigned (commands/cassandra/Start.kt:260) — good fail-fast check, and the extraction of HostOperationsService.filteredHosts so the pre-flight check targets exactly the same set withHosts would act on (rather than duplicating the filter logic) is the right call — avoids the two ever drifting apart.

  • ServicesModule / DefaultStressJobService wiring (services/ServicesModule.kt:95) — switching from singleOf to an explicit single<StressJobService> { DefaultStressJobService(get(), get(), get(), get()) } is correct (confirmed against the constructor: k8sService, clusterStateManager, eventBus, templateService, jobPollInterval = ...), and ServicesModuleTest is a good addition — it guards the reason (Koin's singleOf doesn't honor Kotlin default parameter values) rather than just re-asserting the binding exists, so it'll actually catch a regression back to singleOf.

  • Trino web-ui fix (kits/trino/values.yaml.template) — moving web-ui.* into server.coordinatorExtraConfig is the correct chart-level fix for the coordinator-only property crash. Matches the existing template-resource pattern (no raw-YAML-in-Kotlin violation).

  • k3s NVMe remount (setup_instance.sh, start-k3s-{server,agent}.sh) — UUID-keyed fstab entry with nofail + RequiresMountsFor is the right shape for surviving reboot while not blocking boot if the instance store was wiped. One thing worth double-checking during the live-cluster validation already underway: blkid -o value -s UUID "$DISK" runs immediately after mkfs.xfs $DISK in the fresh-format branch — worth confirming blkid reliably picks up the just-created UUID rather than a stale/cached value on the AMI's base image (usually fine on modern kernels/libblkid, but it's the kind of thing that only shows up on first-provision).

  • EMR per-node role tagging (bootstrap-otel.sh) — the grep -Eq '"isMaster"...true' approach instead of jq is a reasonable, explicitly-documented trade-off given jq isn't guaranteed on EMR AMIs. Fine as-is; just fragile if EMR ever reformats instance.json (e.g. multi-line pretty-printing) — low risk, not blocking.

  • restart-cassandra-and-wait — the fix correctly gives the CQL-port wait its own timeout budget (start_time is reset) rather than sharing the nodetool loop's already-partially-consumed budget. Good catch, and polling systemctl is-active in both loops closes both the "hangs forever" and "false success" failure modes described in the commit.

Test coverage

  • Kotlin-side fixes are all backed by real regression tests, several of which spawn genuine child processes to prove a PID gets killed rather than just asserting a mock was invoked — matches the project's "no mock-echo tests" rule well.
  • The shell-script changes (setup_instance.sh, start-k3s-server.sh, start-k3s-agent.sh, bootstrap-otel.sh) have no automated coverage, but that's consistent with these files sitting outside the existing packer/ testing harness (testPackerBase/testPackerCassandra only cover packer/base and packer/cassandra, not src/main/resources/.../commands/setup_instance.sh or the k3s scripts) — not a regression introduced by this PR, and the description notes live-cluster validation is in progress for exactly these paths.

Minor / style

  • Nothing else stood out — no wildcard imports, no Event.Message/Event.Error misuse, no positional CLI params introduced, println/events usage untouched by this PR.

Summary

Solid, well-tested bug wave. The one thing I'd actually want addressed before merge is hardening terminateStaleProxy against PID reuse before it force-kills based on a bare PID number — everything else is either correct as-is or a pre-existing/documented trade-off.

@rustyrazorblade
rustyrazorblade merged commit 12c724b into main Jul 22, 2026
12 checks passed
@rustyrazorblade
rustyrazorblade deleted the bug-wave branch July 22, 2026 21:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment