fix(security): repair canonical asset integrity
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m32s
CD Pipeline / build-and-deploy (push) Successful in 7m58s
CD Pipeline / post-deploy-checks (push) Successful in 2m25s

This commit is contained in:
ogt
2026-07-14 11:54:02 +08:00
parent 06177b89d6
commit 4508d19d4b
6 changed files with 618 additions and 1 deletions

View File

@@ -52,7 +52,7 @@ def test_cd_applies_and_independently_verifies_the_bounded_migration() -> None:
workflow = CD_WORKFLOW.read_text(encoding="utf-8")
step = workflow.split(
"- name: Reconcile Asset Inventory Type Constraint", 1
)[1].split("# 2026-03-31 ogt: 移除中間通知", 1)[0]
)[1].split("- name: Repair Asset Inventory Canonical Integrity", 1)[0]
script = step.split("run: |", 1)[1]
assert "MIGRATION_DATABASE_URL: ${{ secrets.MIGRATION_DATABASE_URL }}" in step

View File

@@ -0,0 +1,71 @@
import textwrap
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
MIGRATION = (
ROOT
/ "apps"
/ "api"
/ "migrations"
/ "asset_inventory_canonical_integrity_2026-07-14.sql"
)
CD_WORKFLOW = ROOT / ".gitea" / "workflows" / "cd.yaml"
def test_migration_quarantines_duplicates_without_deleting_history() -> None:
sql = MIGRATION.read_text(encoding="utf-8")
upper = sql.upper()
assert "SET lock_timeout = '10s'" in sql
assert "SET statement_timeout = '90s'" in sql
assert "SET enable_indexscan = 'off'" in sql
assert "SET enable_indexonlyscan = 'off'" in sql
assert "SET enable_bitmapscan = 'off'" in sql
assert "ROW_NUMBER() OVER" in sql
assert "repair_row_count > 100" in sql
assert "__integrity_quarantine__" in sql
assert "lifecycle_state = 'deprecated'" in sql
assert "REINDEX INDEX asset_inventory_asset_key_key" in sql
assert "HAVING COUNT(*) > 1" in sql
assert "ON CONFLICT (asset_key) DO UPDATE" in sql
assert "gitea_cd_asset_integrity_reconciler" in sql
assert '"work_item_id":"AIA-P0-006"' in sql
assert "'data_rows_deleted', 0" in sql
assert "DELETE FROM" not in upper
assert "DROP TABLE" not in upper
assert "TRUNCATE" not in upper
def test_cd_runs_bounded_integrity_repair_and_independent_verifier() -> None:
workflow = CD_WORKFLOW.read_text(encoding="utf-8")
step = workflow.split(
"- name: Repair Asset Inventory Canonical Integrity", 1
)[1].split("# 2026-03-31 ogt: 移除中間通知", 1)[0]
script = step.split("run: |", 1)[1]
assert "MIGRATION_DATABASE_URL: ${{ secrets.MIGRATION_DATABASE_URL }}" in step
assert "DATABASE_URL: ${{ secrets.DATABASE_URL }}" in step
assert "${{ secrets." not in script
assert "--network bridge" in script
assert "--network host" not in script
assert "asset_inventory_canonical_integrity_2026-07-14.sql" in script
assert "SET enable_indexscan TO off" in script
assert "SET enable_indexonlyscan TO off" in script
assert "SET enable_bitmapscan TO off" in script
assert "asset_inventory_integrity_migration_applied=" in script
assert "duplicate_group_count=" in script
assert "quarantined_row_count=" in script
assert "unique_index_healthy=" in script
assert "durable_integrity_receipt_present=" in script
assert "canonical_integrity_verified=" in script
assert "asset_inventory_canonical_integrity_verifier_failed" in script
def test_cd_embedded_integrity_migration_python_compiles() -> None:
workflow = CD_WORKFLOW.read_text(encoding="utf-8")
step = workflow.split(
"- name: Repair Asset Inventory Canonical Integrity", 1
)[1]
source = step.split("python - <<'PY'", 1)[1].split(" PY", 1)[0]
compile(textwrap.dedent(source), "<asset-inventory-integrity-migration>", "exec")

View File

@@ -142,6 +142,81 @@ def test_asset_upsert_propagates_database_failure(monkeypatch) -> None:
asyncio.run(exercise())
def test_asset_key_integrity_preflight_accepts_unique_inventory(monkeypatch) -> None:
requested_projects: list[str | None] = []
statements: list[str] = []
class IntegrityResult:
def one(self):
return 0, True
class IntegrityDatabase:
async def execute(self, statement, parameters=None):
statements.append(str(statement))
return IntegrityResult()
class IntegrityContext:
async def __aenter__(self):
return IntegrityDatabase()
async def __aexit__(self, exc_type, exc, traceback):
return False
def fake_db_context(project_id=None):
requested_projects.append(project_id)
return IntegrityContext()
monkeypatch.setattr("src.db.base.get_db_context", fake_db_context)
asyncio.run(asset_scanner_job._assert_asset_key_integrity())
assert requested_projects == ["awoooi"]
assert any("enable_indexscan" in statement for statement in statements)
assert any("enable_indexonlyscan" in statement for statement in statements)
assert any("enable_bitmapscan" in statement for statement in statements)
assert any("HAVING COUNT(*) > 1" in statement for statement in statements)
assert any(
"asset_inventory_asset_key_key" in statement for statement in statements
)
def test_asset_key_integrity_preflight_fails_closed_on_duplicate_key(
monkeypatch,
) -> None:
class IntegrityResult:
def one(self):
return 1, True
class IntegrityDatabase:
async def execute(self, statement, parameters=None):
return IntegrityResult()
class IntegrityContext:
async def __aenter__(self):
return IntegrityDatabase()
async def __aexit__(self, exc_type, exc, traceback):
return False
monkeypatch.setattr(
"src.db.base.get_db_context",
lambda project_id=None: IntegrityContext(),
)
async def exercise() -> None:
try:
await asset_scanner_job._assert_asset_key_integrity()
except RuntimeError as exc:
assert str(exc) == (
"asset_inventory_key_integrity_failed "
"duplicate_groups=1 unique_index_healthy=true"
)
else:
raise AssertionError("duplicate canonical keys must fail closed")
asyncio.run(exercise())
def test_deprecate_missing_assets_is_bounded_to_successful_prefixes(monkeypatch) -> None:
captured: dict[str, object] = {}
@@ -230,6 +305,9 @@ def test_scan_terminal_is_failed_when_any_collector_is_incomplete(monkeypatch) -
async def fake_collect():
return [], [], (), ("prometheus_targets",)
async def fake_integrity():
return None
async def fake_upsert(*args, **kwargs):
return 0, 0
@@ -246,6 +324,11 @@ def test_scan_terminal_is_failed_when_any_collector_is_incomplete(monkeypatch) -
terminal.update(kwargs)
monkeypatch.setattr(asset_scanner_job, "_start_discovery_run", fake_start)
monkeypatch.setattr(
asset_scanner_job,
"_assert_asset_key_integrity",
fake_integrity,
)
monkeypatch.setattr(asset_scanner_job, "_collect_runtime_assets", fake_collect)
monkeypatch.setattr(asset_scanner_job, "_upsert_assets", fake_upsert)
monkeypatch.setattr(