Dates: after local midnight in a UTC+ zone (IST 00:00-05:30) log entries land on the wrong day / are not found #141

Closed
opened 2026-09-25 23:01:54 +00:00 by kayg · 8 comments
Owner

Found 2026-09-26 around 00:00-01:00 IST by job/headers on dev's own build: e2e/search.mjs fails 'section Log entries in Notes,Photos,Files' (log entries not found in search), and e2e/composer.mjs fails 'the RTL entry was not saved'. Both passed earlier in the evening. Hypothesis: somewhere a date is derived from UTC (server now, toISOString().slice(0,10), SQLite date('now')) while the user's local date (Asia/Kolkata, UTC+5:30) is already the next day, so entries are written to or looked up on the wrong Daily note. The owner lives in IST, so this hits every night 00:00-05:30. Find every place a 'today'/day key is computed (web and server: composer, journal API, search sources, calendar ranges, analytics, notes daily) and make the user's time zone the single source for day keys (the request carries the zone; server never assumes UTC for day boundaries). Tests: run the composer/search/calendar e2e and unit tests with a fake clock at 00:30 IST (19:00 UTC previous day) and at 23:30 UTC-7, DST boundaries too.

Found 2026-09-26 around 00:00-01:00 IST by job/headers on dev's own build: e2e/search.mjs fails 'section Log entries in Notes,Photos,Files' (log entries not found in search), and e2e/composer.mjs fails 'the RTL entry was not saved'. Both passed earlier in the evening. Hypothesis: somewhere a date is derived from UTC (server now, toISOString().slice(0,10), SQLite date('now')) while the user's local date (Asia/Kolkata, UTC+5:30) is already the next day, so entries are written to or looked up on the wrong Daily note. The owner lives in IST, so this hits every night 00:00-05:30. Find every place a 'today'/day key is computed (web and server: composer, journal API, search sources, calendar ranges, analytics, notes daily) and make the user's time zone the single source for day keys (the request carries the zone; server never assumes UTC for day boundaries). Tests: run the composer/search/calendar e2e and unit tests with a fake clock at 00:30 IST (19:00 UTC previous day) and at 23:30 UTC-7, DST boundaries too.
Author
Owner

Started review on job/tz-days. The job branch diverges from dev at bff3afb27b31341b32d3af3e0be5b5796b326f26; current dev is 7c1d6c82ea98ac3772117a534fbb923825a60bab. I am auditing the six existing #141 commits and will finish remaining day-key call sites and verification before reporting the final head and gates.

Started review on `job/tz-days`. The job branch diverges from `dev` at `bff3afb27b31341b32d3af3e0be5b5796b326f26`; current `dev` is `7c1d6c82ea98ac3772117a534fbb923825a60bab`. I am auditing the six existing #141 commits and will finish remaining day-key call sites and verification before reporting the final head and gates.
Author
Owner

Finding: crates/plugins/calendar/src/view.rs defines RangeQuery.tz as required and range only calls parse_timezone(&query.tz). Thus the range endpoint cannot resolve the zone from X-Calternal-Timezone, the user's timezone setting, or UTC when tz is absent, unlike §30 C12a and the adjacent year/events routes. I am making tz optional and applying the documented resolution order, with route tests.

Finding: `crates/plugins/calendar/src/view.rs` defines `RangeQuery.tz` as required and `range` only calls `parse_timezone(&query.tz)`. Thus the range endpoint cannot resolve the zone from `X-Calternal-Timezone`, the user's `timezone` setting, or UTC when `tz` is absent, unlike §30 C12a and the adjacent year/events routes. I am making `tz` optional and applying the documented resolution order, with route tests.
Author
Owner

Adversarial round 1 finding: concurrent appearance PUT requests 21–31 took 5.1–6.6 seconds each and returned HTTP 200. The runner labels these SLOW; no failed status was reported for these requests. This is shared-host load per the job rule. The remaining probes are still running.

Adversarial round 1 finding: concurrent appearance PUT requests 21–31 took 5.1–6.6 seconds each and returned HTTP 200. The runner labels these SLOW; no failed status was reported for these requests. This is shared-host load per the job rule. The remaining probes are still running.
Author
Owner

Adversarial round 1 flagged calendar missing time zone as accepted hostile input: the API returned HTTP 200 with tz: UTC. That request sent neither a tz query nor X-Calternal-Timezone; DESIGN §30 C12a specifies header, saved user setting, then UTC fallback, so this response is valid. I updated tests/adversarial/attack.py to assert that a missing query tz uses X-Calternal-Timezone: Europe/Berlin. The in-flight attack.py process had already loaded the old probe; this is a stale test expectation, not a server defect.

