4.1 KiB
Sync Tenant Isolation Implementation Plan
For agentic workers: execute this plan task by task with focused tests before each implementation change.
Goal: prevent cross-user and cross-vault operation visibility while binding the persisted source device to the authenticated token.
Architecture: the server derives user, device, and vault scope from the bearer token. SQLite rows carry that scope; desktop sends the immutable vault ID only while creating a pairing. Existing unscoped devices and operations are migrated into a deterministic legacy scope.
Tech stack: Go, database/sql, SQLite, net/http, desktop Go sync client.
Task 1: Establish server behaviour tests
Files:
-
Modify:
internal/server/server_test.go -
Add helpers that create confirmed users and token-authenticated devices with a specified vault ID.
-
Add a failing test where separate users push and pull from the same vault ID; each pull must contain only its own operation and cursor.
-
Add a failing test where one user owns devices in two vaults; pulls must remain vault-local.
-
Add a failing test that sends another device's ID in
push; assert the stored and returned operation uses the authenticated device ID. -
Add a failing test for identical idempotency keys in different scopes.
-
Run:
go test ./internal/server -run 'TestSync.*Isolation|TestSyncPush'and confirm the new assertions fail for the intended missing behaviour.
Task 2: Add idempotent SQLite scope migration
Files:
-
Modify:
internal/server/schema.go -
Modify:
internal/server/server.go -
Test:
internal/server/server_test.go -
Define
vault_idon new devices anduser_id/vault_idon new operations; define scoped tombstone and idempotency primary keys. -
Add startup migration helpers that inspect columns, add compatible columns, backfill owner IDs, assign
legacy:<user_id>to old scopes, and rebuild the two tables whose primary keys change. -
Add a failing legacy-schema fixture test, then make it pass by opening the database through
NewServerand asserting its operation has the expected owner and legacy scope. -
Run:
go test ./internal/server -run 'Test.*Migration|TestSync.*'.
Task 3: Apply authenticated scope to sync handlers
Files:
-
Modify:
internal/server/middleware.go -
Modify:
internal/server/handlers_api.go -
Test:
internal/server/server_test.go -
Extend authenticated device lookup to provide the effective vault scope; missing user ownership must not authorize sync operations.
-
Require
vault_idwhen creating a new client pairing and store it with the device. -
Make push use authenticated device/user/vault values for inserts, conflicts, revisions, tombstones, and idempotency lookup/storage.
-
Make pull filter operations and its reported cursor by authenticated user/vault.
-
Run the focused tests from Task 1 until green, then
go test ./internal/server.
Task 4: Send the current vault ID while pairing
Files:
-
Modify:
../verstak-desktop/internal/core/sync/client.go -
Modify:
../verstak-desktop/internal/core/sync/client_test.go -
Modify:
../verstak-desktop/internal/api/app.go -
Modify:
../verstak-desktop/internal/api/app_test.go -
Add
vault_idto the pair request and expose it in the pairing client method without changing push/pull wire compatibility. -
Read the open vault metadata in
syncConfigure; reject configuration if the vault ID is absent. -
Add a failing client/API test that captures the pair request and asserts the persistent vault ID is sent.
-
Run:
go test ./internal/core/sync ./internal/api.
Task 5: Document, verify, and publish
Files:
-
Modify:
README.md -
Modify:
docs/superpowers/specs/2026-07-10-sync-tenant-isolation-design.md -
Document that pairing is vault-bound and that sync cursors are scoped.
-
Run
gofmton all changed Go files. -
Run
go test ./...in bothverstak-sync-serverandverstak-desktop, thengit diff --checkin both repositories. -
Commit and push the sync-server and desktop changes as coordinated security commits.