Skip to content

fix: prevent sync timestamp corruption with type-safe UnixSecs wrapper (#157)#160

Merged
careck merged 1 commit into
masterfrom
fix/timestamp-safety
Apr 24, 2026
Merged

fix: prevent sync timestamp corruption with type-safe UnixSecs wrapper (#157)#160
careck merged 1 commit into
masterfrom
fix/timestamp-safety

Conversation

@careck
Copy link
Copy Markdown
Member

@careck careck commented Apr 24, 2026

Summary

Synced notes displayed wildly wrong dates (year 56000+) because the sync replay path stored HLC wall_ms (milliseconds) in columns that expect Unix seconds. This PR fixes the bug and makes it structurally impossible to recur.

  • Root cause: apply_incoming_operation in sync.rs wrote ts.wall_ms as i64 (milliseconds) into created_at/modified_at columns, while local creation wrote chrono::Utc::now().timestamp() (seconds). Frontend does new Date(timestamp * 1000).
  • Runtime fix: All 6 sync locations now convert to seconds via HlcTimestamp::to_unix_secs()
  • Compile-time prevention: New UnixSecs(i64) newtype on Note, UserScript, and AttachmentMeta timestamp fields — assigning raw milliseconds is now a type error
  • Regression test: test_apply_incoming_create_note_stores_seconds_timestamp verified red-green

Key design decisions

  • No From<i64> — forces UnixSecs::now(), UnixSecs::from_secs(), or ts.to_unix_secs()
  • #[serde(transparent)] — JSON unchanged, no frontend impact
  • Approach A (seconds-only wrapper) — future Approach B can also wrap milliseconds side (see issue comment)

Files changed (18)

Area Files Change
New type timestamp.rs UnixSecs + traits
HLC hlc.rs to_unix_secs() method
Structs note.rs, user_script.rs, attachment.rs i64UnixSecs
Sync fix sync.rs ts.wall_msts.to_unix_secs()
Call sites notes.rs, scripts.rs, hooks.rs, undo.rs, attachments.rs, export.rs, mod.rs chrono::Utc::now().timestamp()UnixSecs::now()
Tests tests.rs, scripting/tests.rs, display_helpers.rs Literals → UnixSecs::ZERO/from_secs()

Test plan

  • cargo test -p krillnotes-core — 608/608 pass
  • cargo check -p krillnotes-desktop — clean
  • Regression test red-green verified
  • Manual: sync a note between two peers, verify timestamps match

Supersedes #158 and #159. Closes #157.

The sync replay path stored HLC wall_ms (milliseconds) directly into
created_at/modified_at columns, while local creation stored Unix seconds.
The frontend multiplies by 1000 expecting seconds, so synced notes
displayed dates like year 56000+.

Fix: introduce UnixSecs(i64) newtype that makes this mismatch a compile
error. HlcTimestamp::to_unix_secs() is the only conversion path.
No From<i64> impl forces callers to use named constructors.
serde(transparent) preserves JSON format — no frontend changes needed.

Closes #157
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Modified and Created Timestamps are all wrong in shared notes

1 participant