Commit graph

13 commits

Author SHA1 Message Date
Johannes Millan
d32f7037a3 fix(sync): preserve own vector clock counter across full-state op resets
When a full-state op (SYNC_IMPORT/BACKUP_IMPORT) arrived from another
client, mergeRemoteOpClocks reset the local clock to a "minimal" form
but only preserved the current client's counter from the incoming op's
clock. If the current client had issued ops (e.g. GLOBAL_CONFIG) not
reflected in the incoming full-state op's clock, its counter was dropped,
causing subsequent ops to reuse the same counter value. Downstream clients
then saw these ops as EQUAL (duplicate) and skipped them silently.

Fix: take max(mergedClock[clientId], currentClock[clientId]) when
rebuilding the clock after a full-state op reset.

Also add __SP_E2E_BLOCK_WS_DOWNLOAD flag to WsTriggeredDownloadService
to allow E2E tests to block automatic WS-triggered downloads during
concurrent conflict scenarios.

Fix archive conflict test by blocking WS downloads on Client A during
the concurrent edit phase so it doesn't auto-receive B's rename via
WebSocket before archiving (restoring the intended conflict scenario).

Fix LWW singleton test to assert convergence rather than specific winner.
Fix renameTask helper to avoid Playwright/Angular re-render races.
Fix shepherd.js import paths that broke the Angular dev server build.
2026-04-01 15:41:13 +02:00
Johannes Millan
821a87ba71 chore(deps): upgrade prisma from 5.22.0 to 7.6.0
Migrate super-sync-server to Prisma v7:
- Switch generator from prisma-client-js to prisma-client with local output
- Add prisma.config.ts for datasource URL configuration
- Use PrismaPg driver adapter instead of built-in Rust engine
- Add @prisma/adapter-pg and pg dependencies
- Update all imports from @prisma/client to generated client path
- Fix Buffer→Uint8Array for Bytes fields (Prisma v6 breaking change)
- Update Dockerfile: prisma CLI version, copy prisma.config.ts
- Add DATABASE_URL validation in db.ts
- Add src/generated/ to .gitignore
2026-03-31 20:05:03 +02:00
Johannes Millan
861425fd28 refactor(sync): improve vector clock implementation quality
- Replace O(N) Prisma findMany with PostgreSQL SQL aggregate
  (jsonb_each_text + GROUP BY + MAX) for snapshot vector clock
  computation, avoiding loading all ops' clocks into Node.js memory
- Add defensive regex filter (^[0-9]+$) for non-numeric JSONB values
- Fix || to ?? for nullish coalescing in vector clock value lookups
- Remove unreachable CONCURRENT check in compareVectorClocks
- Remove unused VectorClockMetrics interface and measureVectorClock
- Add 7 new unit tests for SQL aggregate path verification
- Add 10 integration tests running actual SQL against PostgreSQL
2026-03-17 13:59:40 +01:00
Johannes Millan
da8e509972 refactor(sync): unify JWT expiry to 365 days for all auth methods
Replace separate JWT_EXPIRY_MAGIC_LINK (365d) and JWT_EXPIRY_PASSKEY (7d)
constants with a single JWT_EXPIRY (365d). The auth method only matters
during login — once a JWT is issued, it represents a verified session
regardless of how the user authenticated.
2026-02-16 18:44:27 +01:00
Johannes Millan
cd2ae61ae0 Revert "feat(sync): add rolling JWT token refresh on sync requests"
This reverts commit eca323505d1923fcfc470d78fc8984251e825382.
2026-02-02 17:15:13 +01:00
Johannes Millan
717e4ba84d feat(sync): add rolling JWT token refresh on sync requests
On each successful sync request, the server now returns a fresh 14-day
JWT via X-Refreshed-Token response header. The client reads and stores
it automatically, preventing token expiry during active usage.

Server: export JWT_EXPIRY/getJwtSecret from auth, add createRefreshedToken(),
add onSend hook scoped to sync routes, expose header via CORS.
Client: store refreshed token in all 4 fetch methods, fix _getServerSeqKey
to hash only baseUrl (prevents full re-download on token change).
2026-02-02 17:15:13 +01:00
Johannes Millan
ed061f90fb fix(sync): clamp future timestamps instead of rejecting
Operations with timestamps beyond maxClockDriftMs (60s) were being
permanently rejected, causing silent data loss when a client's clock
was slightly ahead.

Now these timestamps are clamped to now + maxClockDriftMs and accepted.
A TIMESTAMP_CLAMPED audit log is recorded for debugging.

