9013ea5e33
The one-time data move decentralization needs (issue 0003c): copy the entire control-plane state from the local SQLite database into the replicated JetStream KV buckets, with a backup taken first. pkg/membership: - Snapshot / SealedKeyRecord: a backend-agnostic dump of the whole control plane (rooms with their real epoch, members, every sealed-key row across epochs, users with status). - (*sqliteStore).ExportSnapshot and (*jetstreamStore).ExportSnapshot read a full Snapshot from each backend; (*jetstreamStore).importSnapshot writes one with raw Puts (preserving epoch/status, not resetting to defaults) so the migration is faithful and idempotent (every write is an overwrite, so re-running converges). - MigrateSQLiteToKV orchestrates export -> import; BackupSQLite makes a consistent copy via SQLite's VACUUM INTO before any migration. cmd/membershipd: - `membershipd migrate-to-kv --db <path> --nats-url <url> [--replicas N] [--ca <cert>] [--no-backup]` backs up the SQLite file, connects to the cluster's NATS, and migrates. Dispatched on the host like `user`. Tests (DoD: golden + edge + parity): - TestMigrateSQLiteToKVParity: seed a representative SQLite (two rooms, one rekeyed to epoch 2, members, a revoked user); after migration the KV ExportSnapshot equals the SQLite ExportSnapshot. - TestMigrateSQLiteToKVIdempotent: running the migration twice yields the same KV state. - TestBackupSQLiteCreatesConsistentCopy: the backup reopens with identical data. Plus a binary smoke (seed user -> run server -> migrate-to-kv -> re-run): backup written, 1 user migrated, second run identical.