Next-wave plans
Implementation plans produced by the read-only design pass (workflow
weat4nfq3, 2026-06-22). Companion to DRIFT-REGISTER.md
(the backlog) and the ADRs. Most waves have since landed and
are collapsed to a status tombstone (heading + what shipped); the still-open waves
keep their full plan. Pick an open wave, re-verify the sites against HEAD, and land
serially.
Wave: Structural context ownership (subsystem loops) — ✅ LANDED 2026-06-22
Section titled “Wave: Structural context ownership (subsystem loops) — ✅ LANDED 2026-06-22”Status: Done (commit 1b746f6a7). The daemon-lifetime ctx is threaded through
each loop subsystem’s DepsBuilder (DaemonCtx field); backup_mount.go’s active
reaper-loop bug + the latent landmines (collectors/docker_events/container_recorder)
- the
node_self_register/config_loader/unix_socketBackground→DaemonCtx migrations all landed, plus the OnLeader-ctx bug class (schedule_dispatcher + swarm_recorder) the judge caught. Fitness Check 7 (subsystem-loop-start-ctx) enforces it.
Wave: Phase B — launcher injection — ✅ LANDED (#106)
Section titled “Wave: Phase B — launcher injection — ✅ LANDED (#106)”Status: Done, but via a deliberately different design than originally
planned — the docker-label approach was rejected. What shipped: the launcher is
pure scoped config (the launcher key, resolved deployment >
stack > environment > tenant > global, then the app.yaml launcher: default). The
launcher *bool state hybrid (models/types/repository/CLI --launcher/--no-launcher)
was deleted and the deployment.launcher column retired. Injection does NOT emit
docker labels — it rides the shc-oidc gating seam: writeIngressRouteFile defines a
per-deployment shc-launcher-<project> rewritebody middleware in the file-provider
route file (cross-node safe under exposedByDefault: false), and MaterializeIngress
re-resolves per sweep via launcherForDeployment. Live in
modules/app/launcher.go (launcherForDeployment),
modules/app/inject_traefik.go (shc-launcher-<project> +
auth.RewritebodyMiddleware()), and auth.EffectiveLauncher (now a scoped-config
resolver).
Remaining Runtime-singleton families (the broader “no legacy” backlog)
Section titled “Remaining Runtime-singleton families (the broader “no legacy” backlog)”The *Service ambient-pull locator is fully gone (di-singletons 0, hard). But
func SetRuntimeService was only ONE family — the broader func SetRuntime* grep
now finds 8 files (fitness Check 1b di-runtime-families, ratchet ceiling 8,
falling). app.RuntimeStackRepo was swept + deleted (commit 4dac5314c,
2026-06-22) and stack.RuntimeState was swept by the ADR-0002 / B1 state-model
collapse (2026-07-04 — the wired stack *State now lives solely on the
composition root, Services.StackState(); ceiling fell 9→8). The rest:
- Deliberately RETAINED bridges (do not naively delete):
ca.RuntimeStore(modules/ca/register.go) — theshc ca …CLI runs in-process with NO composition root and falls back throughGetRuntimeStore. Killing it needs the CLI to route CA ops over HTTP (a feature change). NOTE:keycloakMasterRootFn(services.go) is one of its daemon readers but is the keycloak-backchannel master-CA trust seam — OFF-LIMITS, don’t convert it.— SWEPT (ADR-0002 / B1, 2026-07-04).stack.RuntimeState(modules/stack/state.go)Set/GetRuntimeStateis retired; the wired stack*Stateis on the composition root (Services.StackState()), andstack/router.go+app/router.goresolve it there. No longer a retained bridge.
- ✅ SWEPT (2026-06-23, parallel-worktree generation + serial merge):
internal_stacks.SetRuntime(dead-deleted),job.Enqueuer,backup.RecoveryActions,backup.Scheduler,hud.Handler— all onto the composition root (accessor + consumer-seam resolution + singleton deleted).backup.MountSubprocService: daemon HTTP path converted to the root (dual-write); singleton RETAINED as theshc backup mountCLI in-process bridge (and it’sSetMountSubprocService, notSetRuntime*, so outside the ratchet).di-runtime-familiesratchet: 13 → 9, then 9 → 8 after thestack.RuntimeStatesweep above (current ceiling 8). - FOUNDATIONAL — keep (legitimately process-wide infra, NOT legacy):
core/operations/store.go(async-op SSE event registry, read cross-package via EnsureRuntime),subsystems/impl/ent_client.go(THE single DB-connection publish seam),modules/vault/state.goRuntimeFactory(fires during config.Load before the root exists),modules/app/commands/preset.go(stateless CLI-only). Deleting these would be wrong — the ~9 ratchet floor IS these + the bridges, a desirable floor not 0. - Deferred (cascade-heavy / low ROI):
modules/storage/register.goConfigService (a self-contained dead web — register funcs + a dead daemon file + koanf store + wireStorageProductionDrivers + an os/exec keep-alive),modules/backup/mount/manager.go. - Retained bridges:
modules/ca/register.go(CLI),modules/test/register.go(test-runner harness). (modules/stack/state.gowas here but is now SWEPT — see above.)
Also out of this family but same shape: ~15 SetXResolver push-resolver package
globals (SetDNSResolver, SetVaultResolver, SetScopeResolver, …) — setter
injection via a package var. Milder than ambient-pull; ADR-0006 partially endorses
“inject a resolver”. Convert to threaded Deps only where it’s cheap.
Wave: App-module DI sweep (the final 2 singletons) — ✅ DONE 2026-06-22
Section titled “Wave: App-module DI sweep (the final 2 singletons) — ✅ DONE 2026-06-22”Status: Complete. di-singletons and di-test-fixtures ratchets are both at 0
(the whole DI legacy sweep is done). Landed in 4 commits (4b611d6fa recorder threading,
01373968f state-mirror + cmdAdapter, e7fabf0d7 daemon/subsystem/CLI readers,
d6e540757 delete the singletons). The state_mirror.go block was lifted on explicit
user instruction (“do it anyway”) and converted with a pure reader-mechanism swap
(resolveAppService, zero logic change). Adversarially verified (recorder-regression /
behavior-equivalence / completeness — zero findings). KEY non-finding: the design’s
“router gets nil d.Services” blocker was a FALSE POSITIVE on the dead startAPIServer
shim — the live router sites (unix_socket.go, internal_api.go) thread the root,
now guarded by tests/unit/cli/cli_test.go’s router-deps checks. NOTE: app’s
Set/GetRuntimeStackRepo + SetIngressIntentRecorder are SEPARATE singleton families
(not SetRuntimeService-named, not counted) and remain by design.
Wave: State-model collapse (ADR-0002) — DONE (B1, 2026-07-04)
Section titled “Wave: State-model collapse (ADR-0002) — DONE (B1, 2026-07-04)”Implemented. state_mirror.go is deleted; the app Service routes every stack
read/write through one active deploymentStore (the DB deployment table on the
daemon via SetStackRepo, the in-memory app.State offline when repo==nil — NOT
a read-through cache, the ADR’s ratified Option A). Node canonicalisation moved to
the DB write path (dbStore.stampNode); the mirror funcs
(mirrorStackRow/mirrorStackRowErr/mirrorStackDelete/MirrorAllToRepo/
nodeIDResolver/SetNodeIDResolver) plus the memory→DB boot backfill and the 5s
disk-reconcile ticker are gone. ReconcileDiskStacks is demoted to a one-shot,
readiness-gated ImportOrphanManifests boot import (tombstone branch dropped —
delete is atomic, no resurrection window). RegisterRecoveredStack is KEPT and
rewired to a direct store write (the cross-node node+runtime recording regression
guard, HANDOFF fix 25617e486); the swarm-only node-pin gate in
services_seams_clone.go is preserved. shc.stack.status has one owner
(app/router.go). Step 0/1 (manifest.json rename + legacy state.json
read-fallback) shipped earlier under T17.
Still owed: a fresh 3-node proxmox clone/recover/move + shc ls/status
re-prove on real hardware (the final field gate).
Wave: Duplicate-impl dedup (UDS dialer → daemonclient) — ✅ LANDED
Section titled “Wave: Duplicate-impl dedup (UDS dialer → daemonclient) — ✅ LANDED”Status: Done. The canonical helpers UDSTransport / UDSClient / UDSBaseURL
exist in core/daemonclient/client.go (~194-248) and the inline dialer
copies were repointed onto them: the uds-dialer-copies ratchet is now at 0
(non-test, non-daemonclient DialContext(ctx, "unix", …) copies = 0), and the
arch-fitness.sh ceiling is a hard 0.
Wave: Migration squash (→ single SQL)
Section titled “Wave: Migration squash (→ single SQL)”Squash all ent migrations back to ONE baseline SQL migration. There was a prior
squash (squashes.json folds 2 pre-squash migrations into 20260612043922_auto.sql) —
extend it to a single file. ce’s make migrate is generate-only (atlas hash + diff);
runtime apply is a separate startup runner. CAUTION (memory migrations_squashed):
a squash means pre-squash DBs fail daemon startup until wiped — fine pre-1.0 but the
squash must be a clean baseline that a fresh DB applies in one shot.
Wave: Test relocation (extractable test tree)
Section titled “Wave: Test relocation (extractable test tree)”Move tests so they can later be pulled into a SEPARATE git repo (tests may not be
open-sourced). Go constraint: white-box tests (package foo, accessing unexported
symbols) MUST live next to the code; only black-box (package foo_test) + integration
- e2e + bats can relocate cleanly. The recon must classify: which tests are white-box
(stay, or convert to black-box) vs already-relocatable, and design the layout
(tests/unit/
/ already exists for some). The arch-fitness di-test-fixtures ratchet + the per-package test runs assume current locations — update both.
Wave: CLI performance (max responsiveness)
Section titled “Wave: CLI performance (max responsiveness)”Goal: make shc CLI commands much faster. CONSTRAINTS (user): NO aggressive
lazy-loading that risks state desync; safe caching OK; maintainable; the biggest win
is likely eliminating redundant round-trips + repeated pre-flight checks (auth /
capability / version checked again and again) + N+1 daemon-side queries. Trace the hot
commands end-to-end (CLI process init → UDS/auth handshake → per-command daemon calls
→ handler DB/docker cost) and produce a prioritized, safe optimization plan.