Skip to content

feat: abandon a staging that cannot finish - #85

Merged
krzysztof-smartdataengines merged 3 commits into
mainfrom
feat/staging-abandon
Sep 24, 2026
Merged

krzysztof-smartdataengines merged 3 commits into
mainfrom
feat/staging-abandon

Conversation

@krzysztof-smartdataengines

Copy link
Copy Markdown
Contributor

What

A staging that cannot finish can now be abandoned before its decision: LocalCutover.abandon() / sde-operator abandon removes only that staging's own copy and keeps the map in force.

Why

A staging can reach a state no resume repairs - another operation's barrier on a source table, a changed runtime login, a target that refuses the table, somebody else's object under a copy's name. Until now such a staging blocked the customer's operator (and, through the pending reservation, every issuance in the control plane) until its state was repaired by hand. The in-place index build (#83) shipped with abandonment from the start; this gives staging the same shape.

How

  • The decision abandoned is recorded first. Then only this staging's own tables are dropped: a table under a copy's name that carries its exact creation marker and, once recorded, its native identity - including a table created just before a crash whose identity was never recorded (found by the marker). Anything else under the name is left alone (a foreign table; a recreated object carrying the marker). Indexes and generation constraints go with the table.
  • On ClickHouse the runtime logins' grants on the copy are revoked and read back: measured on 24.8.14.39, table grants outlive DROP TABLE and REVOKE on the dropped table is accepted (docs/qualification/staging-abandon/). PostgreSQL drops a table's privileges with it.
  • Abandonment needs only the same native database - not the runtime logins, not a barrier-free source, whose absence is why a staging cannot finish. The map in force, the watermarks and every process stay as they were.
  • The receipt (protocol 1) gains the outcome abandoned: the map in force, and per entity the identity of the dropped table or null. The authorization is spent: stage with the same packet returns the abandonment.
  • A state holding an abandoned staging is storage contract 4, which operators knowing contracts 1-3 refuse (checked with the real previous operator at 42091bf: exit 2, local cutover state is missing or corrupt). The name-reuse check reads names from the stored authorization, because an abandoned receipt may name no identity.
  • After the decision prepared, abandon is refused and resume publishes; the copy then leaves through its cutover's abort.

Also: the in-place index budget test gets a 6 s signed budget - its resume must fit a DROP and a CREATE INDEX CONCURRENTLY into the same budget, which 2.5 s did not always leave on a loaded two-core machine (observed once in a combined run; passes alone).

Evidence

  • make check with both live engines: Python 1956 passed, 10 skipped (the orderbook slice); TypeScript 936 passed.
  • test_staging_abandon_live.py (both engines): abandonment after every checkpoint before the decision, refusal after it, an interrupted abandonment finished by resume or a second abandon, a staging blocked by another barrier, a foreign table and a recreated object under the copy's name, a SIGKILL right after CREATE, the next staging after an abandonment, the CLI.
  • Mutations: 13 in the first run, 12 as expected, both controls surviving. The survivor - a name-reuse check reading the receipt - exposed a test that abandoned only stagings whose table existed; with an abandonment before any table existed, every later staging would have failed with a TypeError. Test fixed; rerun: killed.

The control-plane side (receipt acceptance, envelope 4, guard, Weather driver) is in the private repository.

🤖 Generated with Claude Code

A staging could reach a state no resume repairs - another operation's
barrier on a source table, a changed runtime login, a target that refuses
the table, somebody else's object under a copy's name - and then blocked
the customer's operator until its state was repaired by hand.

Before its decision, LocalCutover.abandon / sde-operator abandon now ends
it: the decision abandoned is recorded first, then only this staging's own
tables are dropped - the ones under a copy's name that carry its exact
creation marker and, once recorded, its native identity, including a table
created just before a crash whose identity was never recorded. Anything
else under the name is left alone. On ClickHouse the runtime logins'
grants are revoked too: measured on 24.8.14.39, they outlive DROP TABLE.
The map in force, the watermarks and every process stay as they were, and
abandonment needs only the same native database - not the logins or a
barrier-free source whose absence is why the staging cannot finish.

The receipt gains the outcome abandoned (the map in force, per entity the
identity of the dropped table or null). A state holding an abandoned
staging is storage contract 4, which operators knowing contracts 1 to 3
refuse; the name-reuse check reads names from the stored authorization.
After the decision prepared, abandonment is refused and resume publishes.

The in-place index budget test gets a longer signed budget: its resume
must fit a DROP and a CREATE INDEX CONCURRENTLY into it, which 2.5 s did
not always leave on a loaded two-core machine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…repares

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A mutation that read reused names from the receipt survived: the test
abandoned only a staging whose table had been created, whose receipt
names an identity.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@krzysztof-smartdataengines
krzysztof-smartdataengines merged commit 4220b60 into main Sep 24, 2026
11 checks passed
@krzysztof-smartdataengines
krzysztof-smartdataengines deleted the feat/staging-abandon branch September 24, 2026 17:40
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.

1 participant