fix(hrafn-ci): make the deploy converge instead of accrete

The first CI run shipped clean but revealed a design gap in the playbook:
unpacking the context tarball in place overwrites tracked files and never
removes anything. Leftovers from the pre-CI hand-rsync (tests/, docs/,
ROADMAP.md, persistent-memory.md, CLAUDE.md, LICENSE) survived the deploy
and had to be cleaned off ana-docker by hand.

That is the same failure class that produced the mess in the first place:
a deploy that only ever adds cannot return the host to a known state.

- unpack to a staging dir, then rsync --delete onto the compose dir
- protect host-owned .env and .deployed by name
- add .env.example to the context tarball so converge does not delete it
- record in the workflow that the tar list is now AUTHORITATIVE: anything
  omitted is removed from the host on the next deploy

Re-validated with `elway --dry-run` (9 steps, 3 verify, parses clean).
Not yet in vh/hrafn -- infra-ops has no write access there, so this is
offered to the repo holder rather than pushed.
This commit is contained in:
vh
2026-08-22 15:31:50 -07:00
parent b001d0cb2e
commit 11b9d1891e
3 changed files with 44 additions and 10 deletions
+25 -5
View File
@@ -10,7 +10,11 @@
# Requires a build-context tarball at dist/hrafn-context.tgz. The CI
# workflow builds it; for a manual run, build it the same way:
# mkdir -p dist && tar czf dist/hrafn-context.tgz \
# Dockerfile compose.yaml pyproject.toml README.md src
# Dockerfile compose.yaml pyproject.toml README.md .env.example src
#
# The tarball must carry EVERYTHING that belongs in the compose dir: the
# converge step below deletes anything on the host that is not in it,
# except the host-owned .env and .deployed.
#
# DIFFERENCE 1 — tarball instead of per-file upload steps. nevermore
# enumerates every source file as its own upload step. That is explicit,
@@ -59,10 +63,26 @@ steps:
dest: "{{ compose_dir }}/.hrafn-context.tgz"
mode: "0600"
- name: Unpack build context
# Overwrites tracked files in place; leaves .env and any host-only
# state alone because the archive does not contain them.
shell: tar xzf {{ compose_dir }}/.hrafn-context.tgz -C {{ compose_dir }} && rm -f {{ compose_dir }}/.hrafn-context.tgz
- name: Unpack build context into a staging dir
shell: |
rm -rf {{ compose_dir }}/.stage && mkdir -p {{ compose_dir }}/.stage
tar xzf {{ compose_dir }}/.hrafn-context.tgz -C {{ compose_dir }}/.stage
rm -f {{ compose_dir }}/.hrafn-context.tgz
- name: Converge the compose dir onto the build context
# The first version of this playbook unpacked in place, which
# ACCRETED: it overwrote tracked files but never removed anything,
# so leftovers from the pre-CI hand-rsync (tests/, docs/, ROADMAP.md,
# persistent-memory.md, CLAUDE.md, LICENSE) survived the first CI
# deploy and had to be cleaned off the host by hand. rsync --delete
# makes the directory CONVERGE on the build context, so a stray file
# — from an old deploy or a debugging session — cannot outlive the
# next deploy. Host-owned state is protected by name.
shell: |
rsync -a --delete \
--exclude '.env' --exclude '.deployed' \
{{ compose_dir }}/.stage/ {{ compose_dir }}/
rm -rf {{ compose_dir }}/.stage
# ── build + start ───────────────────────────────────────────────────