02 — Promotion control plane

The control plane is a host-side set of small, idempotent scripts plus versioned Markdown "truth" files. It deploys agent runtimes over ssh against immutable releases — live services are never run from a writable host mount, and live paths are never edited in place.

flowchart TB
    A["Archetype A · git-release"] --> WT["worktree @ exact SHA"]
    WT --> GATE{"--apply ?"}
    GATE -->|"no · default"| DRY["print plan<br/>changes nothing"]
    GATE -->|yes| SHIP["git archive → ssh<br/>/opt/agent/releases/&lt;sha&gt;"]
    SHIP --> PROV["write .release.json"]
    PROV --> FLIP["atomic current symlink flip"]
    FLIP --> RST["restart service"]
    RST --> VER["verify active · symlink · source SHA"]
    VER --> PREV["record previous target<br/>rollback-ready"]
    VER --> TRU["state-as-truth"]
    STAT["status-agent · read-only"] -.->|"re-derive live SHA"| TRU
    STAT -.->|"mismatch"| DRIFT["flag drift"]
    B["Archetype B · package-pin"] -.->|"capture-first"| TRU

Solid = verified promotion flow; dotted = read-only drift detection and capture-first archetype.

Immutable, exact-commit releases

Promotion ships an exact commit, not a working tree:

git archive <sha>  →  stream over ssh  →  unpack into /opt/agent/releases/<sha>-<label>
                   →  write <release>/.release.json  (sha, label, source, promoter, timestamp)
                   →  atomic `ln -sfn` flip of /opt/agent/current
                   →  restart the service  →  verify (active · symlink · source SHA)

The .release.json provenance stamp means a release is self-describing; the running source SHA is a fact you can read back, not a guess. The previous current target is recorded as the rollback target before the flip.

control-plane/promote-agent  --sha <sha> --label <l> --worktree <path> [--apply]
control-plane/rollback-agent --to <release-dirname|abs-path> [--apply]

Dry-run by default

Every mutating script previews its plan and changes nothing unless --apply is passed. The exact commit, release path, symlink, and service it will touch are printed first. This makes the dangerous operations safe to run, read, and review.

Drift detection (state-as-truth, re-derived)

state/*.md records the durable truth (topology, runtimes, releases). But a live symlink can be re-pointed by another actor, so status tools re-derive ground truth on demand and flag drift rather than trusting the recorded value:

control-plane/status-agent           # live symlink → release, source SHA (3-tier), service state, drift vs state/
control-plane/status-agent --update  # rewrite the host-local truth markers (never the runtime)
control-plane/smoke-agent            # read-only post-promote sanity checks

Source-SHA resolution is 3-tier: <release>/.release.json → in-release git rev-parse → recover from the <sha>-<label> directory name — so truth survives releases built by other tooling.

Two deployment models = the agnosticism proof

The same control-plane discipline wraps two deliberately different runtimes:

Archetype A (immutable release) Archetype B (package install)
Source git commit package version
Ship git archive → release dir install pinned version
Select current symlink flip binary/symlink version pin
Scripts promote-agent / rollback-agent promote-pkg / rollback-pkg (capture-first)

promote-pkg/rollback-pkg are intentional documented stubs until a package distribution is confirmed — the platform captures and audits Archetype B before it automates it.

Library

control-plane/lib/common.sh is sourced by every script: the ssh target, guest_ssh/guest_sudo helpers, logging, the dry-run gate (APPLY + applying), and print_context (every script prints exactly what it operates on).