Dates: big-bang cutover — drain to zero, coordinated flip, verification #120
Notifications
Due Date
No due date set.
Depends on
#118 Dates: the backfill drain — convert mixed-convention rows, progress surface, day-boundary invalidation
Apps/ImageApi
You do not have permission to read 2 dependencies
Reference: Apps/ImageApi#120
Reference in new issue
Block a user
Parent
ImageApi #113 — SPEC: true-instant date contract (ADR-0001).
What to build
The big-bang cutover: the moment the whole system switches to true instants, and the verification that proves it. Operator-executed with the agent preparing checklists and verifying. All three ImageApi blockers (#117/#118/#119) are merged; the server deploy is mixed-row-safe by construction (#119), so the "big bang" is really the client flip — the server can land ahead of the drain.
Test-server validation findings (2026-09-27, branch deploy, ~5.2k-row corpus)
Evidence from running #118+#119 against a copy of the prod schema on real media:
-edited.jpg, merged photos, FPV exports) lose their date tags; exiftool confirms emptyDate/Time Original+Create Dateon a sample. The drain correctly refusesfs_timeover a real capture date, so those rows are the permanentdate_utc IS NULLremainder: 14/1,313 (2.1%) and 18/3,859 (0.5%) on the two test libraries. "0 remaining" is unreachable by design; the end state is a stable floor (more_remain=false,unresolvedconstant across ≥3 ticks).day_changedfired as predicted: 38 rows on Phone Backup's final tick (late-evening walls crossing the display-zone midnight).reels_invalidated: 0was correct — no pregenerated day-span reels existed yet.date_utc; unconverted wall-rows read throughwall_in_zone(row-by-row spot check vs metadata);/memoriestiles "more realistic" per operator; the uniform 4h (summer) / 5h (winter) divergence between memories and FileViewer's MetadataView is the frozen legacy column, expected until the FV flip.server_zone() = Nonehazard, real: the test container's/etc/localtimeis a copy, not a symlink, so zone-name probes fail while the process-local tz still resolves. SetTZ=America/New_Yorkin the service env (primary guard) — the code fallback (commit615941a) is DST-correct at the wall but only as defense-in-depth. Prod must pass the preflight check below.Production sequence
server_zone():TZ=America/New_Yorkin the service env (or a real/etc/localtimesymlink +/etc/timezone);exiftoolon PATH; ffmpeg/ffmpeg on PATH;auth.dbcopied to a timestamped backup; master ≥7f34802(contains #118, #119, the wall-offset fallback fix).date_taken, byte-identical.utc_backfilllog lines (converted/skipped_missing/unresolved/day_changed),GET /librariesdate_backfill_remaining, and theimageserver_date_backfill_remaininggauge.SELECT library_id, rel_path, date_taken_source FROM image_exif WHERE date_utc IS NULL;Spot-check a sample withexiftool -DateTimeOriginal -CreateDate <abs-path>; every row should show empty date tags (edited/exported files). Any row that has tags in the file is a real bug — stop and chase.feature/photo-chat-imageapi-sync) and FileViewer-React #33 (mobile,main@978961cor later). Both flip to the new-contract triple and delete their offset-shift/re-anchor helpers.day_changedinvalidations during the drain already removed shifted reels. Verify: today's day-reelmonth_dayequals the day the/memoriestiles show; pregen zone = prefsdisplay_tz= the tiles' zone.Acceptance criteria
TZset, exiftool + ffmpeg on PATH, DB backup taken, master ≥7f34802.more_remain=falsewithunresolvedconstant across ≥3 consecutive ticks;GET /librariesdate_backfill_remainingflat.date_utc IS NULLrow verified tag-free by exiftool (sample ≥ 10 or 100%, whichever is smaller); zero rows with in-file tags.month_daymatches the/memoriesday tiles; pregen zone ==display_tzpref.Blocked by
/photosdate filters on the true-instant contract) — openExternal blockers: Apollo #37 (Apps/Apollo#37) and FileViewer-React #33 (Apps/FileViewer-React#33). Server-side blockers: #117, #118, #119. This is the coordination ticket — run it last, after the audit sign-off.
Progress (2026-09-29)
Server read surface extended — #146, merged and deployed to the test env. Beyond the triple, clients now get two server-computed fields:
wall_clock— naive ISO wall (2018-09-29T14:00:00) fromdate_view::row_display_wall, on/image/metadata(+ the date / date-clear / GPS mutation responses),/memoriesitems and/photos/gps-summary. Clients render it verbatim.true_instant— on/image/metadata, fromrow_true_instant(else filename instant, else earliest fs time). The anchor for SMS / location windows.Why server-side rather than consumers computing instant + offset from the triple: the stable floor found above (0.5–2% of rows never get
date_utc) would force every consumer to carry the per-source legacy conventions forever. With these fields the consumers do no zone math, and "insight prompt date == UI date" holds by construction (same function).Consumer flips
wall_clock; the SMS window anchors ontrue_instant; zero legacy-field formatting left. Under operator testing against the test env; merges in the coordinated window.wall_clockas well — deleting its re-anchor helpers then needs no replacement logic, and photo-for-photo web/mobile agreement (step 8) follows by construction.New finding for the verification battery (spec user story 3): the mobile "View messages" window was anchored on the legacy
date_taken(a wall stored as UTC for EXIF rows) while SMS-API dates are true instants — the window was off by the photo's UTC offset, 4–5 h even for photos taken at home. Fixed bytrue_instant(FVR #35). SMS-API itself verified correct: importers store backup epochs as UTC, the for-timestamp endpoint windows on the instant, and 40/40 sampled stored rows equal their source epochs exactly.Automated coverage for step 8:
dev/e2e/e2e.sh check(#145 harness + #146) asserts, for 97 synthetic fixtures, the expectedwall_clockon metadata / memories / gps-summary and the expectedtrue_instant— travel-zone photos (Tokyo / Paris / LA via GPS), no-GPS, filename-class, container video, mtime-only, byte-identical duplicate. 97/97 before and after the tracing rebase (#148). The e2e env pinsTZ=America/New_York, mirroring the step-1 preflight.🤖 Generated with Claude Code
Prod sign-off confirmed by the owner (2026-10-16). Post-cutover date bugs found along the way are fixed: #163 (exiftool GPS hemisphere refs, PR #165), #160 (filename year gate + re-resolve migration, PR #176) and #152 (calendar-day filters, PR #178 + FileViewer-React #47). Every consumer is on the new contract: FileViewer-React #33, Apollo #37.