Adversarial round 1 flagged `calendar missing time zone` as accepted hostile input: the API returned HTTP 200 with `tz: UTC`. That request sent neither a `tz` query nor `X-Calternal-Timezone`; DESIGN §30 C12a specifies header, saved user setting, then UTC fallback, so this response is valid. I updated `tests/adversarial/attack.py` to assert that a missing query `tz` uses `X-Calternal-Timezone: Europe/Berlin`. The in-flight attack.py process had already loaded the old probe; this is a stale test expectation, not a server defect.
Author
Owner

Adversarial round 1 found an out-of-scope non-SLOW case: 16 concurrent bookmark-create requests returned one HTTP 201 and 15 no-response results within the probe's 10-second deadline; the server remained alive. I filed the evidence and reproduction in #154. The rest of round 1 reported latency-only SLOW responses so far.

Adversarial round 1 found an out-of-scope non-SLOW case: 16 concurrent bookmark-create requests returned one HTTP 201 and 15 no-response results within the probe's 10-second deadline; the server remained alive. I filed the evidence and reproduction in #154. The rest of round 1 reported latency-only SLOW responses so far.
Author
Owner

Finished #141 on job/tz-days.

Head: 8798b115d33dc5ae76315a6756fec6e419742152
Worktree: clean. No push, deploy, or merge performed.

Built

  • Added shared IANA-zone parsing and request/settings fallback, plus local-day, today, and DST-safe day-boundary helpers. Notes, Tasks, CalDAV, Search date:today, Calendar range/year, and file/photo day grouping now use the user zone.
  • Made the Calendar tz query optional. Resolution is query, X-Calternal-Timezone, saved user setting, then UTC, as specified in DESIGN §30 C12a. Updated OpenAPI and generated client types.
  • Made the API client send the device zone on requests and replaced UTC date slicing in browser day calculations. Added fake-clock browser flows.
  • Updated the adversarial Calendar probe to check header fallback rather than reject the documented fallback. Increased only the composer screenshot timeout for loaded shared hosts.

Root causes: UTC date extraction used the UTC date instead of the user’s local date (apps/web/src/lib/files/ShareDialog.svelte:127-138); requests did not consistently carry the device zone (packages/api-client/src/index.ts:171-174); server day calculations lacked one shared resolver (crates/calternal-plugin/src/zone.rs:80-114); and Calendar required a tz query instead of applying request context (crates/plugins/calendar/src/view.rs:299-302,386-395).

Files (44): Cargo.lock, docs/DESIGN.md, contracts/openapi.json; crates/calternal-plugin/{Cargo.toml,src/lib.rs,src/zone.rs}, crates/calternal-server/src/main.rs, crates/calternal-search/src/{indexer.rs,query.rs}, crates/calternal-search/tests/{ask_evaluation.rs,indexer.rs,relevance.rs}, crates/plugins/calendar/src/{routes.rs,view.rs}, crates/plugins/notes/src/{bookmarks.rs,lib.rs,tasks_api.rs,tasks_dav.rs}, crates/plugins/files/src/{lib.rs,uploads.rs}, crates/plugins/photos/src/index.rs; packages/api-client/src/{generated.ts,index.test.ts,index.ts}; apps/web/package.json, apps/web/e2e/{calendar.mjs,clock.mjs,composer.mjs,search.mjs}, and apps/web/src/lib/{api/notes.ts,auth/recoveryKey.ts,auth/recoveryKey.test.ts,calendar/data.ts,calendar/zones.test.ts,composer/commit.ts,files/ShareDialog.svelte,notes/NoteView.svelte,notes/api.ts,notes/collab.ts,photos/PhotosView.svelte,photos/api.ts,photos/timeline.svelte.ts}; tests/adversarial/{attack.py,attack2.py}.

