1- name : Nightly Backup
1+ name : Backup/Restore Regression Test
22
3+ # This does not back up a deployed environment. It builds synthetic DuckDB
4+ # fixtures on an ephemeral runner, backs *those* up, verifies the manifest,
5+ # and restores into a scratch directory — a regression test for
6+ # scripts/backup.py, scripts/verify_backup.py and scripts/restore.py, not a
7+ # disaster-recovery control (audit P1-2). See docs/disaster-recovery.md for
8+ # what backup coverage actually exists today.
39on :
410 schedule :
511 - cron : " 0 2 * * *"
@@ -22,11 +28,12 @@ jobs:
2228 - name : Prepare backup fixture
2329 run : |
2430 python - <<'PY'
25- import re
2631 from pathlib import Path
2732
2833 import duckdb
2934
35+ from src.serving.backends.duckdb_backend import DuckDBBackend
36+
3037 pipeline_db = Path("agentflow_demo.duckdb")
3138 usage_db = Path("agentflow_api.duckdb")
3239 for path in (pipeline_db, usage_db):
@@ -36,36 +43,28 @@ jobs:
3643 if wal_path.exists():
3744 wal_path.unlink()
3845
39- tenants_content = Path("config/tenants.yaml").read_text(encoding="utf-8")
40- tenant_schemas = re.findall(
41- r'^\s*duckdb_schema:\s*["\']?([^"\']+)["\']?\s*$',
42- tenants_content,
43- flags=re.MULTILINE,
44- )
45-
46- conn = duckdb.connect(str(pipeline_db))
46+ # The product's own DDL, not a copy of it. This step used to hand-write
47+ # a `CREATE SCHEMA` per tenant, parsed out of the `duckdb_schema` field
48+ # of config/tenants.yaml -- the schema-per-tenant model that ADR-004
49+ # retired. Nothing in src/ ever created those schemas, so the fixture
50+ # was fabricating the very thing the restore smoke test then went
51+ # looking for. Two tenants now share one table, keyed by `tenant_id`,
52+ # which is what a restored store actually has to bring back.
53+ backend = DuckDBBackend(db_path=str(pipeline_db))
54+ backend.ensure_schema()
55+ conn = backend.connection
4756 try:
48- for schema in tenant_schemas:
49- conn.execute(f'CREATE SCHEMA IF NOT EXISTS "{schema}"')
50- conn.execute(
51- f'''
52- CREATE TABLE IF NOT EXISTS "{schema}".orders_v2 (
53- order_id VARCHAR PRIMARY KEY,
54- user_id VARCHAR,
55- status VARCHAR,
56- total_amount DECIMAL(10,2),
57- currency VARCHAR,
58- created_at TIMESTAMP
59- )
60- '''
61- )
62- conn.execute(
63- f'''
64- INSERT INTO "{schema}".orders_v2 VALUES
65- (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
66- ''',
67- [f"ORD-{schema.upper()}-001", f"USR-{schema.upper()}", "confirmed", 42.0, "RUB"],
68- )
57+ conn.execute(
58+ """
59+ INSERT OR REPLACE INTO orders_v2
60+ (tenant_id, order_id, user_id, status, total_amount, currency, created_at)
61+ VALUES
62+ ('acme-corp', 'ORD-ACME-001', 'USR-ACME', 'confirmed', 42.00, 'RUB',
63+ CURRENT_TIMESTAMP),
64+ ('demo', 'ORD-DEMO-001', 'USR-DEMO', 'confirmed', 17.00, 'RUB',
65+ CURRENT_TIMESTAMP)
66+ """
67+ )
6968 finally:
7069 conn.close()
7170
9089 finally:
9190 conn.close()
9291 PY
93- - name : Create backup archive
92+ - name : Create backup archive (synthetic fixture)
9493 run : python scripts/backup.py --output backup-artifacts
9594 - name : Resolve backup path
9695 id : backup
@@ -105,9 +104,9 @@ jobs:
105104 python scripts/restore.py \
106105 --backup "${{ steps.backup.outputs.archive_path }}" \
107106 --target-root /tmp/agentflow-restore
108- - name : Upload backup artifact
107+ - name : Upload regression-test archive
109108 uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
110109 with :
111- name : agentflow-nightly- backup
110+ name : agentflow-backup-restore-regression-fixture
112111 path : ${{ steps.backup.outputs.archive_path }}
113112 retention-days : 7
0 commit comments