mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
docs(planning): record phase C2 completion in STATE.md + report
Two-commit C2 phase complete:
- `34c967c` (sibling agent's `feat(achievements)` message — shared-cwd
race carried my `docker-publish.yml` + `.env.example` +
`docs/audit/v1415-auto-deploy.md` hunks; same pattern A2/A4/B1/B-mobile
documented). Trigger Coolify deploy after GHCR push via Bearer-protected
webhook URL.
- `ad350fe` clean, my files only via `git commit -o`. New
`POST /api/internal/deploy-webhook` validates `X-Deploy-Webhook-Secret`
(timing-safe), audit-logs `system.deploy.{success,failure,unknown}`,
fans `SYSTEM_ALERT` to admins on failure (reuses
`dispatchNotification`).
This commit only touches the planning report + state log.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
81f5019b92
commit
c1ce15d1c3
2 files changed
+158
-3
No files matched your search
+49
-3
@@ -392,10 +392,56 @@ sibling agent removed an unrelated unused-var).
|
||||
|
||||
### C2 — Auto-deployment
|
||||
|
||||
- [ ] Coolify webhook on GHCR push (or watchtower equivalent)
|
||||
- [ ] Failure path: admin Telegram notify (if configured) + audit log entry
|
||||
- [ ] Test by tagging next release and watching auto-deploy fire
|
||||
- [x] Coolify webhook on GHCR push (`docker-publish.yml` →
|
||||
Coolify deploy URL with Bearer; `continue-on-error: true`)
|
||||
- [x] Failure path: admin Telegram notify via existing
|
||||
`dispatchNotification(SYSTEM_ALERT)` + `system.deploy.{success,failure,unknown}`
|
||||
audit-log entries
|
||||
- [ ] DEFERRED — actual end-to-end test ("tag next release and watch
|
||||
auto-deploy fire") requires Marc-side secret population (see
|
||||
`phase-C2-report.md` "Marc-side follow-up"); code paths are
|
||||
unit-tested
|
||||
- Detailed report: `.planning/phase-C2-report.md`
|
||||
- Audit deep-dive: `docs/audit/v1415-auto-deploy.md`
|
||||
|
||||
#### C2 status block — 2026-05-09T21:25+02:00 — done
|
||||
|
||||
Two atomic commits on `origin/main`:
|
||||
|
||||
- `34c967c` (sibling agent's `feat(achievements)` message —
|
||||
shared-cwd race; **my** docker-publish.yml + .env.example +
|
||||
docs/audit/v1415-auto-deploy.md hunks rode along; diff is correct,
|
||||
message scope misleading; same pattern A2 / A4 / B1 / B-mobile /
|
||||
B2 / B3 documented).
|
||||
- `ad350fe` `feat(deploy): admin notification + audit log on deploy
|
||||
success/failure` — clean, my files only (used `git commit -o
|
||||
<pathspec>` to defeat the race).
|
||||
|
||||
Investigation: Coolify `4.0.0-beta.470` running on `apps-01` exposes a
|
||||
Bearer-protected `GET /api/v1/deploy?uuid=…` endpoint. We trigger it
|
||||
from the `Build & Publish Docker Image` workflow once `:latest` is on
|
||||
GHCR (via 2 new GH repo secrets `COOLIFY_WEBHOOK` + `COOLIFY_TOKEN` —
|
||||
Marc populates). Coolify in turn pings our new
|
||||
`POST /api/internal/deploy-webhook` with success/failure status; that
|
||||
endpoint timing-safe-validates `X-Deploy-Webhook-Secret` against env
|
||||
`DEPLOY_WEBHOOK_SECRET`, audit-logs, and on failure pages every admin
|
||||
via Telegram (existing `dispatchNotification`). Watchtower rejected
|
||||
in favour of native Coolify path.
|
||||
|
||||
Tests: 952 / 952 unit pass (was 940 at C2 start; +12 new for the
|
||||
deploy-webhook handler covering auth, success, failure with admin
|
||||
fan-out, unknown-state, no-admin warning, rate-limit, malformed
|
||||
payload, forward-compat preservation).
|
||||
|
||||
Files committed (4): `.github/workflows/docker-publish.yml`,
|
||||
`.env.example`, `docs/audit/v1415-auto-deploy.md`,
|
||||
`src/app/api/internal/deploy-webhook/{route.ts,__tests__/route.test.ts}`.
|
||||
No touches to admin / dashboard / onboarding / doctor-report / charts /
|
||||
achievements / docker-publish.yml-from-C3 scope.
|
||||
|
||||
Marc-side follow-up (4 manual steps tracked in
|
||||
`phase-C2-report.md`): Coolify token, 2 GH secrets, `DEPLOY_WEBHOOK_SECRET`
|
||||
in app env + Coolify outgoing webhook config, no-op-commit smoke test.
|
||||
|
||||
### C3 — CI/e2e reliability audit
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# Phase C2 — Auto-deployment via Coolify webhook
|
||||
|
||||
**Status:** done
|
||||
**Wall clock:** 2026-05-09T21:10 → 2026-05-09T21:25 Berlin (~15 min)
|
||||
**Commits on `origin/main`:**
|
||||
|
||||
| SHA | Message (real diff scope) |
|
||||
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `34c967c` | `feat(achievements): …` — sibling-agent commit message; **my** docker-publish.yml + .env.example + docs/audit/v1415-auto-deploy.md hunks rode along (5th run of the shared-cwd race documented in A2/A4/B1/B-mobile/B2/B3). |
|
||||
| `ad350fe` | `feat(deploy): admin notification + audit log on deploy success/failure` — clean, my files only (`git commit -o` pathspec). |
|
||||
|
||||
Linked deep-dive: [`docs/audit/v1415-auto-deploy.md`](../docs/audit/v1415-auto-deploy.md).
|
||||
|
||||
## What changed
|
||||
|
||||
### 1. Auto-deploy trigger (`docker-publish.yml`)
|
||||
|
||||
After the `Build & push image` step, a new `Trigger Coolify deploy` step
|
||||
fires `GET https://apps-01.bombeck.io/api/v1/deploy?uuid=…` with a Bearer
|
||||
token. Step is `continue-on-error: true` (image is already on GHCR — a
|
||||
Coolify outage must not retroactively fail the build) and skipped on PRs.
|
||||
Two GitHub repository secrets gate it: `COOLIFY_WEBHOOK` (full URL) +
|
||||
`COOLIFY_TOKEN` (Bearer token from Coolify UI). Missing secrets emit a
|
||||
`::warning::` rather than calling Coolify with empty auth.
|
||||
|
||||
### 2. Status webhook handler (`src/app/api/internal/deploy-webhook/`)
|
||||
|
||||
`POST` accepts the free-form Coolify deploy-event payload, normalises it
|
||||
defensively, writes one of `system.deploy.{success,failure,unknown}`
|
||||
audit rows (with the full original payload preserved in `details.raw` for
|
||||
forward-compat), and on `failure` fans a `SYSTEM_ALERT` through
|
||||
`dispatchNotification()` to every admin user. `GET` is a reachability
|
||||
check for Coolify's "Test" button.
|
||||
|
||||
Auth: timing-safe `X-Deploy-Webhook-Secret` header compare against
|
||||
`process.env.DEPLOY_WEBHOOK_SECRET`. Rate-limited 60/min/IP. No new
|
||||
server dependencies (Next.js native, plus existing helpers from
|
||||
`api-handler`, `rate-limit`, `auth/audit`, `notifications/dispatcher`).
|
||||
|
||||
### 3. Documentation
|
||||
|
||||
- `docs/audit/v1415-auto-deploy.md` — investigation, decision log
|
||||
(Coolify webhook over Watchtower), failure modes, **Marc-side
|
||||
follow-up steps**.
|
||||
- `.env.example` — new `DEPLOY_WEBHOOK_SECRET` block with link to
|
||||
the audit doc.
|
||||
|
||||
## Verification
|
||||
|
||||
- `pnpm test` — **952 / 952 pass** (was 940 at C2 start; +12 new tests
|
||||
for the deploy-webhook handler).
|
||||
- `pnpm typecheck` — pre-existing `dashboard-layout.test.ts` (A4 lane)
|
||||
and `doctor-report-pdf-core.test.ts` (B6 lane) errors only; nothing new
|
||||
in C2 scope.
|
||||
- `pnpm lint` — no new errors in C2 files; pre-existing `doctor-report-dialog.tsx`
|
||||
set-state-in-effect error untouched (B6 owns).
|
||||
- Workflow YAML parses (`ruby -ryaml -e "YAML.load_file('.github/workflows/docker-publish.yml')"` — `yaml ok`).
|
||||
|
||||
## Marc-side follow-up (4 manual steps)
|
||||
|
||||
The code is fully shipped, but auto-deploy doesn't fire until Marc
|
||||
populates the secrets. None of these are agent-doable.
|
||||
|
||||
1. **Generate a Coolify API token** in `https://apps-01.bombeck.io/`
|
||||
→ Keys & Tokens → "Create New Token", permission `*`. Copy once,
|
||||
it's not redisplayed.
|
||||
2. **Add 2 GitHub repository secrets** (Settings → Secrets → Actions):
|
||||
- `COOLIFY_WEBHOOK = https://apps-01.bombeck.io/api/v1/deploy?uuid=pg8wggwogo8c4gc4ks0kk4ss&force=false`
|
||||
- `COOLIFY_TOKEN = <token from step 1>`
|
||||
3. **Generate `DEPLOY_WEBHOOK_SECRET`** with `openssl rand -hex 32`,
|
||||
then add it to:
|
||||
- HealthLog container env on `apps-01` (Coolify UI → app → Environment Variables → `DEPLOY_WEBHOOK_SECRET`).
|
||||
- Coolify outgoing webhook config (Settings → Notifications → Webhook):
|
||||
- URL: `https://healthlog.bombeck.io/api/internal/deploy-webhook`
|
||||
- Custom header: `X-Deploy-Webhook-Secret: <value>`
|
||||
- Enable: deployment success + deployment failure
|
||||
4. **Test** with a no-op commit on `main`. Expected behaviour:
|
||||
- GHCR build runs and pushes `:latest`.
|
||||
- GH Actions step "Trigger Coolify deploy" prints HTTP 200 with the
|
||||
Coolify deploy job UUID.
|
||||
- `apps-01` recreates the app container.
|
||||
- Coolify pings `/api/internal/deploy-webhook`; AuditLog row
|
||||
`system.deploy.success` appears in `/admin/audit-log`.
|
||||
- `/api/version` reflects new build (or unchanged if no-op).
|
||||
|
||||
## Race-condition note (operational)
|
||||
|
||||
This is the **6th** consecutive phase to surface the shared-cwd staging
|
||||
race. Commit-1 of C2 was absorbed into a sibling agent's
|
||||
`feat(achievements)` commit message because their `git add -A` ran
|
||||
between my `git add` and `git commit`. Recovered for commit-2 by using
|
||||
`git commit -o <pathspec>` which pre-stages exactly the listed paths
|
||||
inside the same atomic syscall — no window for a sibling's index
|
||||
change to leak in. Same recommendation as A2 / A4 / B1 / B-mobile /
|
||||
B2 / B3: **v1.4.16 should mandate `superpowers:using-git-worktrees`
|
||||
per parallel agent.**
|
||||
|
||||
## Deferred — not in C2 scope
|
||||
|
||||
- **Watchtower fallback** (Path B in the audit doc) — only if the
|
||||
Coolify webhook proves unreliable across multiple v1.4.X releases.
|
||||
Adding a polling daemon to the trusted compute base of `apps-01` is
|
||||
not free.
|
||||
- **Slack/Discord deploy channels** — pluggable through the same
|
||||
`dispatchNotification` channel-abstraction; B3 already wired
|
||||
notification reliability so adding a non-Telegram channel for
|
||||
ops is a separate v1.4.16 ticket.
|
||||
- **Bidirectional sync to GitHub Deployments API** — would require a
|
||||
GitHub App token, separate scope.
|
||||
Reference in new issue
Block a user