Skip to content

worker: free plan syncs one device after a free first week (off by default) - #76

Merged
jordan-gibbs merged 1 commit into
mainfrom
feat/free-sync-week
Oct 26, 2026
Merged

jordan-gibbs merged 1 commit into
mainfrom
feat/free-sync-week

Conversation

@jordan-gibbs

Copy link
Copy Markdown
Contributor

A free account syncs on every device for its first week. After the week, one device keeps syncing and the others are paused. Paid plans are never paused. The rule ships off (SYNC_PLAN_GATE unset), so merging and deploying this changes nothing until the apps that explain the pause are out.

What it does

  • Once the rule applies, every device except the kept one gets 403 sync_paused on the data plane (/relics, /relic/*, /tombstones, /waiting, /keyparams, /ai*, /blob*) and on /sync/socket. Reads are blocked too, because a second device that can still read gets half of sync. Account, device, billing, share and pairing routes stay open.
  • The kept device is the first one to sync after the week, which is usually the computer. A request with no device id falls back to the most recently seen device. An account with only one device is never paused.
  • POST /account/sync-device picks the kept device. Free accounts can switch once a week, and the first switch after the automatic pick is always allowed. It works with the rule off, too.
  • GET /account adds sync_until, sync_device and sync_paused.
  • accounts.sync_until overrides the sign-up week. Launch will use it to give existing two-device users extra time.
  • The plan is cached per isolate for 60 seconds, so the check doesn't add a D1 read to every sync request.

Schema

Migration 0014_sync_plan.sql adds three nullable columns to accounts. It's metadata-only, with no backfill. Self-host gets the same columns through schema-upgrade.ts.

Docs

OpenAPI 1.4.0 and docs/api.md (error model, GET /account, the new route, the tier table).

Tests

  • Worker: 305 passing, 15 of them new in test/syncplan.test.ts.
  • Typecheck clean.
  • Self-host smoke test passes.

🤖 Generated with Claude Code

…fault)

A free account syncs every device for its first week. After that one device
keeps syncing and the others get 403 sync_paused on the data plane and the
doorbell, reads included. Paid tiers are never paused. Off unless
SYNC_PLAN_GATE=on, so this can deploy before the apps that explain the 403.

- accounts.sync_until / sync_device / sync_device_at (migration 0014, the
  self-host column upgrades, schema.sql)
- the kept device is the first to sync after the week; a request with no
  device id falls back to the most recently seen one
- POST /account/sync-device picks it; free accounts switch once a week
- GET /account adds sync_until, sync_device, sync_paused
- the plan is cached per isolate for a minute to keep D1 off the hot path
- OpenAPI 1.4.0 and docs/api.md describe all of it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jordan-gibbs
jordan-gibbs merged commit 9fe5fdd into main Oct 26, 2026
13 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