Files
multica/scripts
8d2da6b8db MUL-6479 feat(dev): make a local environment a named object with one verb per lifecycle step (#7325)
* MUL-6479 feat(dev): make a local environment a named object with one verb per lifecycle step

Local development had six entry points, thirty-four make targets and no way to
see or delete what they created. `make up` / `status` / `list` / `down` /
`destroy` / `gc` / `env-exec` replace that with one set of verbs, shared by
humans and agents, with `C=api,web,daemon,desktop` selecting components.

Three defects made the old flow untrustworthy rather than merely verbose.

1. The database the tooling created was not the one the application reached.
   `ensure-postgres.sh` creates through `docker exec`; when a native PostgreSQL
   owns 5432 the container never binds the host port, so the create lands in a
   server the backend never talks to. It printed "✓ PostgreSQL ready" and the
   next step died with `SQLSTATE 3D000`. On the machine this was written on the
   two servers had already diverged: 94 same-named databases, 69 more in the
   container with no counterpart. `up` now creates through DATABASE_URL and
   treats a successful `migrate up` — which pings that same string — as the only
   proof; failure diagnoses the port owner instead of reporting a missing
   database.

2. Identity was computed, not allocated. Ports, database names and CLI profiles
   all came from `cksum($PWD) % 1000`, and a collision was silent: the loser
   fails to bind while the winner keeps answering 200. Allocation now happens
   under a lock, probing from the same path hash so a checkout keeps its
   familiar numbers, and requires the slot to be both unregistered and free of a
   live listener. The manifest in ~/.multica/dev/envs/<name>/ is what every
   later command reads.

3. Creating an environment left no record of how to destroy it, so there was no
   destroy — only archaeology. `down` stops processes and keeps the data;
   `destroy` consumes the manifest: processes, database, profile, slot. `list`
   makes leaks visible and `gc` collects environments whose checkout is gone or
   whose TTL passed.

/health now reports pid, commit and started_at. A 200 alone proves something is
listening, which is exactly what let a failed restart keep serving the previous
build; `up` compares started_at against its own launch and refuses an answer
that predates it.

Two constraints are enforced rather than documented: the daemon is started from
a built server/bin/multica, never `go run` (the daemon re-execs its own path for
every task, and `go run` deletes that binary when the launcher exits), and
`C=daemon` under a .multica/daemon_task_context.json marker stops with that
explanation up front instead of spending a login on a refusal.

scripts/dev-env.test.sh covers the registry verbs against a throwaway
MULTICA_DEV_HOME with no services running, and pins the regression that made
`down` exit 1 after reporting success: on bash 3.2 a command substitution whose
function ends in a failing command aborts the script under `set -e`, and "no
process is listening" is that function's normal answer.

`make dev` is unchanged and still runs in the foreground.

Co-authored-by: multica-agent <[email protected]>

* fix(dev): close local environment lifecycle gaps

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Lambda <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-23 16:43:58 +08:00
..