Deploying Orchestrata

python scripts/promote_env.py promote --to prod --yes

That is the whole thing. It advances the target tree, syncs dependencies, installs the systemd units, restarts the services, proves they serve, bootstraps both graphs, and ingests every workspace project's AST into the operational KG. If verification fails after the restart, it returns the target to the sha it was serving.

Always look before you leap:

python scripts/promote_env.py promote --to prod --dry-run

What promote used to mean, and why it changed

Until 2026-08-10 promote wrote a receipt — gate satisfied, target reachable, timestamp — and stopped. Nothing moved. The consequence was measured that day: the ledger read PROMOTED while prod served a tree 76 commits behind, and no part of the system could tell, because the record contained no sha to compare against anything.

The ledger now records the deployed sha, and status compares it to the target's real HEAD:

python scripts/promote_env.py status
# prod  validate=PASS@2026-08-10T13:07  promote=DEPLOYED@... sha=4a1cb40fd693
#   drift: target matches ledger at 4a1cb40fd693

The stages

Stage Does Fails when
resolve Pins the commit Source tree dirty, or the commit is on no remote
infra TCP-probes Redis + graph The profile is incomplete, or the fleet is not there
tree fetch + checkout on the target Target is not a checkout, fetch/checkout fails
deps uv sync --frozen when the lock moved sync fails. SKIP when the lock is unchanged
assets Runs deploy/systemd/install.sh A hook is non-executable in the commit
services Restarts the units —
verify Polls units + ports to a deadline A unit is not active, or a port is not listening
graphs bootstrap_graphs.py --all Bootstrap fails
ingest serialize → ast-to-kg, per project Every project failed

SKIP is success, not silence: re-running a completed deploy should print a wall of SKIPs, which is how you see idempotence held.

verify exists because a restart is not evidence. A unit can sit active while its socket refuses connections, and a port can answer because the OLD process still holds it — so both are checked. This is the runtime twin of the suite's kernel rule: a run that does not verify the kernel is false-green, and so is a deploy whose only evidence is that systemd did not complain.

What it refuses

Worktrees are excluded, structurally

ingest walks the workspace and skips worktrees by testing whether .git is a file (worktree) or a directory (repo) — never by name. Ingesting one would duplicate every node of a repo already present, under a second path that drifts the moment the branch does.

Measured on 2026-08-10: 31 projects, 2 worktrees excluded, 25 non-repo directories ignored.

Environment isolation

Every target-tree subprocess runs under the target profile's environment, not the deploying shell's. Deploys are run by a human whose shell is usually carrying the test overlay; a bootstrap or ingest that inherited it would write the TEST graph while reporting a prod deployment, silently, with no later way to tell which graph got the data.

Three lessons from the first real deployment

Running it found what reading it did not.

  1. verify slept 3 seconds and probed once. The kernel loads Prolog, connects Redis and the graph, and binds its port LAST — 9s after the unit started, 6s after verify had given up. It polls to a deadline now (90s default, returning the moment the system is up). A fixed sleep is a guess about a machine's speed.
  2. The deploy disabled its own rollback. assets chmod +x'd every .sh under .claude/hooks, including _payload.sh — a sourced library git records 100644. That one mode change dirtied the target, so the rollback could not check out the previous sha, and prod stayed on the unverified tree. Executability is now checked, never imposed.
  3. The dry run printed no sha. resolve assigned it only when executing, so the plan read would advance ca0c86e5 -> and closed with DEPLOYED prod @ -. A dry run is read instead of deploying; a blank in it is a defect.

Rollback

Automatic, from the services stage onward — a failure before it has changed nothing that needs undoing. It checks out the previous sha with --force (unexpected dirt on the target is not worth preserving over getting the system back up), reloads systemd, restarts the units, and records whether the previous sha actually came up. Rollback is verified, not assumed: if the old sha will not serve either, the record says so rather than implying recovery.

Code

The package was named deploy until the lexicon gate caught it: deploy is a work_type word in the intent vocabulary, and a subsystem holding that name turns "deploy the gateway" into a reference to a package. The action keeps the word.