Files
jobtrackingapp/docs/release-checklist.md
T
cesnimda 25b64bee8a
CI and Deploy / test (push) Failing after 1m3s
CI and Deploy / deploy (push) Has been skipped
docs(ops): add first production deployment runbook
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>
2026-07-19 18:02:49 +02:00

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

  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.