- consolidate API ownership and remove dead vendor code - add Stripe billing, learning paths, and public CV hardening - add migration, recovery, security, audit, and browser gates
6.0 KiB
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, andBLOCKERS.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) 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. |
| 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
- 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.