Gates

  • cargo fmt --check: exit 0; no output.

  • cargo clippy --all-targets -- -D warnings: exit 0. Final output:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 24s
    
  • cargo test: exit 0; workspace unit, integration, and doc tests passed. Relevant output:

    zone::tests::local_day_follows_the_zone_on_both_sides_of_midnight ... ok
    zone::tests::day_start_is_local_midnight_and_survives_dst_changes ... ok
    search::tests::date_filter_uses_the_user_zone_for_files_and_the_day_key_for_log_entries ... ok
    view::tests::range_uses_query_then_header_then_user_setting_then_utc ... ok
    tests::log_entries_land_on_the_users_day_on_both_sides_of_midnight ... ok
    
  • bun run check:

    Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/tz-days/apps/web
    Getting Svelte diagnostics...
    
    svelte-check found 0 errors and 0 warnings
    
  • bun run test:

     Test Files  50 passed (50)
          Tests  375 passed (375)
       Start at  09:52:28
       Duration  70.77s (transform 78%, import 11%, environment 6%, tests 5%)
    
  • bun run build: exit 0. Final output included ✓ built in 2m 44s, Wrote site to "build", and ✔ done. Vite also printed its chunk-size advisory for bundles over 500 kB.

  • Production-build browser e2e passed for Search, Composer, and Calendar at real time and at 00:30 IST. Fake clock output: fake clock: 2026-09-25T19:00:00.000Z (Asia/Kolkata). The requested unit cases cover IST 00:30, Los Angeles 23:30, Lord Howe offsets, and DST days. Composer screenshot captures are attached to this report.

  • The one adversarial round reported ==== FINDINGS 67; hostile-byte probes reported ==== HOSTILE BYTES FINDINGS 0; the tz-days round reported four SLOW responses only; restart probe reported 0 findings; servers stayed alive. The runner exited 1 because of findings. Sixty-four were SLOW responses under shared-host load. One Calendar result was a stale probe expectation: the process had loaded the old test before its correction. The other non-SLOW result was 15/16 concurrent bookmark creates timing out within the probe’s 10-second limit while the server stayed alive. I filed that evidence as #154.

Known gaps and decisions

  • #154 tracks the bookmark-create timeout under a 16-way write storm. No server crash occurred. The adversarial round was not repeated, per the one-round rule; the corrected Calendar probe passed syntax checks, and its resolver order is covered by the Rust route test above.
  • A later real-time screenshot-only recapture exceeded Playwright’s 30-second screenshot limit under host load. The full real-time e2e had already passed; I raised the screenshot capture limit to 120 seconds and the full fake-IST Composer rerun passed.
  • No decisions beyond DESIGN §30 C12a were needed. The Calendar fallback follows that section exactly.

Cleanup output:

Removed 6917 files, 4.2GiB total

The web build and .svelte-kit outputs were also removed.

