Avoids the operations table entirely (sync_devices + users only) so it
returns in milliseconds even when operations grows past 1 GB. Useful when
the standard active-users command is too slow on large prod DBs.
The "just 1ms over max clock drift boundary" test captured Date.now()
once in the test and again inside uploadOps. If the second sample
advanced by >=1ms, the op was within the drift window from the
service's perspective, clamping never fired, and toBeLessThan failed
with equal values.
Freeze time with vi.useFakeTimers/setSystemTime so both samples
match, and tighten the assertion to the exact clamped value.
The showUsage query ran two correlated subqueries over the full
operations table once per user, each calling pg_column_size(payload)
on every row, then sorted all users before LIMIT 20. On a 1.85 GB
operations table with 3.6k users this effectively never returned.
Aggregate per-user size and count in a single CTE pass, then join.
One sequential scan instead of scans × users.
- Separate JsonParseError and SyncDataCorruptedError handlers in sync wrapper
- Handle corrupted remote data gracefully instead of throwing
- Add getOrGenerateClientId() as unified entry point, eliminating dual injection
of ClientIdService + CLIENT_ID_PROVIDER in snapshot-upload, file-based-encryption,
and sync-hydration services
- Use crypto.getRandomValues() instead of Math.random() for client ID generation
- Warn user when stored client ID is invalid and must be regenerated
- Fix flaky e2e supersync tests (premature waitForURL match on daily-summary URL)
The super-sync-server pretest script only ran `prisma generate` but
didn't build the @sp/shared-schema workspace dependency. This caused
15 test suites (and 20+ individual tests) to fail with "Failed to
resolve entry for package @sp/shared-schema" because the dist/ output
didn't exist.
https://claude.ai/code/session_01EsRx9RENAEdsNUqMQZqjGN
Co-authored-by: Claude <noreply@anthropic.com>
* fix(sync): prevent magic link prefetch failures and improve auth resilience
- Make /magic-login prefetch-immune: GET now renders a confirmation page
instead of consuming the single-use token. A "Log In" button triggers
POST /api/login/magic-link/verify which does the actual verification.
This prevents email client link prefetchers (Outlook SafeLinks, Gmail)
from consuming magic link tokens before users click them.
- Increase rate limits on auth endpoints for shared-IP scenarios (e.g.
university NAT). Global: 100→500, register: 10→50, login: 5→30,
verify: 10→50, magic-login page: 10→50.
- Add trustProxy to Fastify so req.ip uses X-Forwarded-For behind
reverse proxies instead of collapsing all clients to one rate bucket.
- Clear stale SuperSync auth tokens after 3 consecutive AuthFailSPErrors
instead of never clearing them. Prevents persistent failure loops where
an invalid token keeps retrying indefinitely until app reinstall.
- Improve registration-to-login UX messaging to emphasize email
verification is required before login links will work.
https://claude.ai/code/session_01WkbLzvhguQwRDtk7ht38aY
* chore: update package-lock.json from npm install
https://claude.ai/code/session_01WkbLzvhguQwRDtk7ht38aY
* fix(sync): address review feedback for auth resilience
- Remove conflicting style.css link from magic-login page
- Change trustProxy from true to 1 (trust single hop only)
- Increase /login/magic-link rate limit from 30 to 50 per 15min
- Delete dead magic-login-redirect.js (replaced by magic-login-confirm.js)
- Reset auth failure counter on non-auth errors (truly consecutive)
- Add test for counter reset on non-auth error between auth errors
https://claude.ai/code/session_01WkbLzvhguQwRDtk7ht38aY
* fix(sync): increase passkey rate limits and fix CSP inline script violations
- Increase passkey endpoint rate limits to match magic-link equivalents (50/15min)
- Extract inline scripts from /reset-password and /recover-passkey pages
into external JS files to comply with CSP scriptSrc: ["'self'"]
- Remove dead safeJsonForScript helper (no longer needed)
- Increase /recover-passkey page rate limit to 50/15min for consistency
https://claude.ai/code/session_01WkbLzvhguQwRDtk7ht38aY
* fix(sync): update security tests for escapeHtml and fix recover/passkey rate limit
- Update server-security.spec.ts: tests now check for escapeHtml output
(<, ") instead of safeJsonForScript output (\u003c), matching
the new data-token attribute approach
- Increase /recover/passkey rate limit from 30 to 50 per 15min to match
/login/magic-link (both send email with a secret link)
https://claude.ai/code/session_01WkbLzvhguQwRDtk7ht38aY
* fix(sync): reset auth failure counter after successful download
Move the counter reset from the final InSync return to right after
downloadRemoteOps() succeeds. This ensures early returns (cancelled,
LWW pending, payload rejected) also break the consecutive-failure
chain, since a successful download confirms auth is working.
https://claude.ai/code/session_01WkbLzvhguQwRDtk7ht38aY
---------
Co-authored-by: Claude <noreply@anthropic.com>
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.
Previously, `npx prisma@5.22.0 migrate deploy` in CMD downloaded prisma
from npm on every container start, making startup slow and dependent on
network availability. Now prisma is installed in the image at build time.
Also increases health check start_period from 10s to 30s to give
migrations time to complete before Docker starts counting failures.
* feat(sync): add Helm chart for SuperSync Kubernetes deployment
Includes Deployment, StatefulSet (PostgreSQL), Service, Ingress,
ConfigMap, Secret, HPA, PDB, NetworkPolicy, and test templates.
Supports both bundled PostgreSQL and external database configurations.
* feat(sync): add WebSocket push notifications for near-realtime sync
Server: Fastify WebSocket plugin with connection manager, app-level
heartbeat (30s), debounced notifications, and per-user routing.
Client: WebSocket service with exponential backoff reconnection,
WS-triggered download service, and reduced polling when connected.
* fix(sync): improve WebSocket error handling and reactivity
Make syncInterval$ reactive to WS connection state, fix Set/Map
mutation during heartbeat iteration, add error handling to WS route
handler, separate JSON parsing from message handling, detect auth
failures in WS-triggered downloads, and add logging to all empty
catch blocks.
* fix(sync): address PR review findings for WebSocket and Helm
Fix race condition in WS-triggered download pipeline by moving
isSyncInProgress filter after debounce and adding guard in
_downloadOps. Add logging to remaining empty catch blocks, fix
missing $NODE_IP in Helm NOTES.txt, and correct inaccurate
comments in values.yaml and ws-triggered-download.service.ts.
* fix(sync): add rate limiting to WebSocket upgrade endpoint
Limit WS connection attempts to 10 per minute per IP to prevent
connection flooding, matching the rate-limit pattern used by other
sync endpoints.
* fix(sync): extract shared constants, fix debounce correctness, replace deprecated toPromise
Extract CLIENT_ID_REGEX and MAX_CLIENT_ID_LENGTH into sync.const.ts
to prevent drift between sync.routes.ts and websocket.routes.ts.
Fix debounce in notifyNewOps to accumulate excluded client IDs across
rapid calls from different clients, preventing self-notifications.
Replace deprecated toPromise() with firstValueFrom in connectWebSocket
and _sync methods. Add .catch() to reconnect path and logging to
silent early returns in _downloadOps.
* fix(build): restore correct package-lock.json and fix upstream lint errors
* revert: restore upstream HTML formatting to match CI prettier config
* fix(sync): use DownloadOutcome discriminated union in WsTriggeredDownloadService
* fix(sync): narrow TokenVerificationResult before accessing userId
* fix(sync): await async getProviderById in connectWebSocket
Missing await caused the Promise object to be cast to
SuperSyncProvider, making getWebSocketParams undefined.
* feat(sync): add SEED_USERS env var to create verified users on startup
For self-hosted single-user setups: set SEED_USERS=email1,email2 to
create verified users on boot and log their access tokens. Skips
existing users. Removes need for SMTP/magic link registration.
Also fix Dockerfile to use npm install instead of npm ci for lockfile
compatibility.
* fix(sync): address PR review feedback for Helm chart and server
Security:
- Remove seed-users.ts (logged full JWT tokens to stdout)
- Add fail assertions for missing jwtSecret and postgresql.password
- Add smtp.user/smtp.password values fields
- Add from: selector to NetworkPolicy ingress
- Add egress rule for external database when postgresql.enabled=false
Correctness:
- Fix PostgreSQL StatefulSet indentation for non-persistent mode
- Fix NOTES.txt panic on empty tls list (use len instead of index)
- Restore npm ci in Dockerfile by using node:24-alpine (ships npm 11)
- Add Recreate deployment strategy when using RWO PVC
Operational:
- Add fail guard preventing HPA maxReplicas > 1 (in-memory WS state)
- Fix PDB to use maxUnavailable instead of minAvailable
- Add WebSocket ingress timeout annotation examples
- Add Prisma db push init container for schema migrations
- Default serviceAccount.automount to false
- Add Chart.yaml maintainers, home, sources metadata
* feat(sync): add ALLOWED_EMAILS env var to restrict registration
Supports fully qualified emails (user@example.com) and domain
wildcards (*@example.com). When unset, all emails are allowed.
Applied to all three registration endpoints (passkey options,
passkey verify, magic link).
* fix(sync): exempt health endpoint from rate limiting
Kubernetes liveness/readiness probes hit /health every 5-15s,
exhausting the global rate limit (100 req/15min) and causing
429 responses that trigger container restarts.
* fix(sync): harden WebSocket, Helm chart, and sync services from multi-agent review
- Pin prisma@5.22.0 in init container and use migrate deploy instead of db push
- Add per-user WebSocket connection limit (max 10) to prevent resource exhaustion
- Add replicaCount > 1 fail guard in deployment template (in-memory WS state)
- Set maxPayload: 1024 on WebSocket plugin (only pong messages expected)
- Remove premature IN_SYNC status from download-only WS-triggered sync
- Fix double removeConnection on error+close events (let close handle cleanup)
- DRY debounce logic in notifyNewOps and store latestSeq on pending entry
- Remove dead fromClientId from NewOpsNotification and WsMessage interfaces
- Add takeUntilDestroyed to _wsProviderCleanup subscription
- Simplify email-allowlist.ts to eager init (eliminate mutable state)
- Guard connectWebSocket at call site for non-SuperSync providers
- Add distinctUntilChanged to syncInterval$ to prevent unnecessary resets
- Add container-level securityContext to PostgreSQL StatefulSet
- Fix HPA maxReplicas default to 1 (matches single-replica constraint)
* fix(sync): restore migration in Dockerfile CMD for non-Helm Docker users
Helm users get migration via init container (runs first, CMD becomes no-op).
Docker-compose/plain Docker users get automatic migration back on startup.
* fix(boards): add missing drag delay for touch and extract shared signal
Add cdkDragStartDelay to board-panel drag items, which was missing
entirely. Extract the repeated `isTouchActive() ? DRAG_DELAY_FOR_TOUCH : 0`
expression into a shared `dragDelayForTouch` computed signal and refactor
all 8 components to use it.
* test(sync): add comprehensive WebSocket test coverage
Add unit tests for the new WebSocket real-time sync notification pipeline:
- WebSocketConnectionService (server): connection lifecycle, max-per-user
limits, notification debouncing, heartbeat, graceful shutdown (13 tests)
- WebSocket routes validation: token/clientId validation, close codes,
error handling, validation ordering (18 tests)
- SuperSyncWebSocketService (frontend): reconnection with exponential
backoff, heartbeat, disconnect cleanup, URL conversion (6 tests)
- WsTriggeredDownloadService: auth error handling, pipeline resilience,
start() idempotency (3 tests)
- SyncWrapperService: connectWebSocket guards for non-SuperSync, null
params, and already-connected cases (3 tests)
- E2E realtime push: verifies WS-triggered download between two clients
Quality fixes:
- Replace waitForTimeout with syncAndWait in E2E
- Add explanatory comment for microtask flushing in sync-wrapper spec
- Use getter for isSyncInProgress mock in ws-triggered-download spec
---------
Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
- Thread syncImportReason field from operation creation through compact
codec, sync providers, server database, and back to receiving clients
- Constrain server Zod schema to z.enum() matching client SyncImportReason
type for defense-in-depth validation
- Use !== undefined in codec for consistency with entityId/entityIds
- Rename piggybackedImport/incomingSyncImport to piggybackedFullStateOp/
incomingFullStateOp for accuracy
The monitor:all, analyze-storage, and related npm scripts used tsx
which isn't available in the production Docker image. Switch to
node dist/scripts/*.js and add :dev variants for local development.
The production container runs as non-root user `supersync` without tsx
(dev dependency excluded via --omit=dev). Analysis and monitor-all
commands failed because they tried to use tsx/npx tsx which couldn't
write to node_modules. All scripts are already compiled to dist/ during
the Docker build, so use those directly.
- Pin Caddy image to 2.11-alpine to prevent breaking changes from
floating tags
- Validate Caddyfile syntax before deploying to catch config errors
early
- Check all container states after startup to detect crashes before
waiting on the HTTPS health check
- Show logs from all services on failure, not just supersync
Caddy v2 reverse_proxy does not support a top-level `timeout`
subdirective. The response_header_timeout in the transport block
already covers long-running operations.
Caddy Alpine images set file capabilities on the binary via setcap.
The no-new-privileges security option prevents the kernel from honoring
file capabilities during exec, causing "operation not permitted" on
startup. Container remains hardened with cap_drop: ALL + cap_add:
NET_BIND_SERVICE.
Newer Caddy Alpine images set file capabilities on the binary, which
requires NET_BIND_SERVICE to bind to ports 80/443. Without it,
cap_drop: ALL prevents execution entirely.
Prisma's $queryRaw tagged template treats ${} as parameterized values, not
raw SQL. Conditional fragments like `${userId ? \`WHERE ...\` : ''}` were
sent as string parameters, causing PostgreSQL syntax error 42601.
Mask user emails by default (--unmask to reveal). Replace all any[]
query results with typed interfaces. Fix showUsageHistory sort that
compared formatted byte strings. Add parseIntArg validation for CLI
flags. Fix unbounded recursion in analyzePayload. Replace execSync with
execFileSync for DATA_DIR to prevent shell injection. Add --no-save flag
for usage command.
* feat(sync): add active-users command to monitor CLI
Adds a new `active-users` command to the SuperSync monitor script that reports:
- Total registered and verified user counts
- Active users by time period (24h, 7d, 30d, 90d) based on device and sync activity
- New registration counts
- Recently active users table with device count and ops
- Users who never synced
Usage: npm run monitor -- active-users
https://claude.ai/code/session_014Tc5vtXW4Z8QZFMDFWKErP
* feat(sync): add engaged users metric to active-users report
Shows users who were active on 3+ distinct days in the last 2 weeks
with new sync operations, giving a measure of genuine recurring usage.
https://claude.ai/code/session_014Tc5vtXW4Z8QZFMDFWKErP
* docs(sync): add active-users to docker monitoring docs
Add missing active-users command to docker-monitor.sh case statement,
help text, and DOCKER-MONITORING.md guide.
https://claude.ai/code/session_014Tc5vtXW4Z8QZFMDFWKErP
* refactor(sync): improve active-users command from review feedback
- Fix timezone-unsafe DATE(): use AT TIME ZONE 'UTC' with explicit
double precision cast for consistent day boundaries
- Replace correlated subquery with LEFT JOIN for ops_7d count
- Show total active count when LIMIT truncates the table
- Add --threshold and --limit CLI flags for flexibility
- Combine device/ops metrics into single line (connected / syncing)
- Add skipInQuick to run-all-monitoring.ts
- Update docker-monitor.sh header, help text, and DOCKER-MONITORING.md
with active-users command, flags, and performance notes
- Clarify "never synced" output label
https://claude.ai/code/session_014Tc5vtXW4Z8QZFMDFWKErP
---------
Co-authored-by: Claude <noreply@anthropic.com>
Add pretest script to run prisma generate before tests, ensuring the
Prisma client is always generated in any environment. Fix verifyToken
mocks across all test files to include valid:true matching the
TokenVerificationResult discriminated union. Expand jsonwebtoken mock
to include JsonWebTokenError/TokenExpiredError stubs needed by auth.ts.
Add $queryRaw to transaction mock in sync-fixes tests.
Server: verifyToken() catch-all was returning 401 "Invalid token" for ALL
errors including database failures. Now only JWT-specific errors
(JsonWebTokenError, TokenExpiredError) return 401. Database/infrastructure
errors propagate as 500s with sanitized logging.
Client: SuperSync AuthFailSPError no longer clears stored credentials —
the server rejection may be transient. The snackbar with "Configure"
button still shows so users can re-auth on genuine failures. Dropbox and
WebDAV still clear immediately so their OAuth/re-auth flows work.
Translate all missing keys to achieve full coverage (2086/2086)
for fr, pt-br, zh-tw, ro, ro-md, zh, es, de, and cs.
Also remove stale keys and include updated translations
for other locales. Remaining languages still have partial coverage.
Adds git pull --ff-only before docker compose pull so scripts,
docker-compose.yml, and other repo files are updated on each deploy.
Falls back gracefully if pull fails.
- Remove email addresses from all log messages (~18 locations) for GDPR
compliance, replacing with userId where available
- Downgrade debug credential logs from info to debug level
- Remove options.user from passkey registration log (contained email in
JSON payload)
- Add container security hardening to docker-compose.yml:
- no-new-privileges on all containers
- cap_drop: ALL on supersync and caddy
- Memory limits (supersync 512m, postgres 1g, caddy 256m)
- CPU limit on supersync (1.0)
- Add .health-alert/ to gitignore
- Add health-alert.sh cron script for container monitoring with
OOM detection, disk checks, and email alerting
- Add accounts-only pg_dump (users + passkeys) to backup script for
lightweight disaster recovery when clients still have data
- Add pipefail to backup script to catch silent dump failures
- Add test endpoint to simulate partial server revert (ops-after/:serverSeq)
- Add 6 e2e tests covering all disaster recovery scenarios:
complete data loss, partial revert, accounts-only restore (API + SQL),
full dump restore + reset account, and all-clients-lost recovery
- Add backup-and-recovery.md with setup, recovery procedures, and
decision tree
- 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
Add deadline support for tasks with date-only and time-specific deadlines.
Core:
- Add deadlineDay, deadlineWithTime, deadlineRemindAt fields to task model
- Add NgRx actions (setDeadline, removeDeadline, clearDeadlineReminder)
- Add meta-reducer with mutual exclusivity and input validation
- Register deadline actions in ActionType enum and op-log codes
UI:
- Create deadline dialog with calendar, time input, and reminder options
- Add deadline badge to task list row with overdue warning color
- Add deadline item to task detail panel with flag icon
- Add deadline options to task context menu
- Show deadlines in scheduled list page
Reminders:
- Add deadline reminder effects and selectors
- Show reminder dialog with grouped deadline/schedule sections
- Handle deadline reminder clearing on dismiss, done, add-to-today
- Cancel Android native deadline notifications on delete/archive
Planner:
- Show deadline tasks in planner day view and overdue section
- Create dedicated planner-deadline-task component
- Optimize deadline grouping with O(N) single-pass selector
Other:
- Add deadline-today banner effect for unplanned deadline tasks
- Add translation keys for all deadline UI strings
- Add E2E tests for deadline reminder flows
- Add unit tests for deadline reducer and overdue utility
- Extract shared isDeadlineOverdue utility function
- Guard app-state selectors against undefined during teardown
- Add error handler to markdown paste promise in add-task-bar
- Fix misleading "try again later" in resend cap error message
(cap is permanent, not time-based)
Prevents email bombing by capping verificationResendCount at 20 for
both magic link and passkey registration flows. Returns a safe error
message when the cap is reached.
Add email-only registration flow (no passkey required) with verification
email. For existing unverified users, email is sent before DB token
update to preserve the old verification link on failure.
- POST /api/register/magic-link endpoint with Zod validation and rate limiting
- registerWithMagicLink function in auth.ts with P2002 race condition handling
- Frontend "Register with Email" button and JS handler
- Export shared VERIFICATION_TOKEN_EXPIRY_MS constant (eliminates duplication)
- 8 unit tests covering all paths including email failure cleanup
* fix(build): fix tag resolution and add error handling in bump-android-version
The previous two-tag approach (`currentTag` → `prevTag`) would resolve the
wrong changelog range during `npm version` since the new tag doesn't exist
yet. Simplified to a single `lastTag...HEAD` range which correctly captures
all commits since the last release.
Added try-catch with fallback to last 20 commits when no tags exist, and
a fallback message for empty changelogs.
* fix(sync): differentiate auth error messages and clarify token revocation
- Rename server dashboard "Refresh Token" to "Revoke & Replace Token"
with explicit warning that ALL devices will be disconnected
- Return rejection reason in 401 responses (revoked, expired, etc.)
via discriminated union TokenVerificationResult type
- Show different client error messages for server-side token rejection
vs missing local credentials to aid user diagnosis
- Extract server error reason from JSON response body in AuthFailSPError
Closes#6597
* refactor(sync): use generic auth reason and improve test readability
- Replace 'User not found' and 'Account not verified' with generic
'Account unavailable' to avoid leaking account state in API responses
- Extract long reason strings to constants in middleware.spec.ts
* fix(sync-server): add per-route rate limits to silence CodeQL alert
Add explicit rate limits to routes that only had the global rate limit:
- GET /api/sync/status: 60/min
- DELETE /api/sync/data: 3/15min (destructive operation)
- GET /api/sync/restore-points: 30/min
- GET /api/sync/restore/:serverSeq: 10/5min (CPU-intensive)
- GET /reset-password: 20/15min
- GET /recover-passkey: 10/15min
These complement the global @fastify/rate-limit (100/15min) and silence
the CodeQL 'Missing rate limiting' alert on authenticated handlers.
* fix(build): fix tag resolution and add error handling in bump-android-version
The previous two-tag approach (`currentTag` → `prevTag`) would resolve the
wrong changelog range during `npm version` since the new tag doesn't exist
yet. Simplified to a single `lastTag...HEAD` range which correctly captures
all commits since the last release.
Added try-catch with fallback to last 20 commits when no tags exist, and
a fallback message for empty changelogs.
* fix(sync): differentiate auth error messages and clarify token revocation
- Rename server dashboard "Refresh Token" to "Revoke & Replace Token"
with explicit warning that ALL devices will be disconnected
- Return rejection reason in 401 responses (revoked, expired, etc.)
via discriminated union TokenVerificationResult type
- Show different client error messages for server-side token rejection
vs missing local credentials to aid user diagnosis
- Extract server error reason from JSON response body in AuthFailSPError
Closes#6597
* refactor(sync): use generic auth reason and improve test readability
- Replace 'User not found' and 'Account not verified' with generic
'Account unavailable' to avoid leaking account state in API responses
- Extract long reason strings to constants in middleware.spec.ts
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.
* fix(sync): pass correct opType from client to server in snapshot upload
The server was hardcoding opType as 'SYNC_IMPORT' for all snapshot
uploads, losing the distinction between SYNC_IMPORT, BACKUP_IMPORT,
and REPAIR operations. Now the client sends snapshotOpType in the
request body, and the server uses it (with fallback to 'SYNC_IMPORT'
for backward compatibility with legacy clients).
https://claude.ai/code/session_01XnqdpGbFR6VRPdCAbZ2EB9
* test(sync): fix uploadSnapshot test expectations for 9th snapshotOpType arg
The uploadSnapshot call now passes snapshotOpType as a 9th argument.
Update all 5 failing test expectations to include the new argument
(SYNC_IMPORT, BACKUP_IMPORT, or REPAIR) and fix the argument count
check from 8 to 9.
https://claude.ai/code/session_01XnqdpGbFR6VRPdCAbZ2EB9
* fix(sync): add missing snapshotOpType param to file-based sync adapter
The file-based adapter's uploadSnapshot signature was missing the 9th
snapshotOpType parameter added in f328c35, making it inconsistent with
the provider interface. Add the parameter (unused, as file-based sync
has no server-side op log).
https://claude.ai/code/session_01XnqdpGbFR6VRPdCAbZ2EB9
---------
Co-authored-by: Claude <noreply@anthropic.com>
Add tests for previously untested vector clock pruning scenarios:
- Multi-preserve-ID pruning (two low-counter IDs both preserved)
- Overlapping/duplicate preserve IDs (Set deduplication)
- Preserve IDs missing from clock (silent skip, no crash)
- Snapshot vector clock aggregation + pruning integration
- Post-snapshot comparison correctness after pruning
- Server sanitizeVectorClock counter cap at 100M
- DoS cap at 2.5x MAX (reject, not prune)
- Invalid inputs (null, arrays, empty keys, long keys, negative/float values)
https://claude.ai/code/session_01GdsbKoo8eax394j2UyWUu1
Co-authored-by: Claude <noreply@anthropic.com>
Clear _syncCycleCache in _deleteAllData() to prevent stale cached data
from being served within the 30s TTL after a data wipe. Extract
ConflictType and ConflictResult into named types in sync.types.ts to
eliminate duplicated inline unions in sync.service.ts.
- Cap sanitizeVectorClock values at 100M instead of MAX_SAFE_INTEGER (prevents adversarial clock inflation)
- Add early-exit optimization in compareVectorClocks for CONCURRENT detection
- Add test.setTimeout(180000) to token-expiry and lastseq-preservation E2E tests
- Remove dead code (unreachable guard) in compaction E2E test
- Extract shared config helpers (navigateToMiscSettings, toggleSetting, etc.) into e2e/utils/config-helpers.ts
- Fix Client B cleanup in provider-switch test (move to finally block)
- Add clarifying comments for raw SQL schema coupling, CORS header, and mergeRemoteOpClocks caller contract
- Extract enforceStorageQuota helper in sync routes to reduce duplication
- Add conflictType to conflict detection for structured error handling
- Add MAX_CLEANUP_ITERATIONS guard to freeStorageForUpload
Unlike uploadOps clean-slate (which must preserve lastSeq to prevent
sequence reuse for other clients), deleteAllUserData should reset
lastSeq to 0. Account reset intentionally wipes everything and clients
detect the wipe via latestSeq=0, triggering a full state re-upload.