# 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.