Raxx · internal docs

internal · gated ↑ index

ADR-0034 — Console-driven deploy flow with GitHub Actions status callbacks

Status: Proposed Date: 2026-04-30 UTC Refs: docs/architecture/console-driven-deploy-flow.md, ADR-0028, ADR-0030


Context

Today, deploying any Raxx surface requires the operator to leave the console, open a terminal (or the GitHub Actions UI), and manually invoke gh workflow run deploy-heroku.yml (or the equivalent workflow). Deploy state is invisible inside the console: the operator has no record of when a deploy was requested, what happened during the run, or whether it succeeded without navigating to GitHub Actions.

This creates three compounding problems:

  1. No deploy intent record. There is no database row that says "Kristerpher asked to deploy api-prod at 14:33 UTC." The only evidence is the GitHub Actions run log, which is not queryable from the console.

  2. No live status visibility. While a deploy is running, the operator must watch the GitHub Actions UI. The console surface tile continues to show whatever status the poller last recorded — not "deploy in progress."

  3. No callback loop. If a deploy fails, the console learns about it only when the health check probe eventually detects a degraded surface. There is no structured "deploy failed at step X because Y" record linked to the deploy intent.

The desired shape is: a Deploy button on each site tile in the console, a type-to-confirm modal per ADR-0028, and then live status streaming inside the console as the GitHub workflow progresses — without sacrificing the intentional friction gate or the existing break-glass paths.


Decision

Introduce a console_deploys table as the deploy intent record and a webhook callback contract between GitHub Actions workflows and the console.

Three additions:

1. console_deploys table. Each Deploy button click writes a row before anything else happens. The row is the source of truth for deploy state inside the console. Status advances through a defined state machine (below) as the GitHub workflow posts callbacks.

2. Console API. Three endpoints on the internal blueprint: - POST /api/internal/deploys — create intent row, call GitHub workflow_dispatch API, store the returned run_id. - GET /api/internal/deploys/<id> — read state + log tail for UI polling. - POST /api/internal/deploys/<id>/status — callback receiver for GitHub workflows; HMAC-verified.

3. Reusable notify action. A new composite action .github/actions/notify-deploy-status wraps the curl callback call. The four deploy workflows (deploy-heroku.yml, deploy-customer-docs.yml, deploy-console.yml, deploy-status-page.yml) each gain steps that invoke this action at lifecycle milestones. If console_deploy_id input is empty (manual gh workflow run invocation), the action is a no-op — preserving all existing manual paths unchanged.


Consequences

Positive:

Negative / tradeoffs:


Alternatives considered

Option A — Full auto deploy (no console button, push-to-main triggers prod)

Rejected. Violates ADR-0028's intentional-friction invariant and the platform invariant that no prod deploy may fire as a side effect of a push.

Option B — Console dispatches via Slack slash command instead of UI button

Rejected. Adds a second tool dependency for a flow that belongs in the console. The console is the operator control plane; deploy initiation belongs there.

Option C — GitHub webhooks push to console instead of workflow curl callbacks

Considered. GitHub can deliver workflow_run webhook events to a public endpoint. This would eliminate the curl-in-workflow pattern. Rejected for v1 because: (1) it requires a public webhook endpoint with GitHub IP allowlisting, (2) webhook delivery order is not guaranteed, (3) it does not provide streaming_log content — only job-level status. The curl-in-workflow pattern gives the workflow author control over what text is appended to the log. Revisit when GitHub Apps integration (#335, #336) lands; at that point a webhook receiver on the console becomes the natural complement to app-authenticated events.

Option D — Long-poll or Server-Sent Events for live status

Deferred to v2. Plain HTTP polling at 2s is adequate for the deploy cadence (one deploy every N minutes at most) and avoids SSE connection management complexity in v1. The GET /api/internal/deploys/<id> polling endpoint is designed so that switching to SSE later requires only a new endpoint, not a schema change.

Option E — GitHub App identity for the workflow_dispatch call

Preferred long-term. For v1, the existing agent PAT is used because GitHub Apps (#335, #336) are in flight and not yet provisioned. The design uses a GITHUB_DISPATCH_TOKEN vault secret that can be swapped to a GitHub App installation token without code changes. ADR update required when Apps land.