Commit Graph

5 Commits

Author SHA1 Message Date
vh 9642952a54 docs(hrafn-ci): mirror the now-canonical vh/hrafn pipeline; record two failed runs
claude-bot holds write on vh/hrafn as of 2026-08-23, so the canonical copy of
the pipeline moved there and infra-ops maintains it directly instead of
routing patches through the repo holder. The files here are a verified mirror
(byte-identical to live at 22e0eb9d75a6).

Live state: run 9922 green, and both new content assertions executed rather
than merely existing --

  verify 4/5  host tree matches shipped context (a99ce748a0c9...)
  verify 5/5  image source matches host source (06f209fd0641...)

The CI-computed context hash matching on the host is the end-to-end proof
that the converge lands what CI ships. Its absence is what let the
frozen-source bug survive every green deploy.

Also records why the HEAD == GITHUB_SHA assertion was added and then removed:
it needed the git binary (run 9920, exit 127), and installing git flipped
actions/checkout@v4 from its node implementation to the git binary, which
died on a missing CA bundle (run 9921). A nice-to-have assertion changed the
checkout code path and broke a working pipeline; it guarded a hypothesis that
proved wrong, so it went rather than getting ca-certificates bolted on.
2026-08-23 02:55:02 -07:00
vh b38c369313 fix(hrafn-ci): staging dir inside the rsync target froze host source silently
Root cause of nevermore-claude's report that v1.0.0 deployed green while the
host kept serving 0.1.0.

The staging dir was $compose_dir/.stage -- INSIDE the rsync target. So
`rsync -a --delete $compose_dir/.stage/ $compose_dir/` deleted .stage from
the destination (absent from the source listing) DURING the transfer,
destroying the source mid-copy. Reproduced exactly:

  before:  app.py="OLD"  leftover.txt  .stage/app.py="NEW"
  after:   app.py="OLD"  leftover.txt GONE, .stage GONE

Deletion worked; the copy silently did not. So the directory looked
converged while host source stayed frozen at the first manual rsync, and
because the build's COPY inputs never changed, Docker full-cache-hit and
every SHA tag aliased one image. The provenance guarantee was false.

Nothing caught it because the verify steps asserted the marker, health, and
a 200 from /readyz -- all of which pass on a frozen host. None measured
content.

Fixes:
- stage at /tmp/hrafn-deploy-stage, outside the target
- CI computes context_sha256 over the shipped file list; the playbook
  recomputes it on the host post-converge and fails on mismatch
- compare the running container's src/**/*.py against the host's, catching
  a SHA tag naming layers the image does not contain
- checkout clean:true + assert HEAD == GITHUB_SHA so a reused runner
  workspace fails the job rather than shipping a stale tree

Declined --no-cache: a cache hit is correct when the context is genuinely
unchanged, and the new assertions prove the property directly rather than
brute-forcing it.

The container-vs-host check compares only *.py -- `pip install .` generates
src/hrafn.egg-info/* inside the image and __pycache__ appears at runtime, so
a naive `find src -type f` compare false-fails on every healthy deploy.
Verified against the live container before shipping (12 host files, 18 in
container, 0 content differences).
2026-08-22 21:57:16 -07:00
vh 11b9d1891e 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.
2026-08-22 15:31:50 -07:00
vh b001d0cb2e feat(hrafn): author the CI deploy that replaces the hand-rsync path
hrafn was handed to infra-ops for uptime ownership with no CI deploy and
no commit provenance -- the image was always local/hrafn:v1 and the whole
working tree lived in the compose directory.

These two files fix both. They are authored here because infra-ops owns
hrafn's uptime, but they belong in vh/hrafn; claude-bot is not a
collaborator there, so they are handed to the repo holder rather than
committed directly.

- playbooks-deploy.yaml -> vh/hrafn playbooks/deploy.yaml
- gitea-workflows-deploy.yaml -> vh/hrafn .gitea/workflows/deploy.yaml

Design calls recorded in the README: the build context travels as one
tarball rather than per-file upload steps (nevermore's pattern fails open
when a new source file has no matching step), and the playbook refuses to
deploy unless .env exists at 0600 -- a guard prompted by it arriving 0644
with a live bearer token in it.

Validated with `elway --dry-run`, which caught a real interpolation bug
during authoring. No new Actions secrets needed.
2026-08-22 15:26:45 -07:00
vh b6924de728 feat(hrafn): register the browser-fetch service and tighten its env perms
nevermore-claude handed hrafn to infra-ops for uptime ownership. Intake:
verified health independently, brought it into the inventory, and fixed one
security defect found during the check.

- canonicalize stacks/hrafn/ (compose.yaml + .env.example + README)
- list hrafn in the ana-docker running-stacks table
- README records the load-bearing bits: shm_size 1gb is required for
  Chromium, playwright and the base image are version-coupled, SSRF denies
  private targets by default, and restart:unless-stopped does NOT act on
  healthcheck failure

Security fix applied on the host, not in this tree: /opt/docker/compose/
hrafn/.env was 0644 with a live 57-char bearer token in it, readable by
every local account (verified by reading it as `nobody`). Tightened to 0600;
owner unchanged so the deploy path still works, confirmed via `docker
compose config` as lkraven. Container untouched and still healthy.

Known gaps recorded rather than silently accepted: no CI deploy, and the
image builds from an rsync'd working tree living in the compose directory,
so the running image has no commit provenance.
2026-08-22 15:05:11 -07:00