Written against the actual implementation rather than the existing docs, and validated locally against MariaDB 11 containers. No application behaviour changed — this commit adds two documents. deploy/first-production-deployment.md covers pre-deployment checks, the eight deployment steps, smoke tests for backend, database and application, and rollback. It documents what deploy.sh really does: it backs up first and aborts on failure, and it replaces containers with up -d --force-recreate rather than running compose down, so the window is container start time. It also records the startup sequence as implemented — reconcile, migrate, reconcile — and that Database.Migrate() throws rather than limping on. Validation surfaced things worth writing down. The connection string resolves from inside the backend container, so Server=127.0.0.1 means the container and not the host; this broke a validation run before it could have broken a deploy. DATABASE_PROVIDER defaults to sqlite, and if it goes missing the backend does not quietly serve an empty database — it exits with "no such table: INFORMATION_SCHEMA.TABLES", which is loud but baffling if unexplained. A blank AUTH_JWT_KEY throws at startup when auth is required, which is the right behaviour. The runbook maps each of these log lines to its cause. Rollback is documented with the distinction stated plainly: a code rollback keeps all data and is almost always the whole fix, while a database restore discards everything written since the dump. Restore only when the data itself is wrong. docs/release-checklist.md records the completed architecture work, local verification results, known risks with severities, the unresolved CI runner blocker and what would unblock it, and seven first-deployment warnings. Validated: compose build; compose up on a fresh MariaDB (42 tables, backend healthy); the depends_on health gate holding the frontend until the backend is healthy; restart against the populated database with rows preserved; backup and restore; and the failure paths. Not validated, and said so in both documents: the authenticated end-to-end journey, because signing in needs a password. 393 backend tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.7 KiB
Release checklist
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) anddocs/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. |
| End-to-end journey unwalked | Medium | No one has driven the authenticated flow from empty profile to recorded outcome. Sign-in needs a password, so the browser checks belong to you. |
| 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 console-only | Low | Fine under Docker; no retention or aggregation. |
| 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
- Take a manual backup as well, and restore it once to a scratch database.
deploy.shbacks up automatically, but the first deploy is the wrong time to discover a backup does not restore. - Watch
docker compose logs -f backendlive.Database.Migrate()throws on failure, so a schema problem exits the container. The runbook lists the exact log lines that mean stop. DATABASE_PROVIDERdefaults tosqlite. If it goes missing from.env, the backend does not quietly serve an empty database — it crashes withno such table: INFORMATION_SCHEMA.TABLES. Loud, but confusing if you have not seen it before.- The connection string resolves from inside the container.
Server=127.0.0.1means the backend container, not the host. - 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.
- Verify row counts before walking away. Applications and companies must match the pre-deploy numbers. This is the check that matters most.
- 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.