feat(restore): Restore command group and Phase 4 apply support - #570
Open
guptadev21 wants to merge 10 commits into
Open
feat(restore): Restore command group and Phase 4 apply support#570guptadev21 wants to merge 10 commits into
guptadev21 wants to merge 10 commits into
Conversation
A restore loads logical databases into a live, running database from a backup, or from an old on-device data directory — the latter being how a major-version migration is done, since the transfer is a logical dump and load rather than a physical copy. It never creates a database, and never touches logical databases outside the ones it is given. Restore is a sub-resource of Database, so every command takes the target with --database rather than guessing it: the route itself is nested. `rio apply` gains the Restore kind. Its dependencies are the target database and, for a backup source, the source backup — so a manifest directory applies in the right order. Update raises NotImplementedError: a restore is one-shot and its spec is immutable, so re-applying an existing one must not re-run it. --if-exists without --clean warns rather than silently doing nothing; pg_restore rejects that combination, so the device drops the flag.
Resolves rapyuta-io-sdk-v2 to feat/managed-database-phase-4 (3e29debe), which carries the Restore model and client methods the restore command group needs.
A backup restore now names the uploaded archive directly, by file-upload GUID or filename — the two columns `rio device uploads list` prints. --backup stays as provenance and no longer resolves anything, so it is optional.
The apiserver removed the restore delete route: a restore runs to a terminal phase and stays as an audit record, and deleting the target database is what stops one still in flight. `rio restore delete` therefore called a route that answers 404. delete_object now raises rather than silently 404-ing, so an apply teardown that reaches a Restore fails loudly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jJvnkwSKGtksZ5gq3PLmq
The lock pinned an SDK rev from before RestoreStatus.step and the Stopped phase existed, so reading a stopped restore would have raised on parse. The bump also brings BackupStatus.fileUploads, which is where the file-upload GUID a restore is addressed by is published. The dependency string still named the Phase 3 branch while [tool.uv.sources] named Phase 4; they now agree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jJvnkwSKGtksZ5gq3PLmq
A backup or restore holds one phase for minutes, so the phase alone cannot tell progress from a stall — that is what the step column is for. The archive count tells the operator whether a restore has anything to read, and `rio backup inspect` now carries the file-upload GUIDs that `rio restore create --file-upload` takes. Adds the restore manifest `rio explain` was missing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jJvnkwSKGtksZ5gq3PLmq
guptadev21
requested review from
ankitrgadiya and
smrutisenapati
and removed request for
a team
August 21, 2026 07:35
|
🤖 Pull Request Artifacts (#32949953673) 🎉 |
A restore is not a first-class resource: it belongs to exactly one database,
the v2 API nests it as /v2/databases/{name}/restores/, and every command
already required --database to name the target. Registering it at the top
level put the command tree at a different altitude from the resource tree.
The package moves to riocli/database/restore/ to match how a nested group is
laid out elsewhere in the repo (rio device tools).
Wiring and help text only. The apply kind registry is keyed by manifest kind,
not command path, so `rio apply` and `rio explain restore` are unchanged, as
are every command's options.
`rio backup` deliberately stays top-level: a backup really is a top-level API
resource, where `database` is only a list filter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019jJvnkwSKGtksZ5gq3PLmq
Model.delete() catches only HttpNotFoundError, so delete_object raising NotImplementedError aborted `rio delete -f` for every other resource in the same bundle. A restore is an audit record with no delete route, so skipping it is the correct outcome. update_object keeps raising -- that path is handled and correctly reports the restore as existing rather than re-running it. Drops the Archives column from `rio backup list` and the manifest's reference to status.fileUploads: the field is gone from the v2 API, so the column would always read 0. Also corrects the manifest's claim that backupName and backupRunID are provenance only -- backupName selects the restore image's major version, both are derived from the upload's metadata, and targetTime is now honoured when the device still holds the backup locally.
Lists a database's uploaded backup archives. There was no way to see them before: `rio device uploads list` needs a device name and has no filters, so archives were unreachable once the uploading device or the Backup record was gone. The Upload ID it prints is what `rio database restore create --file-upload` takes, which is the whole point -- finding an archive to restore was previously guesswork. Picks up list_database_uploads from the pinned SDK branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jJvnkwSKGtksZ5gq3PLmq
Archives now outlive their backup, so this is the only thing that removes one. Takes the database and the upload GUID that `rio database upload list` prints. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jJvnkwSKGtksZ5gq3PLmq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the client half of Managed Database Phase 4 — restore into a live
database, and the major-version migration expressed as a restore from an old
data directory.
Stacked on #569 (Phase 3, standby topology). Depends on
rapyuta-io-sdk-v2#66, which this branch's lock pins.
What lands
rio database restore create|list|inspect, plusRestorein the applykind registry so a restore can run from a manifest. A restore belongs to
exactly one database — the API nests it as
/v2/databases/{name}/restores/— so the commands are nested too, and each takes
--database.rio backupdeliberately stays top-level: a backup really is a top-levelAPI resource, where
databaseis only a list filter.--source dataDirectorywith--source-versionis the major-versionmigration path; it is not a separate command, because the transfer is a
logical dump-and-load rather than a physical copy.
--file-upload, by GUID orfilename. A backup produces many archives over its life, and only the caller
knows which one they mean.
restore.yamlsample manifest, sorio explain restoreworks.Corrections to the earlier Phase 4 commits
These were cut on 2026-08-19 and the apiserver moved after them.
terminal phase and stays as an audit record, and deleting the target
database is what stops one still in flight.
rio restore deletewas callinga route that answers 404.
delete_objectnow raises rather than 404-ingsilently, so an apply teardown that reaches a Restore fails loudly.
RestoreStatus.stepand theStoppedphase existed, so reading a stopped restore would have raised onparse. The dependency string also still named the Phase 3 branch while
[tool.uv.sources]named Phase 4; they now agree.minutes, so the phase alone cannot tell progress from a stall — that is what
stepis for. The archive count says whether a restore has anything toread, and
rio backup inspectnow carries the file-upload GUIDs that--file-uploadtakes. Without the SDK'sBackupStatus.fileUploadsthoseGUIDs were unreachable except through
rio device uploads list, which brokethe documented flow end to end.
Verification
uv run ruff check .clean,uv run pytest tests/unit301 passed. New unitcoverage for both list columns.
Not covered by unit tests, which are all httpx-mocked and so cannot catch a
route that no longer exists — needs an ephemeral env on the Phase 4 apiserver:
rio backup inspectprintsstatus.fileUploads.--file-upload, and confirm theother is untouched.
rio database deleteunder a running restore leaves itStoppedand that the list renders it.
--source dataDirectory --source-version 17.🤖 Generated with Claude Code
https://claude.ai/code/session_019jJvnkwSKGtksZ5gq3PLmq