fix(gitea): add full backup restore drill receipt path
This commit is contained in:
@@ -73,9 +73,11 @@ def load_latest_gitea_full_server_backup_dr_receipts(
|
||||
def build_gitea_full_server_backup_source_contract(repo_root: Path) -> dict[str, Any]:
|
||||
"""Inspect committed Gitea backup/offsite source without reading secrets."""
|
||||
backup_script = repo_root / "scripts" / "backup" / "backup-gitea.sh"
|
||||
restore_drill_script = repo_root / "scripts" / "backup" / "gitea-full-backup-restore-drill.sh"
|
||||
sync_script = repo_root / "scripts" / "backup" / "sync-offsite-backups.sh"
|
||||
verify_script = repo_root / "scripts" / "backup" / "verify-offsite-full-sync.sh"
|
||||
backup_text = _read_text(backup_script)
|
||||
restore_drill_text = _read_text(restore_drill_script)
|
||||
sync_text = _read_text(sync_script)
|
||||
verify_text = _read_text(verify_script)
|
||||
metric_writer_present = (
|
||||
@@ -83,6 +85,13 @@ def build_gitea_full_server_backup_source_contract(repo_root: Path) -> dict[str,
|
||||
and "secret_value_collected" in backup_text
|
||||
and "production_restore_performed" in backup_text
|
||||
)
|
||||
restore_drill_source_ready = (
|
||||
"awoooi_gitea_full_backup_restore_drill_performed" in restore_drill_text
|
||||
and "structural_metadata_only" in restore_drill_text
|
||||
and "never restores to production" in restore_drill_text.lower()
|
||||
and "SECRET_VALUES_COLLECTED=0" in restore_drill_text
|
||||
and "PRODUCTION_RESTORE_PERFORMED=0" in restore_drill_text
|
||||
)
|
||||
gitea_dump_present = "gitea dump" in backup_text and "/tmp/gitea-dump.zip" in backup_text
|
||||
restic_target_present = "restic -r" in backup_text and (
|
||||
"/backup" in backup_text or "BACKUP_BASE" in backup_text
|
||||
@@ -108,12 +117,16 @@ def build_gitea_full_server_backup_source_contract(repo_root: Path) -> dict[str,
|
||||
"schema_version": "gitea_full_server_backup_source_contract_v1",
|
||||
"source_refs": [
|
||||
"scripts/backup/backup-gitea.sh",
|
||||
"scripts/backup/gitea-full-backup-restore-drill.sh",
|
||||
"scripts/backup/sync-offsite-backups.sh",
|
||||
"scripts/backup/verify-offsite-full-sync.sh",
|
||||
],
|
||||
"gitea_dump_source_ready": gitea_dump_present,
|
||||
"restic_target_source_ready": restic_target_present,
|
||||
"component_metric_writer_source_ready": metric_writer_present,
|
||||
"restore_drill_source_ready": restore_drill_source_ready,
|
||||
"restore_drill_receipt_metric_writer_source_ready": restore_drill_source_ready,
|
||||
"restore_drill_scope": "structural_metadata_only",
|
||||
"google_drive_offsite_source_ready": offsite_source_ready,
|
||||
"component_rows": component_rows,
|
||||
"source_ready_component_count": sum(
|
||||
@@ -151,6 +164,7 @@ def build_gitea_full_server_backup_dr_receipts(
|
||||
"secret_values_collected_by_source_inspection": False,
|
||||
}
|
||||
receipt_metrics = _receipt_metric_rollup(metric_samples or [], host)
|
||||
restore_drill_source_ready = contract.get("restore_drill_source_ready") is True
|
||||
|
||||
component_rows = [
|
||||
{
|
||||
@@ -192,20 +206,35 @@ def build_gitea_full_server_backup_dr_receipts(
|
||||
"status": "verified_ready"
|
||||
if receipt_metrics["restore_drill_performed"]
|
||||
else "approval_required",
|
||||
"evidence_refs": ["GET /api/v1/agents/backup-dr-readiness-matrix"],
|
||||
"evidence_refs": [
|
||||
"scripts/backup/gitea-full-backup-restore-drill.sh",
|
||||
"GET /api/v1/agents/backup-dr-readiness-matrix",
|
||||
"GET /api/v1/agents/backup-restore-drill-approval-package-template",
|
||||
],
|
||||
"receipt_state": "restore_drill_receipt_present"
|
||||
if receipt_metrics["restore_drill_performed"]
|
||||
else "restore_drill_source_ready_live_receipt_pending"
|
||||
if restore_drill_source_ready
|
||||
else "restore_drill_not_executed_by_readback",
|
||||
"restore_scope": "restore requires controlled drill approval",
|
||||
"restore_scope": str(
|
||||
contract.get("restore_drill_scope") or "controlled_restore_drill"
|
||||
),
|
||||
"production_restore_performed": False,
|
||||
"metrics": {
|
||||
"gitea_restore_drill_status": str(
|
||||
gitea_readiness.get("restore_drill_status") or ""
|
||||
),
|
||||
"restore_execution_allowed": False,
|
||||
"restore_drill_source_ready": restore_drill_source_ready,
|
||||
"restore_drill_receipt_metric_writer_source_ready": contract.get(
|
||||
"restore_drill_receipt_metric_writer_source_ready"
|
||||
)
|
||||
is True,
|
||||
"restore_drill_performed_metric": receipt_metrics[
|
||||
"restore_drill_performed"
|
||||
],
|
||||
"secret_value_collection_allowed": False,
|
||||
"production_restore_allowed": False,
|
||||
},
|
||||
},
|
||||
*_component_receipt_rows(contract, receipt_metrics),
|
||||
@@ -277,14 +306,19 @@ def build_gitea_full_server_backup_dr_receipts(
|
||||
"component_receipt_count"
|
||||
],
|
||||
"gitea_full_backup_metric_error": metrics_error,
|
||||
"restore_drill_source_ready": restore_drill_source_ready,
|
||||
"restore_drill_receipt_pending": not receipt_metrics[
|
||||
"restore_drill_performed"
|
||||
],
|
||||
"production_restore_performed": False,
|
||||
},
|
||||
"source_contract": contract,
|
||||
"receipt_metric_rollup": receipt_metrics,
|
||||
"component_rows": component_rows,
|
||||
"safe_next_actions": [
|
||||
"deploy backup-gitea.sh receipt metric writer and rerun the normal Gitea backup cadence",
|
||||
"keep production restore disabled; use controlled restore-drill approval only",
|
||||
"deploy gitea-full-backup-restore-drill.sh and run it against the latest Gitea dump in an isolated non-production drill",
|
||||
"write the restore drill textfile receipt only after the structural drill passes",
|
||||
"keep production restore disabled; production restore metrics must remain zero",
|
||||
"verify Google Drive/offsite freshness with redacted non-secret receipts",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -65,7 +65,11 @@ def _payload() -> dict:
|
||||
)
|
||||
|
||||
|
||||
def _full_backup_metric_samples() -> list[dict]:
|
||||
def _full_backup_metric_samples(
|
||||
*,
|
||||
restore_drill_performed: bool = False,
|
||||
production_restore_performed: bool = False,
|
||||
) -> list[dict]:
|
||||
def sample(name: str, value: float, **labels: str) -> dict:
|
||||
return {"name": name, "labels": {"host": "110", **labels}, "value": value}
|
||||
|
||||
@@ -73,8 +77,10 @@ def _full_backup_metric_samples() -> list[dict]:
|
||||
sample("awoooi_gitea_full_backup_last_run_failed", 0, service="gitea"),
|
||||
sample(
|
||||
"awoooi_gitea_full_backup_restore_drill_performed",
|
||||
0,
|
||||
1 if restore_drill_performed else 0,
|
||||
service="gitea",
|
||||
drill_scope="structural_metadata_only",
|
||||
production_restore="false",
|
||||
),
|
||||
sample(
|
||||
"awoooi_gitea_full_backup_secret_value_collected",
|
||||
@@ -83,7 +89,7 @@ def _full_backup_metric_samples() -> list[dict]:
|
||||
),
|
||||
sample(
|
||||
"awoooi_gitea_full_backup_production_restore_performed",
|
||||
0,
|
||||
1 if production_restore_performed else 0,
|
||||
service="gitea",
|
||||
),
|
||||
]
|
||||
@@ -130,10 +136,17 @@ def test_gitea_full_server_backup_dr_receipts_separates_bundle_from_full_dr() ->
|
||||
assert by_id["gitea_server_backup_target"]["status"] == "verified_ready"
|
||||
assert by_id["google_drive_offsite_mirror"]["status"] == "verified_ready"
|
||||
assert by_id["restore_drill_approval_gate"]["status"] == "approval_required"
|
||||
assert by_id["restore_drill_approval_gate"]["receipt_state"] == (
|
||||
"restore_drill_source_ready_live_receipt_pending"
|
||||
)
|
||||
assert by_id["restore_drill_approval_gate"]["metrics"][
|
||||
"restore_drill_source_ready"
|
||||
] is True
|
||||
assert by_id["gitea_database_receipt"]["status"] == "source_ready"
|
||||
assert by_id["gitea_packages_receipt"]["status"] == "source_ready"
|
||||
assert by_id["gitea_attachments_receipt"]["production_restore_performed"] is False
|
||||
assert payload["source_contract"]["component_metric_writer_source_ready"] is True
|
||||
assert payload["source_contract"]["restore_drill_source_ready"] is True
|
||||
assert payload["source_contract"]["secret_values_collected_by_source_inspection"] is False
|
||||
|
||||
|
||||
@@ -155,6 +168,51 @@ def test_gitea_full_server_backup_dr_receipts_promotes_live_component_metrics()
|
||||
assert payload["receipt_metric_rollup"]["production_restore_performed"] is False
|
||||
|
||||
|
||||
def test_gitea_full_server_backup_dr_receipts_promotes_restore_drill_metric() -> None:
|
||||
repo_root = Path(__file__).resolve().parents[3]
|
||||
payload = build_gitea_full_server_backup_dr_receipts(
|
||||
repo_bundle_backup_readback=_repo_bundle_payload(),
|
||||
backup_dr_readiness_matrix=load_latest_backup_dr_readiness_matrix(),
|
||||
backup_dr_target_inventory=load_latest_backup_dr_target_inventory(),
|
||||
source_contract=build_gitea_full_server_backup_source_contract(repo_root),
|
||||
metric_samples=_full_backup_metric_samples(restore_drill_performed=True),
|
||||
generated_at="2026-07-10T00:03:00+08:00",
|
||||
)
|
||||
|
||||
assert payload["status"] == "gitea_full_server_backup_dr_receipts_ready"
|
||||
assert payload["ready"] is True
|
||||
assert payload["active_gap_ids"] == []
|
||||
assert payload["summary"]["verified_ready_component_count"] == 10
|
||||
assert payload["summary"]["restore_drill_source_ready"] is True
|
||||
assert payload["summary"]["restore_drill_receipt_pending"] is False
|
||||
by_id = {row["component_id"]: row for row in payload["component_rows"]}
|
||||
assert by_id["restore_drill_approval_gate"]["status"] == "verified_ready"
|
||||
assert by_id["restore_drill_approval_gate"]["metrics"][
|
||||
"restore_drill_performed_metric"
|
||||
] is True
|
||||
assert payload["receipt_metric_rollup"]["production_restore_performed"] is False
|
||||
|
||||
|
||||
def test_gitea_full_server_backup_dr_receipts_rejects_production_restore_metric() -> None:
|
||||
repo_root = Path(__file__).resolve().parents[3]
|
||||
try:
|
||||
build_gitea_full_server_backup_dr_receipts(
|
||||
repo_bundle_backup_readback=_repo_bundle_payload(),
|
||||
backup_dr_readiness_matrix=load_latest_backup_dr_readiness_matrix(),
|
||||
backup_dr_target_inventory=load_latest_backup_dr_target_inventory(),
|
||||
source_contract=build_gitea_full_server_backup_source_contract(repo_root),
|
||||
metric_samples=_full_backup_metric_samples(
|
||||
restore_drill_performed=True,
|
||||
production_restore_performed=True,
|
||||
),
|
||||
generated_at="2026-07-10T00:04:00+08:00",
|
||||
)
|
||||
except ValueError as exc:
|
||||
assert "must not restore production" in str(exc)
|
||||
else:
|
||||
raise AssertionError("expected production restore metric to fail closed")
|
||||
|
||||
|
||||
def test_gitea_full_server_backup_dr_receipts_endpoint_returns_payload(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
agents,
|
||||
|
||||
Reference in New Issue
Block a user