Change the Root
Move a Location to a new URI or Connection with a new version, and keep its inventory.
When recordings move to a new prefix, point the Location at it without
changing its name. Readers keep using recordings, and pinned commits keep
the old root and the old file list. The change creates a new
Location version and keeps the
inventory until the next refresh.
A revision can point a Location at a new root URI, name another Connection, or both. Every version names the Connection that reads its root.
Before you start
- The Location ID and the version ID at the branch head. Read the Resources at
the head commit and find the Resource with the kind
location. The Resource ID is the Location ID. See Select an exact state. - The branch ID and its head commit ID.
location:updateon the Location, andconnection:bindon the Connection.- The ID of the Connection whose URI contains the new root.
- A new UUIDv7 for the version ID, and another UUIDv7 for the transaction ID.
Create the new version
Name the base version, the new root and its Connection. The version ID is yours, so a retry of the same request is safe:
request = LocationsCreateVersionRequest(
base_version_id=base_version_id,
version_id=new_version_id,
change=LocationsVersionChange(
revise=LocationsLocationBacking(uri="s3://example-robotics/recordings-2026/", connection_id=connection_id)
),
)
created = client.locations.create_version(location_id, request)
print(f"created version {created.version.version_id}")The root must lie inside the URI of the Connection that change.revise names.
The new version is not visible until a commit selects it.
Publish the new version
A commit makes the new version current. The publication names the version that you started from and the new one, and Spiral swaps them only if the Location still has the base version:
request = CatalogPublishRequest(
branch_id=branch_id,
base_commit_id=head_commit_id,
transaction_id=transaction_id,
idempotency_key="revise-recordings-2026",
message="Move recordings to recordings-2026",
resources=[
CatalogResourceChange(
advance=CatalogAdvanceResourceChange(
resource_id=location_id, expected=base_version_id, replacement=new_version_id
)
)
],
namespace=[],
)
outcome = client.catalog.publish("demo", request)
if outcome.published is not None:
print(f"published commit {outcome.published.commit_id}")
elif outcome.conflict is not None:
print(f"conflict: the head is now {outcome.conflict.current_commit_id}")The response is {"type": "published", ...} with the new commit, or
{"type": "conflict", ...}. A conflict means that another change, such as a
refresh, advanced the Location first. Nothing is published. Read the new head
and create the version again from the new base.
The Rust SDK publishes Table changes only. Publication of other Resource kinds from Rust is planned.
The inventory
The revision keeps the previous inventory until a refresh reconciles it with the new root. A refresh updates the URI of a path that both roots contain, even when its size and ETag match, because the same ETag under a different root can be a different object.
Verify the result
Read the new version:
curl "https://api.spiraldb.dev/api/locations/$LOCATION_ID/versions/$NEW_VERSION_ID" \
-H "Authorization: Bearer $SPIRAL_TOKEN"version.definition.backing shows the new root, and version.parentVersionId
is the base version.