#!/usr/bin/env python3 """Actual-Postgres proof for G7 consent-withdrawal serialization. The probe creates a unique synthetic learner/session, grants derived-feature processing, holds the shared consent transaction lock while committing a withdrawal, and proves a concurrent evaluator timeline write remains blocked until that withdrawal commits and then fails closed. No microphone, provider, login, or external service is used. The append-only fixture is intentionally left in the expendable development database as audit evidence. """ from __future__ import annotations import asyncio import json import sys import time from pathlib import Path from uuid import UUID, uuid4 API_ROOT = Path(__file__).resolve().parents[1] / "apps" / "api" if str(API_ROOT) not in sys.path: sys.path.insert(0, str(API_ROOT)) from app import db # noqa: E402 from app.contracts.multimodal_alliance import AlignedVoiceTimeline # noqa: E402 from app.deps import Principal, Role # noqa: E402 from app.services import multimodal_alliance_store as store # noqa: E402 def learner_principal(learner_id: UUID) -> Principal: return Principal( user_id=str(learner_id), role=Role.LEARNER, cohort_ids=["g7-consent-concurrency"], consent_at=1.0, profile_completed_at=1.0, ) async def seed_fixture(learner_id: UUID, session_id: UUID) -> None: pool = db.get_pool() async with pool.acquire() as conn: await conn.execute( """ INSERT INTO app.app_user ( user_id, external_id, email, display_name, role, cohort, consent_at, profile_completed_at ) VALUES ($1,$2,$3,'G7 Consent Concurrency','learner', 'g7-consent-concurrency',now(),now()) """, learner_id, f"g7-consent-concurrency:{learner_id}", f"{learner_id}@g7-consent-concurrency.invalid", ) async with db.acquire( role="learner", user_id=str(learner_id), cohort_ids=["g7-consent-concurrency"], ) as conn: await conn.execute( """ INSERT INTO app.sessions (id, learner_id, theory_mode) VALUES ($1,$2,'integrative') """, session_id, learner_id, ) async def persist_timeline(session_id: UUID, submission_id: UUID) -> dict: async with db.acquire(ai_view="evaluator", ai_context=True) as conn: return await store.append_timeline( conn=conn, session_id=session_id, submission_id=submission_id, timeline=AlignedVoiceTimeline( audio_duration_ms=1000, words=(), events=(), ), audio_asset=None, ) async def append_withdrawal_inside_lock( *, conn, learner_id: UUID, session_id: UUID, submission_id: UUID, ) -> UUID: latest = await store._latest_consent(conn, session_id) if latest is None or latest["consent_status"] != "granted": raise AssertionError("concurrency probe requires a current granted consent") consent_snapshot_id = uuid4() await store._insert_request( conn, submission_id=submission_id, content_hash=store._canonical_hash( { "probe": "concurrent-withdrawal", "session_id": str(session_id), } ), request_kind="consent", session_id=session_id, learner_id=learner_id, result_id=consent_snapshot_id, created_by_role="learner", ) await conn.execute( """ INSERT INTO app.multimodal_consent_snapshot ( consent_snapshot_id, submission_id, session_id, learner_id, sequence_no, consent_status, retain_audio, retain_derived_features, transcript_retained, retention_days, policy_version, reason_code, created_by_uid ) VALUES ($1,$2,$3,$4,$5,'withdrawn',FALSE,FALSE,TRUE,NULL, 'g7-concurrency-v1','concurrency_probe',$4) """, consent_snapshot_id, submission_id, session_id, learner_id, int(latest["sequence_no"]) + 1, ) deletion_submission_id, deletion_request_id = store._withdrawal_ids(submission_id) await store._insert_deletion_request( conn, submission_id=deletion_submission_id, deletion_request_id=deletion_request_id, session_id=session_id, learner_id=learner_id, scopes=("audio", "derived_features"), request_reason="consent_withdrawal", requested_by_uid=learner_id, created_by_role="learner", ) return consent_snapshot_id async def main() -> None: learner_id = uuid4() session_id = uuid4() grant_submission_id = uuid4() withdrawal_submission_id = uuid4() timeline_submission_id = uuid4() await db.init_pool() timeline_task: asyncio.Task[dict] | None = None try: await seed_fixture(learner_id, session_id) learner = learner_principal(learner_id) await store.append_consent_snapshot( principal=learner, session_id=session_id, submission_id=grant_submission_id, consent_status="granted", retain_audio=False, retain_derived_features=True, transcript_retained=True, retention_days=30, policy_version="g7-concurrency-v1", reason_code=None, ) blocked_while_withdrawal_uncommitted = False started_at = time.monotonic() async with db.acquire( role="learner", user_id=str(learner_id), cohort_ids=["g7-consent-concurrency"], ) as withdrawal_conn: await store._lock_consent_processing(withdrawal_conn, session_id) timeline_task = asyncio.create_task( persist_timeline(session_id, timeline_submission_id) ) try: await asyncio.wait_for(asyncio.shield(timeline_task), timeout=0.25) except TimeoutError: blocked_while_withdrawal_uncommitted = True else: raise AssertionError( "timeline write escaped the shared consent transaction lock" ) withdrawal_snapshot_id = await append_withdrawal_inside_lock( conn=withdrawal_conn, learner_id=learner_id, session_id=session_id, submission_id=withdrawal_submission_id, ) write_blocked_after_commit = False try: await asyncio.wait_for(timeline_task, timeout=5.0) except store.MultimodalConsentRequiredError: write_blocked_after_commit = True if not write_blocked_after_commit: raise AssertionError("timeline write was not rejected after withdrawal commit") async with db.acquire(ai_view="evaluator", ai_context=True) as conn: timeline_rows = await conn.fetchval( """ SELECT count(*) FROM app.multimodal_audio_timeline WHERE submission_id = $1 """, timeline_submission_id, ) latest = await store._latest_consent(conn, session_id) if timeline_rows != 0: raise AssertionError("a post-withdrawal timeline row was persisted") if latest is None or latest["consent_snapshot_id"] != withdrawal_snapshot_id: raise AssertionError("withdrawal is not the latest consent snapshot") voice_processing_blocked = False try: await store.assert_voice_processing_allowed( principal=learner, session_id=str(session_id), ) except store.MultimodalConsentWithdrawnError: voice_processing_blocked = True if not voice_processing_blocked: raise AssertionError("withdrawal did not block subsequent voice processing") print( json.dumps( { "schema_version": "vignette.g7-consent-concurrency-proof.v1", "status": "passed", "actual_postgres": True, "synthetic_fixture": True, "session_id": str(session_id), "blocked_while_withdrawal_uncommitted": ( blocked_while_withdrawal_uncommitted ), "write_blocked_after_withdrawal_commit": ( write_blocked_after_commit ), "timeline_rows_persisted": timeline_rows, "subsequent_voice_processing_blocked": voice_processing_blocked, "elapsed_ms": round((time.monotonic() - started_at) * 1000), "microphone_used": False, "external_provider_used": False, }, ensure_ascii=False, indent=2, ) ) finally: if timeline_task is not None and not timeline_task.done(): timeline_task.cancel() await db.close_pool() if __name__ == "__main__": asyncio.run(main())