Finished #141 on `job/tz-days`. **Head:** `8798b115d33dc5ae76315a6756fec6e419742152` **Worktree:** clean. No push, deploy, or merge performed. ## Built - Added shared IANA-zone parsing and request/settings fallback, plus local-day, today, and DST-safe day-boundary helpers. Notes, Tasks, CalDAV, Search `date:today`, Calendar range/year, and file/photo day grouping now use the user zone. - Made the Calendar `tz` query optional. Resolution is query, `X-Calternal-Timezone`, saved user setting, then UTC, as specified in DESIGN §30 C12a. Updated OpenAPI and generated client types. - Made the API client send the device zone on requests and replaced UTC date slicing in browser day calculations. Added fake-clock browser flows. - Updated the adversarial Calendar probe to check header fallback rather than reject the documented fallback. Increased only the composer screenshot timeout for loaded shared hosts. **Root causes:** UTC date extraction used the UTC date instead of the user’s local date (`apps/web/src/lib/files/ShareDialog.svelte:127-138`); requests did not consistently carry the device zone (`packages/api-client/src/index.ts:171-174`); server day calculations lacked one shared resolver (`crates/calternal-plugin/src/zone.rs:80-114`); and Calendar required a `tz` query instead of applying request context (`crates/plugins/calendar/src/view.rs:299-302,386-395`). **Files (44):** `Cargo.lock`, `docs/DESIGN.md`, `contracts/openapi.json`; `crates/calternal-plugin/{Cargo.toml,src/lib.rs,src/zone.rs}`, `crates/calternal-server/src/main.rs`, `crates/calternal-search/src/{indexer.rs,query.rs}`, `crates/calternal-search/tests/{ask_evaluation.rs,indexer.rs,relevance.rs}`, `crates/plugins/calendar/src/{routes.rs,view.rs}`, `crates/plugins/notes/src/{bookmarks.rs,lib.rs,tasks_api.rs,tasks_dav.rs}`, `crates/plugins/files/src/{lib.rs,uploads.rs}`, `crates/plugins/photos/src/index.rs`; `packages/api-client/src/{generated.ts,index.test.ts,index.ts}`; `apps/web/package.json`, `apps/web/e2e/{calendar.mjs,clock.mjs,composer.mjs,search.mjs}`, and `apps/web/src/lib/{api/notes.ts,auth/recoveryKey.ts,auth/recoveryKey.test.ts,calendar/data.ts,calendar/zones.test.ts,composer/commit.ts,files/ShareDialog.svelte,notes/NoteView.svelte,notes/api.ts,notes/collab.ts,photos/PhotosView.svelte,photos/api.ts,photos/timeline.svelte.ts}`; `tests/adversarial/{attack.py,attack2.py}`. ## Gates - `cargo fmt --check`: exit 0; no output. - `cargo clippy --all-targets -- -D warnings`: exit 0. Final output: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 24s ``` - `cargo test`: exit 0; workspace unit, integration, and doc tests passed. Relevant output: ```text zone::tests::local_day_follows_the_zone_on_both_sides_of_midnight ... ok zone::tests::day_start_is_local_midnight_and_survives_dst_changes ... ok search::tests::date_filter_uses_the_user_zone_for_files_and_the_day_key_for_log_entries ... ok view::tests::range_uses_query_then_header_then_user_setting_then_utc ... ok tests::log_entries_land_on_the_users_day_on_both_sides_of_midnight ... ok ``` - `bun run check`: ```text Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/tz-days/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` - `bun run test`: ```text Test Files 50 passed (50) Tests 375 passed (375) Start at 09:52:28 Duration 70.77s (transform 78%, import 11%, environment 6%, tests 5%) ``` - `bun run build`: exit 0. Final output included `✓ built in 2m 44s`, `Wrote site to "build"`, and `✔ done`. Vite also printed its chunk-size advisory for bundles over 500 kB. - Production-build browser e2e passed for Search, Composer, and Calendar at real time and at 00:30 IST. Fake clock output: `fake clock: 2026-09-25T19:00:00.000Z (Asia/Kolkata)`. The requested unit cases cover IST 00:30, Los Angeles 23:30, Lord Howe offsets, and DST days. Composer screenshot captures are attached to this report. - The one adversarial round reported `==== FINDINGS 67`; hostile-byte probes reported `==== HOSTILE BYTES FINDINGS 0`; the `tz-days` round reported four SLOW responses only; restart probe reported `0 findings`; servers stayed alive. The runner exited 1 because of findings. Sixty-four were SLOW responses under shared-host load. One Calendar result was a stale probe expectation: the process had loaded the old test before its correction. The other non-SLOW result was 15/16 concurrent bookmark creates timing out within the probe’s 10-second limit while the server stayed alive. I filed that evidence as #154. ## Known gaps and decisions - #154 tracks the bookmark-create timeout under a 16-way write storm. No server crash occurred. The adversarial round was not repeated, per the one-round rule; the corrected Calendar probe passed syntax checks, and its resolver order is covered by the Rust route test above. - A later real-time screenshot-only recapture exceeded Playwright’s 30-second screenshot limit under host load. The full real-time e2e had already passed; I raised the screenshot capture limit to 120 seconds and the full fake-IST Composer rerun passed. - No decisions beyond DESIGN §30 C12a were needed. The Calendar fallback follows that section exactly. Cleanup output: ```text Removed 6917 files, 4.2GiB total ``` The web `build` and `.svelte-kit` outputs were also removed.
kayg referenced this issue from a commit 2026-09-26 10:22:06 +00:00
Author
Owner

Merged-dev adversarial evidence for #141: the tz-days probe created a Log at 2026-08-14T19:00:00Z in Asia/Kolkata, confirmed the initial text search could find it, then q=<marker> date:2026-08-15 with X-Calternal-Timezone: Asia/Kolkata returned HTTP 200 with no Log result. The probe reported ... not found with date:2026-08-15 in Asia/Kolkata. This run is on dev merged at 6e1e5656, which does not include the job/tz-days report's unmerged date:today zone-aware Search changes. I am recording this against #141 for follow-up; Analytics is not involved.

Merged-dev adversarial evidence for #141: the `tz-days` probe created a Log at `2026-08-14T19:00:00Z` in `Asia/Kolkata`, confirmed the initial text search could find it, then `q=<marker> date:2026-08-15` with `X-Calternal-Timezone: Asia/Kolkata` returned HTTP 200 with no Log result. The probe reported `... not found with date:2026-08-15 in Asia/Kolkata`. This run is on `dev` merged at `6e1e5656`, which does not include the `job/tz-days` report's unmerged `date:today` zone-aware Search changes. I am recording this against #141 for follow-up; Analytics is not involved.
Author
Owner

Merged into dev (ui-batch 642dfafc / upload-scale b74e772c / tz-days db733d1). Deploy status is tracked on #203.

Merged into dev (ui-batch 642dfafc / upload-scale b74e772c / tz-days db733d1). Deploy status is tracked on #203.
kayg closed this issue 2026-09-26 16:00:10 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#141
No description provided.