- Add clamping logic in SyncService.processOperation()
- Remove future timestamp rejection from ValidationService
- Add comprehensive tests verifying clamped values and boundaries
- Update integration test to expect acceptance
2025-12-28 12:23:36 +01:00
Johannes Millan
a52b7716aa refactor(sync): remove tombstone system
Tombstones were used for tracking deleted entities but are no longer
needed with the operation log architecture. This removes:

- Tombstone table from Prisma schema
- Tombstone-related methods from SyncService
- Tombstone mocks and tests from all test files
- Database migration to drop tombstones table

The operation log now handles deletions through DEL operations,
making the separate tombstone tracking redundant.
2025-12-28 12:08:28 +01:00
Johannes Millan
e36ba3f47d test(sync-server): add tests for snapshot skip optimization
Unit tests (6):
- Returns latestSnapshotSeq when SYNC_IMPORT exists
- Skips operations before SYNC_IMPORT for fresh clients (sinceSeq=0)
- Does NOT skip when sinceSeq is after SYNC_IMPORT
- Returns undefined latestSnapshotSeq when no full-state ops exist
- Handles BACKUP_IMPORT as a full-state operation
- Handles REPAIR as a full-state operation

Integration tests (5):
- HTTP endpoint includes latestSnapshotSeq in response
- HTTP endpoint skips pre-import ops for fresh clients
- HTTP endpoint does NOT skip when sinceSeq > SYNC_IMPORT seq
- HTTP endpoint omits latestSnapshotSeq when no full-state ops
- HTTP endpoint uses the latest full-state op when multiple exist
2025-12-17 11:36:04 +01:00
Johannes Millan
fe8cc5e7ac fix(sync): code review fixes and parentOpId support
Code review fixes:
- Fix download memory limit to correctly return success: false
- Add logging when vector clock entries are stripped during sanitization
- Add integration test for partial batch upload recovery (network failure)

parentOpId support for conflict resolution:
- Add parentOpId field to Operation type for conflict chain tracking
- Add parent_op_id column to operations table with migration
- Update sync routes schema to accept parentOpId
- Store and retrieve parentOpId in sync service

Also adds PLUGIN_USER_DATA and PLUGIN_METADATA to allowed entity types.
2025-12-12 20:47:47 +01:00
Johannes Millan
b2c4cf1548 refactor(sync): remove ACK mechanism, simplify to time-based cleanup
Remove the acknowledgeOps mechanism and simplify server-side garbage
collection to pure time-based cleanup (delete ops older than 50 days).

Client-side changes:
- Remove acknowledgeOps from OperationSyncCapable interface
- Remove acknowledgeOps implementation from super-sync.ts
- Remove ACK call from download service (with explanatory comment)
- Remove ACK-related tests and mock server methods

Server-side changes:
- Remove ACK endpoint (POST /api/sync/devices/:clientId/ack)
- Remove updateDeviceAck and getMinAckedSeq from sync service
- Simplify deleteOldSyncedOpsForAllUsers to time-based only
- Update status endpoint (pendingOps now always 0)

Documentation updates:
- Update architecture diagrams to remove ACK references
- Update cleanup process to show 50-day time-based cleanup

This simplification was needed because fresh clients couldn't ACK
(device registration only happens on upload), causing 403 errors.
Time-based cleanup at 50 days is simpler and equally effective.

Also fixes DIAGNOSTIC test to expect server-side conflict detection
(test was written before server conflict detection was added).
2025-12-12 20:47:47 +01:00
Johannes Millan
54ad38890a test(sync-server): add gzip compression integration tests
Add two integration tests for gzip-compressed snapshot uploads:
- Verify compressed snapshots work through full route handler and sync
- Verify backwards compatibility with uncompressed snapshots

Tests simulate real multi-client scenarios where one client uploads
a compressed snapshot and another client retrieves it.
2025-12-12 20:47:47 +01:00
Johannes Millan
8e73d25200 test(sync-server): add multi-client sync integration tests
Add comprehensive programmatic integration tests that simulate multiple
clients syncing through the server without requiring Playwright/browser:

- Basic sync round-trip between clients
- Sequence continuity and pagination
- Piggybacked operations in upload responses
- Fresh client bootstrap scenarios (new device joining sync group)
- Vector clock preservation and merging
- Idempotency (duplicate op rejection)
- Multi-user isolation
- Device acknowledgment and pending ops tracking
- Concurrent uploads from multiple clients
- Delete operations and tombstone tracking
- Error cases (future timestamps, invalid entity types)
- Diagnostic tests for real-world sync scenarios

Includes SimulatedClient helper class that encapsulates client-side
sync behavior (vector clocks, sequence tracking, upload/download).
2025-12-12 20:47:41 +01:00