Skip to content

Add spacetime lock/unlock to prevent accidental database deletion#4502

Merged
clockwork-labs-bot merged 7 commits into
masterfrom
bot/database-lock
Apr 20, 2026
Merged

Add spacetime lock/unlock to prevent accidental database deletion#4502
clockwork-labs-bot merged 7 commits into
masterfrom
bot/database-lock

Conversation

@clockwork-labs-bot

Copy link
Copy Markdown
Contributor

Motivation

Feature request: "Is there any way we can lock a module to prevent it from being deleted? A bit concerned about some fat finger risk of accidentally deleting prod."

Solution

Adds a database lock mechanism. A locked database cannot be deleted until explicitly unlocked.

New CLI Commands

# Lock a database to prevent deletion
spacetime lock my-database

# Attempt to delete a locked database (fails with 403)
spacetime delete my-database
# Error: Database is locked and cannot be deleted. Run \`spacetime unlock\` first.

# Unlock when you actually need to delete
spacetime unlock my-database
spacetime delete my-database

Both commands support --server and --no-config flags, and resolve the database from spacetime.json when no argument is given (same as spacetime delete).

New HTTP API

  • POST /v1/database/:name_or_identity/lock -- Lock a database
  • POST /v1/database/:name_or_identity/unlock -- Unlock a database

Both require the same authorization as DELETE (owner only).

Implementation

  • Lock state stored in a separate database_locks sled tree in the standalone control DB (avoids changing the Database struct and needing a data migration)
  • ControlStateReadAccess::is_database_locked() and ControlStateWriteAccess::set_database_lock() added to the trait
  • delete_database route checks lock state before proceeding; returns 403 Forbidden with a descriptive message if locked
  • Locking is idempotent (locking an already-locked database is a no-op, same for unlock)
  • Lock only prevents deletion, not publishing updates

What is NOT locked

  • spacetime publish (updating module code) still works on locked databases
  • Only spacetime delete is blocked

This matches the intent: protect prod from accidental destruction while allowing normal deployments.

Adds a database lock mechanism to protect production databases from
accidental deletion via `spacetime delete`.

## Changes

### Server (client-api + standalone)
- Add `is_database_locked` to `ControlStateReadAccess` trait
- Add `set_database_lock` to `ControlStateWriteAccess` trait
- Implement both in standalone control DB using a `database_locks`
  sled tree (separate from the Database struct to avoid migration)
- Add `POST /v1/database/:name_or_identity/lock` route
- Add `POST /v1/database/:name_or_identity/unlock` route
- Check lock state in `delete_database` route handler; return 403
  with descriptive error if locked

### CLI
- Add `spacetime lock [database]` subcommand
- Add `spacetime unlock [database]` subcommand
- Both support `--server` and `--no-config` flags
- Both resolve database from spacetime.json when no argument given

## Usage

```bash
# Lock a database
spacetime lock my-database

# Attempt to delete (fails with 403)
spacetime delete my-database
# Error: Database is locked and cannot be deleted.
#        Run \`spacetime unlock\` first.

# Unlock when you really want to delete
spacetime unlock my-database
spacetime delete my-database
```
Lock now also prevents the reset route (which publish --delete-data
delegates to). Returns 403 with a message telling the user to unlock.
Seven tests covering the lock/unlock feature:
- Locked database cannot be deleted
- Locked database cannot be reset (--delete-data)
- Unlock allows subsequent delete
- Lock is idempotent (double-lock OK)
- Unlock is idempotent (unlock without lock OK)
- Non-owner cannot lock or unlock
- Locked database still allows publish (without --delete-data)

@cloutiertyler cloutiertyler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally LGTM

@clockwork-labs-bot clockwork-labs-bot added this pull request to the merge queue Apr 20, 2026
Merged via the queue into master with commit 81c9eab Apr 20, 2026
51 of 56 checks passed
@bfops bfops mentioned this pull request Apr 23, 2026
bfops added a commit that referenced this pull request Apr 23, 2026
# Description of Changes

Revert the following PRs that have caused some breakage:
```
a32cffa Finish refactoring out replay (#4850)
d639be0 Replay: some code motion & reuse `ReplayCommittedState` (#4849)
78d6b6f Update NativeAOT-LLVM infrastructure to current ABI (#4515)
d5c1738 Better module backtraces for panics and whatnot (#577)
6f23b19 Wait for database update to become durable (#4846)
81c9eab Add `spacetime lock/unlock` to prevent accidental database deletion (#4502)
809aebd Move field `replay_table_updated` to `ReplayCommittedState` (#4807)
21b58ef Update axum (#2713)
b5cadff Extract replay stuff out of `CommittedState`, part 1 (#4804)
```

I also updated the Python smoketests for breakage introduced in
#4502. Reverting that
PR caused conflicts, so this fix is more straightforward.

# API and ABI breaking changes

Maybe kind of, but we haven't released any of these.

# Expected complexity level and risk

1

# Testing

Ask @bfops about testing

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
pull Bot pushed a commit to Abaso007/SpacetimeDB that referenced this pull request Jun 17, 2026
…on (clockworklabs#4888)

Re-lands clockworklabs#4502 on current `master` after the revert in clockworklabs#4881.

## Summary
- restore `spacetime lock` / `spacetime unlock`
- block deleting locked databases
- restore the database-lock smoketests

## Validation
- `cargo fmt --all --check`
- `cargo check -p spacetimedb-cli -p spacetimedb-client-api -p
spacetimedb-standalone`

---------

Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: Tyler Cloutier <cloutiertyler@aol.com>
Co-authored-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com>
Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants