Skip to content

fix(schedule): apply HTTP full snapshot as authoritative SQLite state - #225

Merged
Wintercom merged 3 commits into
1024XEngineer:mainfrom
znnnnnnn-wil:codex/fix-schedule-full-snapshot-replacement
Aug 13, 2026
Merged

fix(schedule): apply HTTP full snapshot as authoritative SQLite state#225
Wintercom merged 3 commits into
1024XEngineer:mainfrom
znnnnnnn-wil:codex/fix-schedule-full-snapshot-replacement

Conversation

@znnnnnnn-wil

Copy link
Copy Markdown
Contributor

Summary

Part of #176

  • add a dedicated applyFullScheduleSnapshotToSqlite boundary for HTTP recovery while keeping applyScheduleSnapshotToSqlite as the unchanged WebSocket incremental path
  • replace the target account's SQLite cloud projection atomically: remove stale occurrence overrides first, purge schedules absent from the full snapshot, then apply the authoritative schedules and overrides
  • accept schedules = [], preserve account isolation and device-local reminder runtime fields, and retain per-schedule cloud_revision protection for rows still present in the snapshot
  • add integration and type-contract coverage for stale deletion, empty recovery, inserts, authoritative updates, override replacement, account isolation, rollback, revision protection, and WebSocket regression

Verification

  • npx vitest run tests/integration/scheduleSyncService.test.ts — 1 file / 20 tests passed
  • npm run check (Node 20.20.2, clean LF checkout matching CI) — ESLint passed; Prettier passed; TypeScript passed; Vitest 3 files / 38 tests passed; Jest 7 suites / 28 tests passed
  • npx expo export --platform android --output-dir dist — Android bundle exported successfully
  • git diff --check — passed

Scope

This PR only fixes HTTP Full Snapshot → client SQLite full-recovery semantics.

  • WebSocket confirmed-snapshot application remains incremental: applying A does not remove B or C.
  • Reminder runtime execution, Agent behavior, ASR/TTS, GUI, WebSocket transport/protocol, and backend HTTP contracts are unchanged.
  • Existing rows preserve device-owned runtime fields (next_trigger_at, snoozed_until, geofence_armed, local disposition state, and sync_status) through the existing repository upsert semantics; cloud-owned schedule/reminder configuration fields are refreshed.
  • Deletes and writes are account-scoped and run inside one exclusive SQLite transaction. Any failure rolls back schedules and occurrence overrides together.

Concurrency note

For schedule IDs present in the HTTP snapshot, a lower incoming revision cannot overwrite a newer local cloud_revision; its local overrides are preserved as part of the same protection. The current HTTP contract has no account-wide snapshot version or cursor, so it cannot totally order an HTTP response against a WebSocket-created schedule that commits after the HTTP snapshot was generated but before local application. The exclusive SQLite transaction prevents interleaved/partial local writes, but eliminating that protocol-level absent-row race would require a future global snapshot version and is intentionally outside this focused PR.

@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
timeflow Ready Ready Preview Aug 13, 2026 7:24am

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No high-confidence issues found in the changed sync implementation. The fixed-range diff was reviewed across code quality, performance, security, and documentation. Focused Vitest execution was attempted but could not run because the checked-out workspace has no usable vitest binary (Permission denied / dependencies unavailable).

View job run

@Wintercom Wintercom left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

发现一个确认的问题:isValidScheduleend_time 解析为 end,但没有检查非空 schedule.end_time 在解析失败时的情况。比如 end_time: "not-an-iso-instant" 会通过校验(因为后续只有 end !== null 时才比较),随后被写入 SQLite;这与 start_time、created_at、updated_at 和 reminder_trigger_at 的校验不一致,也会污染本地投影。建议在应用前增加 schedule.end_time !== null && end === null 的拒绝条件,并补充回归测试。

@znnnnnnn-wil

Copy link
Copy Markdown
Contributor Author

@Wintercom 已在 d190f41 修复:现在会拒绝无法解析的非空 end_time,并新增回归测试验证返回 invalid_snapshot 且 SQLite 不发生任何写入。相关测试和 CI 均已通过。

@Wintercom Wintercom left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@Wintercom
Wintercom merged commit 0fa1854 into 1024XEngineer:main Aug 13, 2026
5 checks passed
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.

2 participants