|
@@ -328,3 +328,219 @@ def test_old_140_upgrades_to_durable_handoff_and_enforces_cas(tmp_path):
|
|
|
)
|
|
)
|
|
|
connection.execute(text(f'DROP DATABASE IF EXISTS "{database_name}"'))
|
|
connection.execute(text(f'DROP DATABASE IF EXISTS "{database_name}"'))
|
|
|
admin.dispose()
|
|
admin.dispose()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_old_180_cleanup_claims_become_expired_and_cas_takeover_ready():
|
|
|
|
|
+ platform_user = _compose_value(
|
|
|
|
|
+ r"\n postgres:.*?POSTGRES_USER:\s*([^\s]+)"
|
|
|
|
|
+ )
|
|
|
|
|
+ platform_password = _compose_value(
|
|
|
|
|
+ r"\n postgres:.*?POSTGRES_PASSWORD:\s*([^\s]+)"
|
|
|
|
|
+ )
|
|
|
|
|
+ platform_port = _compose_value(r'"(15432):5432"')
|
|
|
|
|
+ admin_url = (
|
|
|
|
|
+ f"postgresql+psycopg2://{platform_user}:{platform_password}"
|
|
|
|
|
+ f"@127.0.0.1:{platform_port}/postgres"
|
|
|
|
|
+ )
|
|
|
|
|
+ database_name = f"task6_claim_{new_governance_uid().replace('-', '')}"
|
|
|
|
|
+ database_url = (
|
|
|
|
|
+ f"postgresql+psycopg2://{platform_user}:{platform_password}"
|
|
|
|
|
+ f"@127.0.0.1:{platform_port}/{database_name}"
|
|
|
|
|
+ )
|
|
|
|
|
+ admin = create_engine(admin_url, isolation_level="AUTOCOMMIT")
|
|
|
|
|
+ engine = None
|
|
|
|
|
+ try:
|
|
|
|
|
+ with admin.connect() as connection:
|
|
|
|
|
+ connection.execute(text(f'CREATE DATABASE "{database_name}"'))
|
|
|
|
|
+ _upgrade(database_url, "20260723_180")
|
|
|
|
|
+ engine = create_engine(database_url, pool_pre_ping=True)
|
|
|
|
|
+ rule_run_id = new_governance_uid()
|
|
|
|
|
+ sample_id = new_governance_uid()
|
|
|
|
|
+ receipt_id = new_governance_uid()
|
|
|
|
|
+ old_sample_claim = new_governance_uid()
|
|
|
|
|
+ old_receipt_claim = new_governance_uid()
|
|
|
|
|
+ with engine.begin() as connection:
|
|
|
|
|
+ expiry_columns = connection.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT table_name
|
|
|
|
|
+ FROM information_schema.columns
|
|
|
|
|
+ WHERE table_schema = 'public'
|
|
|
|
|
+ AND table_name IN (
|
|
|
|
|
+ 'rule_violation_samples',
|
|
|
|
|
+ 'rule_sql_staging_receipts'
|
|
|
|
|
+ )
|
|
|
|
|
+ AND column_name = 'cleanup_claim_expires_at'
|
|
|
|
|
+ """
|
|
|
|
|
+ )
|
|
|
|
|
+ ).all()
|
|
|
|
|
+ assert expiry_columns == []
|
|
|
|
|
+ connection.execute(text("SET session_replication_role = replica"))
|
|
|
|
|
+ connection.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ """
|
|
|
|
|
+ INSERT INTO public.rule_runs (
|
|
|
|
|
+ id, deployment_id, component_binding_id,
|
|
|
|
|
+ rule_version_id, plan_hash, status, correlation_id
|
|
|
|
|
+ ) VALUES (
|
|
|
|
|
+ CAST(:id AS uuid), CAST(:deployment_id AS uuid),
|
|
|
|
|
+ CAST(:component_id AS uuid),
|
|
|
|
|
+ CAST(:rule_version_id AS uuid),
|
|
|
|
|
+ :plan_hash, 'failed', CAST(:correlation_id AS uuid)
|
|
|
|
|
+ )
|
|
|
|
|
+ """
|
|
|
|
|
+ ),
|
|
|
|
|
+ {
|
|
|
|
|
+ "id": rule_run_id,
|
|
|
|
|
+ "deployment_id": new_governance_uid(),
|
|
|
|
|
+ "component_id": new_governance_uid(),
|
|
|
|
|
+ "rule_version_id": new_governance_uid(),
|
|
|
|
|
+ "plan_hash": "a" * 64,
|
|
|
|
|
+ "correlation_id": new_governance_uid(),
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ connection.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ """
|
|
|
|
|
+ INSERT INTO public.rule_violation_samples (
|
|
|
|
|
+ id, rule_run_id, artifact_ref, sample_count,
|
|
|
|
|
+ redaction_policy, expires_at, cleanup_claim
|
|
|
|
|
+ ) VALUES (
|
|
|
|
|
+ CAST(:id AS uuid), CAST(:rule_run_id AS uuid),
|
|
|
|
|
+ :artifact_ref, 1, 'all-fields',
|
|
|
|
|
+ CURRENT_TIMESTAMP - INTERVAL '1 hour',
|
|
|
|
|
+ CAST(:cleanup_claim AS uuid)
|
|
|
|
|
+ )
|
|
|
|
|
+ """
|
|
|
|
|
+ ),
|
|
|
|
|
+ {
|
|
|
|
|
+ "id": sample_id,
|
|
|
|
|
+ "rule_run_id": rule_run_id,
|
|
|
|
|
+ "artifact_ref": (
|
|
|
|
|
+ f"minio://legacy/{new_governance_uid()}.parquet"
|
|
|
|
|
+ ),
|
|
|
|
|
+ "cleanup_claim": old_sample_claim,
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ connection.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ """
|
|
|
|
|
+ INSERT INTO public.rule_sql_staging_receipts (
|
|
|
|
|
+ id, producer_rule_run_id, deployment_id,
|
|
|
|
|
+ correlation_id, output_binding_id,
|
|
|
|
|
+ output_binding_hash, relation_ref, relation_digest,
|
|
|
|
|
+ commit_outcome, status, expires_at, cleanup_claim
|
|
|
|
|
+ ) VALUES (
|
|
|
|
|
+ CAST(:id AS uuid), CAST(:run_id AS uuid),
|
|
|
|
|
+ CAST(:deployment_id AS uuid),
|
|
|
|
|
+ CAST(:correlation_id AS uuid),
|
|
|
|
|
+ CAST(:binding_id AS uuid), :binding_hash,
|
|
|
|
|
+ :relation_ref, :relation_digest,
|
|
|
|
|
+ 'committed', 'expired',
|
|
|
|
|
+ CURRENT_TIMESTAMP - INTERVAL '1 hour',
|
|
|
|
|
+ CAST(:cleanup_claim AS uuid)
|
|
|
|
|
+ )
|
|
|
|
|
+ """
|
|
|
|
|
+ ),
|
|
|
|
|
+ {
|
|
|
|
|
+ "id": receipt_id,
|
|
|
|
|
+ "run_id": rule_run_id,
|
|
|
|
|
+ "deployment_id": new_governance_uid(),
|
|
|
|
|
+ "correlation_id": new_governance_uid(),
|
|
|
|
|
+ "binding_id": new_governance_uid(),
|
|
|
|
|
+ "binding_hash": "b" * 64,
|
|
|
|
|
+ "relation_ref": "legacy.receipt",
|
|
|
|
|
+ "relation_digest": "c" * 64,
|
|
|
|
|
+ "cleanup_claim": old_receipt_claim,
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ connection.execute(text("SET session_replication_role = origin"))
|
|
|
|
|
+
|
|
|
|
|
+ engine.dispose()
|
|
|
|
|
+ engine = None
|
|
|
|
|
+ _upgrade(database_url, "20260723_190")
|
|
|
|
|
+ engine = create_engine(database_url, pool_pre_ping=True)
|
|
|
|
|
+ new_sample_claim = new_governance_uid()
|
|
|
|
|
+ new_receipt_claim = new_governance_uid()
|
|
|
|
|
+ with engine.begin() as connection:
|
|
|
|
|
+ migrated = connection.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT
|
|
|
|
|
+ (
|
|
|
|
|
+ SELECT cleanup_claim_expires_at
|
|
|
|
|
+ FROM public.rule_violation_samples
|
|
|
|
|
+ WHERE id = CAST(:sample_id AS uuid)
|
|
|
|
|
+ ) AS sample_claim_expires_at,
|
|
|
|
|
+ (
|
|
|
|
|
+ SELECT cleanup_claim_expires_at
|
|
|
|
|
+ FROM public.rule_sql_staging_receipts
|
|
|
|
|
+ WHERE id = CAST(:receipt_id AS uuid)
|
|
|
|
|
+ ) AS receipt_claim_expires_at,
|
|
|
|
|
+ CURRENT_TIMESTAMP AS observed_at
|
|
|
|
|
+ """
|
|
|
|
|
+ ),
|
|
|
|
|
+ {"sample_id": sample_id, "receipt_id": receipt_id},
|
|
|
|
|
+ ).mappings().one()
|
|
|
|
|
+ assert migrated["sample_claim_expires_at"] is not None
|
|
|
|
|
+ assert migrated["receipt_claim_expires_at"] is not None
|
|
|
|
|
+ assert migrated["sample_claim_expires_at"] <= migrated["observed_at"]
|
|
|
|
|
+ assert migrated["receipt_claim_expires_at"] <= migrated["observed_at"]
|
|
|
|
|
+
|
|
|
|
|
+ sample_takeover = connection.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ """
|
|
|
|
|
+ UPDATE public.rule_violation_samples
|
|
|
|
|
+ SET cleanup_claim = CAST(:new_claim AS uuid),
|
|
|
|
|
+ cleanup_claim_expires_at =
|
|
|
|
|
+ CURRENT_TIMESTAMP + INTERVAL '5 minutes'
|
|
|
|
|
+ WHERE id = CAST(:id AS uuid)
|
|
|
|
|
+ AND cleanup_claim = CAST(:old_claim AS uuid)
|
|
|
|
|
+ AND cleanup_claim_expires_at <= CURRENT_TIMESTAMP
|
|
|
|
|
+ RETURNING cleanup_claim
|
|
|
|
|
+ """
|
|
|
|
|
+ ),
|
|
|
|
|
+ {
|
|
|
|
|
+ "id": sample_id,
|
|
|
|
|
+ "old_claim": old_sample_claim,
|
|
|
|
|
+ "new_claim": new_sample_claim,
|
|
|
|
|
+ },
|
|
|
|
|
+ ).scalar_one()
|
|
|
|
|
+ receipt_takeover = connection.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ """
|
|
|
|
|
+ UPDATE public.rule_sql_staging_receipts
|
|
|
|
|
+ SET cleanup_claim = CAST(:new_claim AS uuid),
|
|
|
|
|
+ cleanup_claim_expires_at =
|
|
|
|
|
+ CURRENT_TIMESTAMP + INTERVAL '5 minutes'
|
|
|
|
|
+ WHERE id = CAST(:id AS uuid)
|
|
|
|
|
+ AND cleanup_claim = CAST(:old_claim AS uuid)
|
|
|
|
|
+ AND cleanup_claim_expires_at <= CURRENT_TIMESTAMP
|
|
|
|
|
+ RETURNING cleanup_claim
|
|
|
|
|
+ """
|
|
|
|
|
+ ),
|
|
|
|
|
+ {
|
|
|
|
|
+ "id": receipt_id,
|
|
|
|
|
+ "old_claim": old_receipt_claim,
|
|
|
|
|
+ "new_claim": new_receipt_claim,
|
|
|
|
|
+ },
|
|
|
|
|
+ ).scalar_one()
|
|
|
|
|
+ assert str(sample_takeover) == new_sample_claim
|
|
|
|
|
+ assert str(receipt_takeover) == new_receipt_claim
|
|
|
|
|
+ finally:
|
|
|
|
|
+ if engine is not None:
|
|
|
|
|
+ engine.dispose()
|
|
|
|
|
+ with admin.connect() as connection:
|
|
|
|
|
+ connection.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ """
|
|
|
|
|
+ SELECT pg_terminate_backend(pid)
|
|
|
|
|
+ FROM pg_stat_activity
|
|
|
|
|
+ WHERE datname = :database_name
|
|
|
|
|
+ AND pid <> pg_backend_pid()
|
|
|
|
|
+ """
|
|
|
|
|
+ ),
|
|
|
|
|
+ {"database_name": database_name},
|
|
|
|
|
+ )
|
|
|
|
|
+ connection.execute(text(f'DROP DATABASE IF EXISTS "{database_name}"'))
|
|
|
|
|
+ admin.dispose()
|