# Release checklist

> **Historical snapshot (2026-07-19).** The current release gate and external dependencies are
> documented in `.gitea/workflows/ci-deploy.yml`, `docs/implementation-roadmap.md`, and `BLOCKERS.md`.

> 2026-07-19. State of the build before the first production deployment after the Phase 4/5
> architecture changes. Companion to `deploy/first-production-deployment.md` (how to deploy),
> `docs/production-readiness-review.md` (what was audited) and
> `docs/phase-5-completion-report.md` (what was built and why).

## Completed architecture work

| Area | State |
|---|---|
| **Career Profile** | Single source of truth. Relational children with stable item keys; `ProfileJson` is a derived projection. Nothing downstream writes to it. |
| **CV Builder** | Data-driven theme engine — themes are data, not hardcoded templates. Eight themes; adding one is appending an object. Variants are lenses over the master profile, never copies. Autosave, version history, restore. |
| **Public CV** | `/cv/{slug}`, off by default, per-variant opt-in, unguessable slug, `noindex`. SPA deep-link routing fixed so direct navigation, refresh and shared links resolve. |
| **Application Workspace** | `/applications/{id}`: Overview, Checklist, Timeline, Analysis, Match, CV, Cover Letter, Documents, Interview Prep, Communication. Owns almost no data — it composes systems that already existed. |
| **AI integrations** | Suggestion-only. Every generation appends to `AiInteraction` (that history *is* the versioning). Interview generation now receives the analysis and match context. Nothing AI-driven writes to the profile, a variant, a cover letter or prep content without the user saving it. |
| **Interview preparation** | User-owned `InterviewPrepItem`, distinct from the two AI *caches* that regenerate. Six categories in one table. |
| **Production hardening** | Explicit `[Authorize]` on every controller with a reflection test; clean MariaDB initialization; database ownership documented; pre-deploy backups; restore and rollback docs; health checks on backend and frontend. |

## Local verification

| Check | Result |
|---|---|
| Backend tests (Release) | 393 passed |
| Frontend tests | 128 passed, 36 suites |
| TypeScript | clean |
| Frontend production build | clean |
| Docker images | backend and frontend build |
| Fresh empty MariaDB 11 | 42 tables, starts healthy |
| Populated MariaDB restart | idempotent, rows preserved |
| Existing SQLite dev database | upgraded 34 → 44 tables, rows preserved |
| Backup and restore | dump verified, restored into a clean database, rows identical |

## Unresolved external blocker

### CI runner — deployment is gated on this

CI is red for a reason **proven to be outside the repository**: commit `8f73548` changed a single
markdown file — no code, no test, no dependency, no workflow — and its test job failed in the same
duration band as every other run. A change that cannot affect compilation cannot fail a test job.

Consequence: **everything above is local verification.** Nothing has been proven in CI, and the
deployment pipeline will not promote a build until this is fixed.

Blocked on, and needing you:

- Job logs — the API returns 401 unauthenticated; a read-scoped Gitea token would unblock this
- `journalctl -u act_runner --since '2 hours ago'` on the runner host
- The act_runner container/service configuration

Full evidence: `docs/infrastructure/runner-investigation.md`.

## Known risks

| Risk | Severity | Notes |
|---|---|---|
| Production is many commits behind | High | The first deploy applies the whole Phase 4/5 change at once. Reconciler builds ~12 tables against real data for the first time. |
| Reconciler path unproven on your data | Medium | Verified on containers and on a real SQLite dev database — not on production. This is what the backup is for. |
| Production journey unwalked | Medium | The CI-gated local browser suite covers login, saved-job creation, Career Workspace, and public CV/PDF. The same authenticated and OAuth checks still require production credentials. |
| Rollback loses new-table data | Medium | A code rollback keeps everything. A *database restore* discards rows created since the dump. Keep the deploy window quiet. |
| Logging is structured JSON on stdout | Low | Compose retains three 10 MB files per service; aggregation remains deployment-owned. |
| No global exception handler | Low | Unhandled errors return a bare 500 with no correlation id. No stack leaks outside Development. |
| `ClientErrorsController` is anonymous | Low | By design — browser error reports must work before sign-in. Size-limited and truncated; stores nothing. |
| Prompt quality unmeasured | Low | Interview generation gets richer context; whether the questions are *better* is a judgement no test makes. |

## First deployment warnings

1. **Take a manual backup as well, and restore it once to a scratch database.** `deploy.sh` backs up
   automatically, but the first deploy is the wrong time to discover a backup does not restore.
2. **Watch `docker compose logs -f backend` live.** `Database.Migrate()` throws on failure, so a schema
   problem exits the container. The runbook lists the exact log lines that mean stop.
3. **`DATABASE_PROVIDER` defaults to `sqlite`.** If it goes missing from `.env`, the backend does not
   quietly serve an empty database — it crashes with `no such table: INFORMATION_SCHEMA.TABLES`.
   Loud, but confusing if you have not seen it before.
4. **The connection string resolves from inside the container.** `Server=127.0.0.1` means the backend
   container, not the host.
5. **Empty new sections are expected.** Existing applications will show an empty Checklist, Timeline
   and Interview Prep until used. The checklist seeds on first open. That is correct behaviour.
6. **Verify row counts before walking away.** Applications and companies must match the pre-deploy
   numbers. This is the check that matters most.
7. **Do not restore the database for a code problem.** Roll the code back first; it is almost always
   the whole fix, and it costs no data.
