v0.4.1 · disposable previews · mesh isolation · bucket websites Agents ship · humans inspect
← Engineering log

app release: commit to live in one bounded loop

One idempotent command observes the build, pins the image by digest, deploys, waits for convergence, and optionally verifies the public route, with stable exit codes agents can loop on.

The original Mechanics deploy loop asked an agent to chain four commands: wait for the build, verify the image, deploy the tag, wait for convergence. Each step had its own failure modes and its own retry etiquette. Starting with 0.3.11 that chain collapsed into one command, and by 0.3.30 it became the default shape of shipping on Mechanics.

the whole loop

ssh -T mechanics.run -- app -a myapp release --latest-success --json
# or pin an exact commit:
sha=$(git rev-parse HEAD)
ssh -T mechanics.run -- app -a myapp release --commit "$sha" --json
# exit 0 live · exit 2 retry after retry_after · exit 3 app blocked

One command, stable exit codes

app release observes the commit build, resolves the image to an immutable tag@digest target, deploys it, waits for digest-verified rollout convergence, and, with --route, confirms the public route reaches the released instance. One invocation now drives the full pipeline inside a 60-second budget, re-observing pending stages every 5 seconds. Exit 0 means live, 2 means still progressing and safe to retry the identical command, 3 means app state blocks the release, 4 means platform state does. Since 0.3.19, --latest-success releases the newest successful build without any local Git inspection.

Digests decide readiness, not tag text

Since 0.3.13 readiness is judged from runtime imageID digests, not status tag strings, so a stale pod from the previous ReplicaSet can never pass for the new release. When a registry rebuilds an index with identical runnable content but new attestation descriptors, 0.3.33 added equivalence proof: the runtime digest is resolved read-only against the registry and matched by platform manifest instead of looping forever on a false mismatch.

Bounded waits everywhere

The same discipline spread to the rest of the surface. repo build status --commit is a cheap one-shot probe. app wait blocks by default and caps at 60 seconds. route wait, secret wait, and registry credential wait share the same budget. Every pending state exits 2 with a retry_after and the exact next command. No agent should ever have to invent a polling strategy.

Retry the same command until it says live: that is the entire contract.