feat(2): bluesky-pds P4 data-integrity overlay — deterministic atproto account marker (recipe-aware; catches running-app-holds-sqlite restore gap) via _p4.py + ops/test_upgrade/backup/restore
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
75
tests/bluesky-pds/_p4.py
Normal file
75
tests/bluesky-pds/_p4.py
Normal file
@ -0,0 +1,75 @@
|
||||
"""Shared bluesky-pds P4 data-integrity helpers.
|
||||
|
||||
The marker is a DETERMINISTIC atproto account (recipe-aware data living in the PDS's sqlite store
|
||||
under /pds), NOT a loose file — so the restore assertion genuinely catches a restore that fails to
|
||||
bring the account back (e.g. if the running PDS holds its sqlite open and never reloads the restored
|
||||
files, the same data-loss class cc-ci caught in immich/mattermost). The handle is derived from the
|
||||
per-run domain so ops.py (seed/wipe) and the test_<op>.py overlays agree without passing state.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
||||
from harness import http as harness_http, lifecycle # noqa: E402
|
||||
|
||||
PDS_HOST_LOCAL = "http://localhost:3000"
|
||||
_PW = "ccci-P4-marker-pw-2026"
|
||||
|
||||
|
||||
def _handle(domain: str) -> str:
|
||||
return f"ccci-p4.{domain}"
|
||||
|
||||
|
||||
def _email(domain: str) -> str:
|
||||
return f"ccci-p4@{domain}"
|
||||
|
||||
|
||||
def _goat_admin(domain: str, args: str) -> str:
|
||||
"""`goat pds admin <args>` inside the PDS `app` container (admin bypasses invite requirement)."""
|
||||
cmd = (
|
||||
f"goat pds admin {args} "
|
||||
f'--admin-password "$(cat /run/secrets/pds_admin_password)" '
|
||||
f"--pds-host {PDS_HOST_LOCAL} 2>&1"
|
||||
)
|
||||
return lifecycle.exec_in_app(domain, ["sh", "-c", cmd], timeout=120)
|
||||
|
||||
|
||||
def account_did(domain: str) -> str | None:
|
||||
"""The marker account's DID if it exists (public XRPC describeRepo by handle), else None."""
|
||||
st, body = harness_http.http_get(
|
||||
f"https://{domain}/xrpc/com.atproto.repo.describeRepo?repo={_handle(domain)}"
|
||||
)
|
||||
if st == 200 and isinstance(body, dict):
|
||||
return body.get("did")
|
||||
return None
|
||||
|
||||
|
||||
def account_exists(domain: str) -> bool:
|
||||
return account_did(domain) is not None
|
||||
|
||||
|
||||
def create_account(domain: str) -> str:
|
||||
"""Idempotently create the deterministic marker account; return its DID."""
|
||||
did = account_did(domain)
|
||||
if did:
|
||||
return did
|
||||
out = _goat_admin(
|
||||
domain,
|
||||
f"account create --handle {shlex.quote(_handle(domain))} "
|
||||
f"--email {shlex.quote(_email(domain))} --password {shlex.quote(_PW)}",
|
||||
)
|
||||
m = re.search(r"did:plc:[a-z0-9]+", out)
|
||||
assert m, f"goat account create produced no DID. Output:\n{out[:400]!r}"
|
||||
return m.group(0)
|
||||
|
||||
|
||||
def delete_account(domain: str) -> None:
|
||||
"""Delete the marker account (so a successful restore is observable). No-op if already gone."""
|
||||
did = account_did(domain)
|
||||
if did:
|
||||
_goat_admin(domain, f"account delete {did}")
|
||||
22
tests/bluesky-pds/ops.py
Normal file
22
tests/bluesky-pds/ops.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""bluesky-pds — pre-op seed hooks (Phase 1e HC3). The P4 marker is a deterministic atproto account
|
||||
(recipe-aware data in the PDS sqlite under /pds, the backed-up volume) — see _p4.py. pre_restore
|
||||
deletes the account so a successful restore is observable (non-vacuous)."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _p4 # noqa: E402
|
||||
|
||||
|
||||
def pre_upgrade(domain, meta):
|
||||
_p4.create_account(domain)
|
||||
|
||||
|
||||
def pre_backup(domain, meta):
|
||||
_p4.create_account(domain)
|
||||
|
||||
|
||||
def pre_restore(domain, meta):
|
||||
_p4.delete_account(domain)
|
||||
assert not _p4.account_exists(domain), "marker account delete did not take (pre_restore)"
|
||||
13
tests/bluesky-pds/test_backup.py
Normal file
13
tests/bluesky-pds/test_backup.py
Normal file
@ -0,0 +1,13 @@
|
||||
"""bluesky-pds — BACKUP overlay (Phase 1e HC3): assertion-only + additive.
|
||||
ops.pre_backup created the deterministic marker account before the backup op; this overlay asserts it
|
||||
is present at backup time (so the backup archive captures it)."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _p4 # noqa: E402
|
||||
|
||||
|
||||
def test_backup_captures_state(live_app):
|
||||
assert _p4.account_exists(live_app), "seeded marker account not present at backup time"
|
||||
16
tests/bluesky-pds/test_restore.py
Normal file
16
tests/bluesky-pds/test_restore.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""bluesky-pds — RESTORE overlay (Phase 1e HC3): data-integrity, assertion-only + additive.
|
||||
ops.pre_restore deleted the marker account (diverge); the orchestrator restored once. This overlay
|
||||
asserts the account is back — i.e. the PDS data volume (atproto sqlite) genuinely round-tripped
|
||||
through backup→restore (not just service-up)."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _p4 # noqa: E402
|
||||
|
||||
|
||||
def test_restore_returns_state(live_app):
|
||||
assert _p4.account_exists(live_app), (
|
||||
"restore did not bring back the seeded marker account (PDS data did not survive restore)"
|
||||
)
|
||||
13
tests/bluesky-pds/test_upgrade.py
Normal file
13
tests/bluesky-pds/test_upgrade.py
Normal file
@ -0,0 +1,13 @@
|
||||
"""bluesky-pds — UPGRADE overlay (Phase 1e HC3): data-continuity, assertion-only + additive.
|
||||
ops.pre_upgrade created the deterministic marker account before the upgrade; this overlay asserts it
|
||||
survived the prev→PR-head chaos crossover."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _p4 # noqa: E402
|
||||
|
||||
|
||||
def test_upgrade_preserves_data(live_app):
|
||||
assert _p4.account_exists(live_app), "marker account did not survive the upgrade"
|
||||
Reference in New Issue
Block a user