DreamLake

Compose capacity

Status: Implementation merged; release, deployment and remote acceptance unverified. This note is proposed documentation; its live publication has not been verified. Tracking: Master #247, compose issue #22, merged implementation PR #24. No Ge review or testing status is asserted.

2026-09-12 — count convergence and safe local recovery

Compose retains daemon groups with an explicit non-negative count setpoint. Lowering four to two terminates confirmed surplus through the provider endpoint; zero retires a group. Pending or uncertain workers occupy launch slots but cannot justify terminating a healthy survivor. Victims are selected by exact project and numeric-suffix group ownership, unhealthy first, then newest creation time and higher worker ID. Gone/stopping history no longer counts as capacity.

Queue elasticity was not substituted for compose: it owns queue membership rather than project/group identity, does not preserve the complete group launch template, and its reviewed production scale-down path deletes worker rows without provider termination. Keep queue elasticity fixed for compose-owned fleets.

The follow-up adds a local-user lock shared by up and down, independent of compose file path and selected group. The key includes server URL, authenticated namespace and project. A journal records each mutating intent before dispatch. Failed or ambiguous requests and process death retain the lock, blocking blind retries. No PID/age-based stale-lock takeover is performed. Requests already in flight may finish; new mutations stop once uncertainty is observed.

Both commands reject a compose/auth namespace mismatch before network access. down --terminate now reports provider failure, ignores foreign-project workers and already-stopping hosts, and up --wait returns nonzero on startup timeout. These are host/daemon capacity operations, separate from user-space jobs. This work does not change nymph job supervision or vault SSH sync.

Validation and merge evidence

At tested implementation head adb528673bc98cc68a989c63e314bd237eb6db86:

  • TypeScript build passed.
  • All 44 compose/locking regressions passed.
  • A clean git archive HEAD export, excluding unrelated dirty source, passed npm test: 641 passed, one skipped, zero failed; type checking is included.
  • Tests cover four-to-two shrink, zero count, retained history, pending survivors, group ownership/order, concurrent local processes, SIGKILL during pending intent, lost launch responses, failed termination with a missing CP row, namespace mismatch, explicit recovery and startup timeout.
bash
npm run build
node --import tsx --test src/cli/__tests__/compose.test.ts src/cli/__tests__/compose-lock.test.ts
npm test

PR #24 merged as eec3335f7ef7a3918a7e49b7cef4f2e9d7a3ada9 at 2026-09-13 00:50:13 UTC (2026-09-12 Pacific). The tests above apply to the tested PR head; they are not a deployed-system acceptance claim. Provider calls used synthetic HTTP fixtures; the process-crash test used a local child process. Detailed readiness evidence.

Recovery and remaining gaps

Locks live under $XDG_CONFIG_HOME/lakeshore/compose-locks, defaulting to ~/.config/lakeshore/compose-locks. To recover, ensure the original writer has stopped, inspect journal.json, and reconcile every recorded outcome with the control plane and provider, including resources whose worker row disappeared. Only then remove the specific lock directory manually and run up --plan. Do not delete a live writer's lock.

  • Different hosts, users, config roots, server aliases, older clients and other controllers do not share this lock. Distributed leases, fencing, idempotency and atomic reconciliation still need control-plane work; serialize externally.
  • Automatic crash/wedge reaping and retry/backoff remain outside this one-shot command. Pending/unknown workers retain occupancy. Power-loss recovery was not tested.
  • Some CP versions delete worker rows despite provider termination failure. Local journals block blind retries but do not restore missing ownership or roll back cloud actions.
  • Termination can interrupt running jobs; compose does not manage their lifecycle.
  • Real-provider acceptance, package release, deployment and live verification remain unverified. See Unreleased release notes.

Future coherent changes to this note require an Unreleased update and a Slack DM to Ge with source links until publication is verified. Sent-message evidence is recorded in issue #22 and PR #24, separately from implementation and delivery status.