# CI Required Status Checks

Configure these as required checks on the `main` branch in GitHub → Settings → Branches → Branch protection rules.

## Workflows actually in the repo

Only these run. A workflow file that is not committed cannot be a required check —
GitHub has nothing to execute, and branch protection will simply wait forever on a
check name that never reports.

| Check name | Workflow file | Scope |
|---|---|---|
| `boot-smoke` | `.github/workflows/boot-smoke.yml` | Loads every route + service module with and without `OPENAI_API_KEY`; runs the unit tests. No services, no secrets, ~1 min |
| `e2e / connections-smoke` | `.github/workflows/e2e-smoke.yml` | Boots app + Postgres in CI, runs Playwright smoke suite against /connections |
| `Enforce Architecture Rules` | `.github/workflows/architecture.yml` | No longer just `scripts/check-architecture-rules.sh` — this has become the repo's main gate file (21 steps as of 2026-09-02). Also runs `audit-grants`, `lint-inline-scripts`, `lint-server-undef`, `check-silent-empty`, `check-catalog-ui` (an op reachable from no UI fails the build), `gen-clone-catalog --check`, and the unit/contract tests: patches gap analysis, destructive-confirm, advisor budget, delivery attempts, managed-server states, connection health, upgrade opt-out, tmp sweep, grant-check, plus the clone set — `test-clone-long-running.js`, `test-rman-duplicate.js`, `test-clone-credential-seam.js` (the credential across page→route→runner→agent) and `test-clone-fake-tools.py` (the agent's real script executed against fake `rman`/`sqlplus`). Uses `node` and `python3`. No services, no secrets |
| `Playwright E2E Tests` | `.github/workflows/playwright.yml` | Playwright suite against live `tunevault.app`. Needs `PLAYWRIGHT_SESSION_COOKIE` |
| `Installer Smoke Test` | `.github/workflows/installer-smoke-test.yml` | `install.sh` in OL7/OL8 containers. Path-filtered on PRs; runs on every push to main |
| `Systemd Gate (required)` | `.github/workflows/install-sh-ol7-gate.yml` | Full-systemd install proof on **OL8** against a stub cloud (service active, 8 journal milestones, `doctor --deep`, register + heartbeat) + daily live-URL regression on OL7 at 06:00 UTC. Filename still says ol7 — see the header comment for why the systemd half moved to OL8 |
| — | `.github/workflows/installer-validation.yml` | 6-hourly OL7/OL8 validation. POSTs to `/api/installer-validation/report` — this is what populates `/api/status/installer` |
| — | `.github/workflows/e2e-oracle-xe-full.yml` | Manual/scheduled installer proof on Oracle XE 21c. Not a merge gate |
| — | `.github/workflows/patch-index-refresh.yml` | Fortnightly `data/oracle-patches.json` refresh PR. Not a merge gate |

> **`boot-smoke` is the one to make required first.** It is the cheapest gate with the
> highest yield: it would have caught the 2026-08-05 P0 where `new OpenAI()` at module
> scope made the app unbootable without `OPENAI_API_KEY`, and it catches any
> `require()`-time regression across all 137 routes and 31 services.

## Committed 2026-08-06 — running, not yet gateable

The seven workflows above marked as needing secrets had been sitting untracked since
`e9b2ad6` (2026-05-26). They were restored in `f0f8060`. The gates they define had
therefore never run once — which is why `/api/status/installer` had no data to report
(`c489a79`) and why the Rule-3 scope fix in `5c16831` went unverified.

Restoring them is not the same as gating them. Current first-run state:

