# v0.1.9 Release Automation Retrospective

Date: 2026-06-22
Branch: `develop`
Production release: `v0.1.9`

## Result

`v0.1.9` is the first verified end-to-end automatic production release for 小虾 SaaS:

1. Gitea tag push starts production release workflow.
2. Dedicated `runtime-builder-01` builds API/Worker runtime images.
3. Dedicated builder builds Web `apps/web/dist` in a Node Docker container.
4. Dedicated builder uploads both artifacts to production.
5. Dedicated builder drives production deployment over SSH.
6. Production host only extracts artifacts, loads runtime images, runs migrations, restarts containers, and health checks.
7. Public Web/API smoke tests pass.

## Verified Production State

After `v0.1.9`:

- `xiaoxia-api-production` runs `xiaoxia-saas-api:v0.1.9` and is healthy.
- `xiaoxia-worker-production` runs `xiaoxia-saas-worker:v0.1.9`.
- `xiaoxia-web-production` is healthy and serves prebuilt Web dist.
- `/var/lib/xiaoxia-saas-production/runtime-images-v0.1.9.tar` is present, about 325 MB.
- `/var/lib/xiaoxia-saas-production/release-v0.1.9.tar.gz` is present, about 883 KB.

Public smoke results:

```text
python scripts\smoke_public_auth_flow.py
public_auth_flow=ok

python scripts\smoke_public_upload_flow.py
public_upload_flow=ok

public project detail smoke
public_project_get=ok
```

## What Repeated

The repeated tags were not feature releases; they were release-chain verification tags.

- `v0.1.6`: runtime-builder checkout/token fix worked, but Gitea skipped downstream production deploy after cross-runner `needs`.
- `v0.1.7`: production deploy moved to `runtime-builder` over SSH; build/upload succeeded, but deploy failed because `deploy-production.sh` defaulted to `/host` paths while SSH uses host paths directly.
- `v0.1.8`: SSH deploy used `HOST_PREFIX=`, but release artifact packaging used broad `--exclude=dist`, which removed `apps/web/dist` and correctly failed the Web artifact gate.
- `v0.1.9`: artifact packaging changed to `--exclude=./dist`, preserving `apps/web/dist`; automatic release succeeded.

Usable rollback tags:

- `v0.1.5`: previously verified stable production release.
- `v0.1.9`: current verified automatic production release.

Do not use `v0.1.6`, `v0.1.7`, or `v0.1.8` as rollback targets unless intentionally reproducing release-chain failures.

## Root Causes Fixed

1. `GITHUB_TOKEN` was not implicitly available in act_runner shell scripts.
   - Fix: checkout steps explicitly bind `GITHUB_TOKEN: ${{ github.token }}`.

2. Gitea `needs` with production deploy on a different runner path skipped the downstream deploy job.
   - Fix: production deploy also runs on `runtime-builder`, then SSHes to production.

3. Production deploy script defaults to `/host` for Docker-in-Docker CI execution.
   - Fix: SSH deploy passes `HOST_PREFIX=` so the script uses normal host paths.

4. Release source artifact accidentally included build output tarballs.
   - Fix: exclude root `./dist` from release tar.

5. A broad `--exclude=dist` also excluded Web dist.
   - Fix: use `--exclude=./dist`, not `--exclude=dist`.

## Current Release Contract

For production `v*` tag releases:

- Production host must not build API or Worker images.
- Production host must not build frontend assets.
- `runtime-builder-01` is the build and deployment driver.
- `runtime-images-<tag>.tar` must exist before production deploy.
- `release-<tag>.tar.gz` must include `apps/web/dist/index.html`.
- Production deploy must fail if either artifact is missing.
- Production smoke is required before saying release is done.

## Follow-Ups

- Add an explicit dry-run workflow or staging release rehearsal to reduce tag-based trial and error.
- Keep `v0.1.6` to `v0.1.8` documented as failed release-chain probes.
- Consider adding an API version endpoint that reports the Docker image tag; current `/health` version text can lag image tags.
- Review temporary credential posture for builder SSH key and old registration tokens.
