feat(6845): every instance has an id lineage, and every full backup carries it
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 1m37s
CI & Build / integration (push) Successful in 2m7s
CI & Build / Build & push image (push) Successful in 22s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 1m37s
CI & Build / integration (push) Successful in 2m7s
CI & Build / Build & push image (push) Successful in 22s
instance_identity is one row made by migration 0127 with a random id, so an instance has it before its first account. A full backup carries it in its header as instance_id (the tar manifest header and the JSON top level); BACKUP_VERSION is 28. The table rides in _NOT_INCLUDED: a restore decides from the header value rather than copying a row. Two stores with the same id number their rows the same way. That is what lets a later step roll a live instance back by original id, refuse a backup whose ids would land on unrelated rows, and have a fresh-instance restore adopt the backup's id (adopt_instance_id). Milestone 582, step 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
"""Real-Postgres proof that every instance has one id lineage, that a full
|
||||
backup carries it, and that adopting a backup's id replaces this one's."""
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
||||
from grimoire.models import async_session
|
||||
from grimoire.services import backup
|
||||
from grimoire.services.backup_file import read_archive, write_archive_to
|
||||
from grimoire.services.instance import adopt_instance_id, get_instance_id
|
||||
|
||||
pytestmark = [pytest.mark.integration, pytest.mark.usefixtures("_dispose_engine")]
|
||||
|
||||
|
||||
async def test_the_migration_gave_this_instance_an_id_and_it_stays_put():
|
||||
first = await get_instance_id()
|
||||
assert first and len(first) >= 32
|
||||
assert await get_instance_id() == first
|
||||
|
||||
|
||||
async def test_a_full_backup_carries_the_instance_id_through_the_tar():
|
||||
payload = await backup.export_full_backup()
|
||||
assert payload["instance_id"] == await get_instance_id()
|
||||
|
||||
out = io.BytesIO()
|
||||
write_archive_to(payload, out, app_version="test")
|
||||
out.seek(0)
|
||||
assert read_archive(out)["instance_id"] == payload["instance_id"]
|
||||
|
||||
|
||||
async def test_adopting_a_backups_id_replaces_this_instances():
|
||||
own = await get_instance_id()
|
||||
try:
|
||||
async with async_session() as s:
|
||||
await adopt_instance_id(s, "adopted-lineage")
|
||||
await s.commit()
|
||||
assert await get_instance_id() == "adopted-lineage"
|
||||
finally:
|
||||
async with async_session() as s:
|
||||
await adopt_instance_id(s, own)
|
||||
await s.commit()
|
||||
assert await get_instance_id() == own
|
||||
@@ -11,7 +11,7 @@ The full FK-remapping round-trip needs real Postgres and lives in the
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -22,7 +22,7 @@ def test_backup_version_is_current():
|
||||
"""The bump is the point of the test — a payload section added without
|
||||
moving the version produces backups that are structurally different and
|
||||
indistinguishable by inspection."""
|
||||
assert backup.BACKUP_VERSION == 27
|
||||
assert backup.BACKUP_VERSION == 28
|
||||
|
||||
|
||||
def _exportable_note(**over):
|
||||
@@ -561,11 +561,15 @@ class _CM:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_export_full_backup_contains_every_declared_section():
|
||||
with patch("grimoire.services.backup.async_session", lambda: _CM()):
|
||||
with patch("grimoire.services.backup.async_session", lambda: _CM()), patch(
|
||||
"grimoire.services.instance.get_instance_id", AsyncMock(return_value="test-lineage"),
|
||||
):
|
||||
out = await backup.export_full_backup()
|
||||
|
||||
assert out["version"] == backup.BACKUP_VERSION
|
||||
assert out["scope"] == "full"
|
||||
# v28: the id lineage a live rollback is checked against.
|
||||
assert out["instance_id"] == "test-lineage"
|
||||
assert "api_keys" in out["_not_included"]
|
||||
# The sections v2 silently dropped, the six v5 added, v6's
|
||||
# note_supersessions, and v7's code_shapes (all empty here).
|
||||
|
||||
Reference in New Issue
Block a user