- 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
deploy.sh and the other scripts run on the Linux deploy host and in
Docker. A CRLF checkout breaks them with "bad interpreter: bash\r".
The committed blobs are already LF, but nothing guaranteed it against a
host with core.autocrlf=true. `*.sh text eol=lf` makes it explicit.
Verified: git ls-files --eol shows attr/text eol=lf on all .sh files;
renormalize produced no index churn (already LF).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the two issues found and fixed during the release-candidate audit to
release-candidate-review.md: the follow-up reminder index that never
created on MariaDB (fix in the preceding commit), and the
nondeterministic timeline day-grouping test.
Corrects database-ownership.md drift: the MariaDB startup scenarios now
report 42 tables (measured in every scenario this audit), not the stale
40 from before the last Phase 5 tables were added, and adds the
partially-migrated heal scenario (35 -> 42) that was verified.
All claims reflect behaviour verified in this audit: 420 tests on Windows
and Linux in both ICU modes, all three Docker images built, four database
startup scenarios against live MariaDB 11 and SQLite, and a full
backup -> restore -> app-start cycle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Validation only. No application behaviour changed.
Verified against the code rather than the prose, by line number:
Before deployment -- validate_deploy_config (deploy.sh:343) and
backup_database (:347) both precede the build (:375) and the container
replacement (:382), so nothing is built, stopped or replaced without a
verified restore point.
During deployment -- startup runs ReconcileSchema (:1965),
Database.Migrate (:1973), ReconcileSchema (:1984). All seven Phase 4/5
migrations confirmed to have a literally empty Up body, which is what
makes a code rollback safe.
Health checks and rollback -- backend and frontend healthchecks present,
frontend gated on backend health, rollback documented in two places with
the code-vs-database distinction.
After deployment -- added an eight-point owner checklist covering login,
existing applications, workspace, career profile, CV builder, public CV,
AI features and attachments. Each item names what wrong looks like,
because "it loaded" is not a check. Merged the previous overlapping
"After deploying" list into it rather than leaving two competing
checklists.
Recorded the CV language ICU defect as closed, with the note that it was
invisible to a normal local test run -- the clearest evidence in this
review that passing locally and correct in the deployed container are
different claims.
Sections are now READY / BLOCKED / MANUAL VERIFICATION, with accepted
limitations kept separate. Test count updated to 420.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restructured into READY / BLOCKED / MANUAL, with accepted limitations
kept separate.
B1 (backup selected the wrong provider and reported success) and N2
(/health always reported version: unknown) are both closed and verified;
their original findings are kept because the failure modes are worth
understanding. N1 closed with the .env.example additions.
One blocker remains and it is external: the CI runner. Stated with what
it needs from the owner, and with the decision it forces -- fix the
runner, or deploy deliberately from a locally verified commit knowing CI
is red.
MANUAL now leads with backup and restore readiness: the mechanism is
verified against containers, but only the owner can prove it works on
production data. Added a note that authenticated smoke testing is not an
automation gap that more work would close -- sign-in needs a password,
and no automated step here should handle one.
Validation only. No application behaviour changed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
deploy.sh symlinked /opt/job-tracker/shared/.env for docker compose but
never loaded it into its own shell. Its own decisions therefore ran
against an empty environment: DATABASE_PROVIDER fell back to sqlite on a
MariaDB host, so the deploy tarred the data volume, printed "Backup
verified" and continued with no database dump. The operator saw a green
backup line and a new file in the backups directory, and had no restore
point.
Load the shared env before any decision. Parsed line by line rather than
sourced, because a compose .env is not a shell script and an unquoted
value containing spaces would execute as a command. Values already in
the environment win, so CI-provided APP_VERSION and friends still
override the file. No value is echoed.
Remove the sqlite default. DATABASE_PROVIDER must be stated; missing or
unrecognised aborts the deploy.
Validate deployment configuration before the backup, and so before
anything is built, stopped or replaced: the connection string when the
provider needs one, AI_SERVICE_TOKEN (compose declares it with :?) and
AUTH_JWT_KEY (the backend throws on a blank key). Names in the output,
never values.
Verify each backup against its own format. A dump must be valid gzip,
contain CREATE TABLE, and carry the "Dump completed" trailer, so a dump
that died partway through is rejected. An archive must contain
jobtracker.db. A tar can no longer pass the dump check.
Also resolve the SQLite volume by its project-prefixed name and fail if
absent. The bare jobtracker_data name would have silently created an
empty volume and backed that up -- the same class of bug, found while
testing this fix.
Verified against a seeded MariaDB 11 container and real Docker volumes:
provider selection, all four validation failures, both backup formats
and their failure paths, truncated and trailer-stripped dumps, and zero
secret occurrences across every test's output.
Docs updated for the drift: deploy/README.md, deploy/first-production-
deployment.md, docs/release-candidate-review.md (B1 closed) and
.env.example, which now names the two database variables.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final validation pass before the first production deployment. Findings
only; no application code changed.
Verified against the implementation rather than the other documents:
migration and reconciler ownership, startup order, authorization
coverage, tenant isolation, AI service protection, file access, the
five architecture rules, container dependency ordering and failure
behaviour.
Two blocking items, one new: deploy.sh symlinks the shared .env for
docker compose but never sources it, so DATABASE_PROVIDER is unset in
the script's own shell and backup_database takes the SQLite branch on a
MariaDB host. It tars the data volume, verify_backup only checks the
file is non-empty on that path, and the deploy reports a verified
backup that contains no database dump. Same root cause silently
disables the APP_PUBLIC_BASE_URL smoke check and the Ollama warmup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>