| Workflow | State | Blocker |
|---|---|---|
| `architecture.yml` | ✅ zero violations | — (Rule 3a's 3 lab-hostname leaks on the public `/status/installer` page were fixed 2026-08-06) |
| `playwright.yml` | ✅ passes | — |
| `installer-smoke-test.yml` | ✅ 3/3 cells pass (OL7, OL8, Ubuntu 22.04) | — |
| `install-sh-ol7-gate.yml` | systemd half moved to OL8 (2026-08-06) | It had never actually run: OL7's systemd 219 needs cgroup v1, ubuntu-latest is cgroup v2 only, so `/usr/sbin/init` never booted and every assertion failed on "Failed to get D-Bus connection". The readiness wait warned and continued, hiding it. It now fails loudly if systemd is not up |
| `installer-validation.yml` | ❌ cron only | Needs `CI_REPORT_TOKEN`, `ORACLE_TEST_PASSWORD`, `TEST_TENANT_API_KEY` |

> **Branch protection is unavailable on this repo's plan** (private repo, `GET
> /branches/main/protection` → 403 "Upgrade to GitHub Pro"). No status check can be
> *required* today, so these red runs report but block nothing. Treat the table above
> as a to-do list, not an outage.

### Obsolete triggers — will never fire

| Workflow file | Why |
|---|---|
| `e2e-post-deploy.yml` | Triggers on Render's `deployment_status` event, which no longer exists — deploy is a 5-min git-pull cron on the OCI VM since the 2026-07-26 cutover |
| `installer-smoke-post-deploy.yml` | Same dead `deployment_status` trigger |

Both are committed but inert. Retarget them at the VM's deploy cron (or delete them)
before relying on any post-deploy assertion.

## How to add a required status check

1. Go to **GitHub → Settings → Branches → Branch protection rules** for `main`.
2. Enable **"Require status checks to pass before merging"**.
3. Click **"Add status check"** and type the exact check name from the table above.
4. Enable **"Require branches to be up to date before merging"**.
5. Save.

> The exact check name must match the `name:` field of the job in the workflow YAML, prefixed by the workflow name: `<workflow-name> / <job-name>`.
> Example: workflow `name: E2E Smoke — /connections tripwire`, job `name: connections-smoke` → check name is `connections-smoke` (GitHub uses just the job name).

## E2E smoke: required secrets

The `e2e-smoke.yml` workflow runs against a local Postgres service — no secrets needed for the CI variant.

The `e2e-post-deploy.yml` workflow runs against live `tunevault.app` — it requires:

| Secret name | Description |
|---|---|
| `E2E_SMOKE_SESSION_COOKIE` | Pre-baked `tv_session=<token>` cookie for the dedicated smoke-test admin account. Generate by logging in as `e2e-smoke@tunevault.internal` and copying the `tv_session` cookie value, prefixed with `tv_session=`. |
| `POSTMARK_SERVER_TOKEN` | Postmark server API token for ops failure alert emails. |
| `OPS_ALERT_EMAIL` | Recipient address for failure alerts (e.g. `ops@tunevault.app`). |

## Renewing E2E_SMOKE_SESSION_COOKIE

The `tv_session` cookie has a 30-day TTL. Set a calendar reminder to rotate it monthly:

1. Log in to `tunevault.app` as `e2e-smoke@tunevault.internal` (use magic-link flow).
2. Copy the `tv_session` cookie value from browser DevTools → Application → Cookies.
3. Update the `E2E_SMOKE_SESSION_COOKIE` secret: `tv_session=<new-value>`.

## Local development

Run the connections smoke suite locally against a running dev server:

```bash
# Mode 1: pre-baked cookie (if you have a valid session)
TEST_BASE_URL=http://localhost:3000 \
TEST_SESSION_COOKIE="tv_session=<your-cookie>" \
npx playwright test tests/e2e/connections.spec.ts

# Mode 2: mint a fresh token from SESSION_SECRET
TEST_BASE_URL=http://localhost:3000 \
SESSION_SECRET=your-dev-secret \
E2E_ADMIN_USER_ID=1 \
npx playwright test tests/e2e/connections.spec.ts
```

## What the connections smoke suite asserts

1. `GET /connections` returns 200 and the page shell renders.
2. Within 5s, loading spinner is gone and either the connections grid or empty-state is visible.
3. Delete button on a connection card opens the confirm modal (skipped if account has no connections).
4. `/dashboard#connections` has no in-page connections table (old section removed).
5. `GET /api/connections` returns 200 with a JSON array.

Total: 5 tests, ~6–8 assertions. Intentionally tight — this is a tripwire, not full regression coverage.
