docs(ops): record release-candidate audit findings
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>
This commit is contained in:
@@ -150,9 +150,10 @@ All four scenarios, 2026-07-19, against MariaDB 11 and SQLite:
|
||||
|
||||
| Scenario | Result |
|
||||
|---|---|
|
||||
| Empty MariaDB | 40 tables created, app starts |
|
||||
| Populated MariaDB, restart | idempotent — still 40 tables, rows preserved |
|
||||
| Empty MariaDB via the Docker image | 40 tables created, app starts |
|
||||
| Empty MariaDB | 42 tables created, app starts |
|
||||
| Populated MariaDB, restart | idempotent — still 42 tables, rows preserved |
|
||||
| Partially-migrated MariaDB (Phase 4/5 tables dropped) | healed 35 → 42, surviving rows preserved |
|
||||
| Empty MariaDB via the Docker image | 42 tables created, app starts |
|
||||
| Fresh SQLite | 42 tables created, app starts |
|
||||
| Existing partially-migrated SQLite dev DB (34 tables) | upgraded to 44 tables, 13 applications and 8 companies preserved |
|
||||
|
||||
|
||||
@@ -21,6 +21,13 @@ re-verified by line number rather than by prose, and a full backup → restore
|
||||
was completed and recorded in
|
||||
[`docs/operations/production-backup-verification.md`](operations/production-backup-verification.md).
|
||||
|
||||
**Release-candidate audit, 2026-07-19.** A full verification pass — 420 backend tests (Windows + Linux,
|
||||
both ICU modes), frontend tests/typecheck/build, all three Docker images, and all four database startup
|
||||
scenarios run against live MariaDB 11 and SQLite — found and fixed two more issues: a follow-up reminder
|
||||
index that never created on MariaDB (and logged a false rollback signal on every boot), and a
|
||||
nondeterministic timeline test. Both are recorded below. Backup → restore → app-start was re-run
|
||||
end to end. No open blocker remains in the code or deployment path.
|
||||
|
||||
**Verdict: one blocker remains, and it is external — CI (B2).** Nothing in the application or the
|
||||
deployment path is now known to be blocking. Everything verified here is *local* verification; CI has
|
||||
proven none of it.
|
||||
@@ -276,6 +283,43 @@ different claims — which is exactly what B2 below leaves unresolved.
|
||||
|
||||
---
|
||||
|
||||
### Follow-up reminder index never created on MariaDB — **CLOSED 2026-07-19**
|
||||
|
||||
Found during the release-candidate audit, watching a real backend boot against an empty MariaDB.
|
||||
|
||||
`IX_JobApplications_OwnerUserId_FollowUpAt` was declared `(OwnerUserId(191), FollowUpAt)` with **no
|
||||
prefix length on `FollowUpAt`**. But `FollowUpAt` is `text` on MariaDB — `JobApplications` is
|
||||
migration-owned and the migration was scaffolded against SQLite, which stores `DateTimeOffset` as
|
||||
`TEXT`. A text column cannot be indexed without a prefix length, so the index failed the 3072-byte key
|
||||
check on **every** MariaDB boot, was caught by `TryCreateIndex`, and was silently skipped.
|
||||
|
||||
Two consequences, both real:
|
||||
|
||||
- The follow-up reminder query (`OwnerUserId + FollowUpAt`) ran unindexed — the index the code says it
|
||||
creates never existed.
|
||||
- Every healthy boot logged `Specified key was too long` — the exact string
|
||||
`deploy/first-production-deployment.md` lists as a **stop-and-roll-back** signal. An operator
|
||||
following the runbook could abort a perfectly good deploy on a false alarm.
|
||||
|
||||
**Fixed** by prefixing `FollowUpAt(20)`, matching the `Status(50)` fix the author already applied one
|
||||
line below for the identical longtext problem. ISO-8601 date strings sort lexicographically, so a
|
||||
20-char prefix stays useful for the reminder scan.
|
||||
|
||||
*Verified* on a fresh empty MariaDB 11 container: index now created with both key parts, **zero**
|
||||
"too long" lines, **zero** skipped indexes, **zero** unhandled exceptions, 42 tables, app healthy.
|
||||
Audited the whole class — `FollowUpAt` was the only unprefixed text column in any reconciler composite
|
||||
index; the datetime columns on reconciler-owned tables are `datetime(6)`.
|
||||
|
||||
### Timeline day-grouping test was nondeterministic — **CLOSED 2026-07-19**
|
||||
|
||||
`Timeline_groups_by_day_newest_first` seeded two "same day" events with
|
||||
`DateTime.Now.AddDays(-3).AddHours(2)`, which crossed midnight whenever the wall clock was within two
|
||||
hours of it — failing the test ~2 hours out of every 24, including in CI. The service is correct
|
||||
(groups by `.Date`); the test was fixed to anchor the older events to `DateTime.Today` plus fixed
|
||||
hours. Verified passing at 22:35 local (inside the failing window) and on Linux, both ICU modes.
|
||||
|
||||
---
|
||||
|
||||
## BLOCKED — requires external action
|
||||
|
||||
One item. Nothing in this repository can clear it.
|
||||
|
||||
Reference in New Issue
Block a user