returnfmt.Errorf("privacy request type %s is not handled by delete processor",request.requestType)
}
if_,err:=p.pool.Exec(ctx,`UPDATE privacy_request SET status='VERIFYING',version=version+1,updated_at=now() WHERE id=$1::uuid AND status='SUBMITTED'`,request.id);err!=nil{
returnerr
}
if_,err:=p.pool.Exec(ctx,`UPDATE privacy_request SET status='EXECUTING',completed_at=NULL,version=version+1,updated_at=now() WHERE id=$1::uuid AND status IN ('VERIFYING','PARTIAL_FAILED')`,request.id);err!=nil{
if_,err:=p.pool.Exec(ctx,`UPDATE privacy_subtask SET status='RUNNING',attempts=attempts+1,next_attempt_at=NULL,last_error=NULL,completed_at=NULL,updated_at=now() WHERE id=$1::uuid`,task.id);err!=nil{
if_,err:=p.pool.Exec(ctx,`UPDATE privacy_subtask SET status=$2,last_error=NULLIF($3,''),next_attempt_at=NULL,completed_at=now(),updated_at=now() WHERE id=$1::uuid`,task.id,outcome,note);err!=nil{
iferr:=tx.QueryRow(ctx,`SELECT count(*) FROM privacy_subtask WHERE privacy_request_id=$1::uuid AND status NOT IN ('COMPLETED','SKIPPED')`,request.id).Scan(&open);err!=nil{
returnerr
}
ifopen!=0{
returnerrors.New("privacy deletion reconciliation still has open subtasks")
if_,err:=tx.Exec(ctx,`UPDATE privacy_request SET status='COMPLETED',result_summary=$2::jsonb,completed_at=now(),version=version+1,updated_at=now() WHERE id=$1::uuid`,request.id,result);err!=nil{
if_,err:=tx.Exec(ctx,`INSERT INTO tombstone(tenant_id,resource_type,resource_digest,deletion_request_id,retention_reason) VALUES($1::uuid,'subject',$2,$3::uuid,'privacy deletion proof; contains no subject content') ON CONFLICT (tenant_id,resource_type,resource_digest) DO NOTHING`,request.tenantID,hex.EncodeToString(digest[:]),request.id);err!=nil{
if_,err:=tx.Exec(ctx,`UPDATE privacy_subtask SET status='FAILED',last_error=$2,next_attempt_at=$3,updated_at=now() WHERE id=$1::uuid`,task.id,safeDeletionError(runErr),nextAttempt);err!=nil{
returnerr
}
_,err:=tx.Exec(ctx,`UPDATE privacy_request SET status='PARTIAL_FAILED',result_summary=jsonb_build_object('failed_subsystem',$2::text,'content_included',false),version=version+1,updated_at=now() WHERE id=$1::uuid`,request.id,task.subsystem)
err:=p.pool.QueryRow(ctx,`SELECT pr.id::text,pr.tenant_id::text,pr.subject_id::text,sp.public_id::text,pr.public_id::text,pr.status,pr.request_type FROM privacy_request pr JOIN subject_profile sp ON sp.id=pr.subject_id WHERE pr.id=$1::uuid`,id).Scan(&value.id,&value.tenantID,&value.subjectID,&value.subjectPublicID,&value.publicID,&value.status,&value.requestType)
rows,err:=p.pool.Query(ctx,`SELECT id::text,subsystem,status FROM privacy_subtask WHERE privacy_request_id=$1::uuid ORDER BY CASE subsystem WHEN 'redis' THEN 1 WHEN 'export_files' THEN 2 WHEN 'object_storage' THEN 3 WHEN 'pgvector' THEN 4 WHEN 'provider_index' THEN 5 WHEN 'postgresql' THEN 6 ELSE 99 END,id`,requestID)
err:=p.pool.QueryRow(ctx,`SELECT EXISTS(SELECT 1 FROM memory_projection_job WHERE subject_id=$1::uuid AND state<>'VISIBLE'), EXISTS(SELECT 1 FROM call_session WHERE subject_id=$1::uuid AND manifest_status IN('ISSUED','REVOKED') AND NOT(local_audio_off AND sdk_stopped AND remote_state IN ('NOT_STARTED','STOPPED_OBSERVED'))), EXISTS(SELECT 1 FROM memory_deletion_ledger WHERE subject_id=$1::uuid AND backup_cleanup_verified_at IS NULL), EXISTS(SELECT 1 FROM memory_user_state WHERE subject_id=$1::uuid AND legacy_projection_pending), call_legacy_cleanup_pending($1::uuid)`,request.subjectID).Scan(&pending.Projection,&pending.Runtime,&pending.Backup,&pending.LegacyProjection,&pending.LegacyCleanup)
rows,err:=p.pool.Query(ctx,`SELECT object_ref FROM export_task WHERE tenant_id=$1::uuid AND subject_id=$2::uuid AND object_ref IS NOT NULL`,request.tenantID,request.subjectID)
iferr!=nil{
return"","",err
}
refs:=[]string{}
forrows.Next(){
varrefstring
iferr:=rows.Scan(&ref);err!=nil{
rows.Close()
return"","",err
}
refs=append(refs,ref)
}
rows.Close()
iferr:=rows.Err();err!=nil{
return"","",err
}
for_,ref:=rangerefs{
iferr:=p.store.Remove(ctx,ref);err!=nil{
return"","",outbox.Retryable(err)
}
}
_,err=p.pool.Exec(ctx,`UPDATE export_task SET status='EXPIRED',scope='{}'::jsonb,object_ref=NULL,object_size=NULL,content_digest=NULL,expires_at=NULL,last_error=NULL,updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,request.tenantID,request.subjectID)
return"COMPLETED","",err
case"object_storage":
rows,err:=p.pool.Query(ctx,`SELECT ms.object_ref FROM media_segment ms JOIN conversation c ON c.id=ms.conversation_id WHERE c.tenant_id=$1::uuid AND c.subject_id=$2::uuid AND ms.object_ref IS NOT NULL`,request.tenantID,request.subjectID)
iferr!=nil{
return"","",err
}
refs:=[]string{}
forrows.Next(){
varrefstring
iferr:=rows.Scan(&ref);err!=nil{
rows.Close()
return"","",err
}
refs=append(refs,ref)
}
rows.Close()
iferr:=rows.Err();err!=nil{
return"","",err
}
for_,ref:=rangerefs{
iferr:=p.store.Remove(ctx,ref);err!=nil{
return"","",outbox.Retryable(err)
}
}
_,err=p.pool.Exec(ctx,`UPDATE media_segment ms SET status='DELETED',object_ref=NULL,expected_digest=repeat('0',64),expected_size=1,duration_ms=1,last_error_code=NULL,deleted_at=COALESCE(deleted_at,now()),updated_at=now() FROM conversation c WHERE ms.conversation_id=c.id AND c.tenant_id=$1::uuid AND c.subject_id=$2::uuid`,request.tenantID,request.subjectID)
return"COMPLETED","",err
case"pgvector":
_,err:=p.pool.Exec(ctx,`UPDATE memory_index_entry SET embedding=NULL,provider_ref=NULL,status='DELETED',last_error=NULL,updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid AND index_type='pgvector'`,request.tenantID,request.subjectID)
rows,err:=p.pool.Query(ctx,`SELECT memory_version_id::text,provider_ref FROM memory_index_entry WHERE tenant_id=$1::uuid AND subject_id=$2::uuid AND index_type='provider' AND provider_ref IS NOT NULL AND status<>'DELETED' ORDER BY created_at,id`,request.tenantID,request.subjectID)
_,err=p.pool.Exec(ctx,`UPDATE memory_index_entry SET embedding=NULL,provider_ref=NULL,status='DELETED',last_error=NULL,updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid AND index_type='provider'`,request.tenantID,request.subjectID)
iflen(refs)==0{
return"SKIPPED","no external provider index references were present",err
rows,err:=p.pool.Query(ctx,`SELECT DISTINCT c.public_id::text,d.public_id::text FROM conversation c JOIN device d ON d.id=c.device_id AND d.tenant_id=c.tenant_id WHERE c.tenant_id=$1::uuid AND c.subject_id=$2::uuid`,tenantID,subjectID)
`DELETE FROM call_request_result WHERE response->>'manifest_b64' IN(SELECT manifest_b64 FROM call_session WHERE tenant_id=$1::uuid AND subject_id=$2::uuid)`,
`UPDATE call_event SET payload='{"redacted":true}'::jsonb WHERE session_id IN(SELECT id FROM call_session WHERE tenant_id=$1::uuid AND subject_id=$2::uuid)`,
`UPDATE call_utterance SET text_content='[deleted]' WHERE session_id IN(SELECT id FROM call_session WHERE tenant_id=$1::uuid AND subject_id=$2::uuid)`,
`UPDATE call_text_fragment SET text_content='[deleted]' WHERE session_id IN(SELECT id FROM call_session WHERE tenant_id=$1::uuid AND subject_id=$2::uuid)`,
`UPDATE call_session SET manifest_b64=NULL,manifest_mac=NULL,budget_snapshot=budget_snapshot-'story_context' WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE call_postprocess_job SET state='SUPERSEDED' WHERE session_id IN(SELECT id FROM call_session WHERE tenant_id=$1::uuid AND subject_id=$2::uuid)`,
`UPDATE memory_mutation SET patch='{}',confirmation_hash=NULL,proposal_digest=NULL,confirmation_expires_at=NULL,state=CASE WHEN state IN('RECEIVED','NEEDS_CONFIRMATION') THEN 'CANCELLED' ELSE state END WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE subject_fact SET content='[已按隐私请求删除]',source_ref=NULL,confirmation_status='superseded',valid_to=COALESCE(valid_to,now()),version=version+1,updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE conversation_turn SET final_text='[已按隐私请求删除]',provider_ref=NULL WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE conversation_summary SET content='[已按隐私请求删除]',source_turn_ids='["privacy-redacted"]'::jsonb,status='SUPERSEDED',last_error=NULL WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE conversation_content_assessment SET decision_reason='[privacy-redacted]',model_ref='privacy-redacted',prompt_version='privacy-redacted' WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE story_revision sr SET content='[已按隐私请求删除]',edit_reason=NULL,model_ref=NULL,prompt_version=NULL FROM story_draft sd WHERE sr.story_id=sd.id AND sd.tenant_id=$1::uuid AND sd.subject_id=$2::uuid`,
`UPDATE story_draft SET title='[已按隐私请求删除]',status='SUPERSEDED',review_reason=NULL,version=version+1,updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE fact_candidate SET content='[已按隐私请求删除]',time_range='{}'::jsonb,source_turn_ids='["privacy-redacted"]'::jsonb,status='SUPERSEDED',review_reason=NULL,version=version+1,updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE fact_candidate_revision fcr SET content='[已按隐私请求删除]',edit_reason=NULL FROM fact_candidate fc WHERE fcr.fact_candidate_id=fc.id AND fc.tenant_id=$1::uuid AND fc.subject_id=$2::uuid`,
`UPDATE memory_version mv SET content='[已按隐私请求删除]',source_refs='[{"type":"privacy_tombstone"}]'::jsonb,change_reason=NULL,content_digest=encode(digest('[已按隐私请求删除]','sha256'),'hex') FROM curated_memory cm WHERE mv.memory_id=cm.id AND cm.tenant_id=$1::uuid AND cm.subject_id=$2::uuid`,
`UPDATE curated_memory SET status='DELETED',entity_key='',completion_basis=CASE WHEN completion_basis IS NOT NULL THEN '{"redacted":true}'::jsonb END,valid_to=COALESCE(valid_to,now()),deleted_at=COALESCE(deleted_at,now()),version=version+1,updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE memory_index_entry SET embedding=NULL,provider_ref=NULL,status='DELETED',last_error=NULL,updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`DELETE FROM memoir_document WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`DELETE FROM chat_daily_event WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE memoir_import_turn SET content='[deleted]' WHERE import_id IN(SELECT id FROM memoir_chat_import WHERE tenant_id=$1::uuid AND subject_id=$2::uuid)`,
`UPDATE memoir_chat_import SET title='[deleted]',status='FAILED' WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE memoir_generation_job SET state='FAILED',error_code='MEMOIR_CONSENT_REQUIRED',updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE consent_snapshot SET effective_consents='{}'::jsonb,source_consent_ids='[]'::jsonb WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE context_snapshot SET memory_versions='[]'::jsonb,content_digest=repeat('0',64),sanitized_summary=NULL WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE recording_access_grant SET purpose='[privacy-redacted]',revoked_at=COALESCE(revoked_at,now()),updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE sharing_grant SET purpose='[privacy-redacted]',revoked_at=COALESCE(revoked_at,now()),version=version+1,updated_at=now() WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE sharing_access_log sal SET reason_code=CASE WHEN result='DENIED' THEN 'PRIVACY_REDACTED' ELSE NULL END FROM sharing_grant sg WHERE sal.sharing_grant_id=sg.id AND sg.tenant_id=$1::uuid AND sg.subject_id=$2::uuid`,
`UPDATE media_access_log mal SET purpose='[privacy-redacted]',reason_code=NULL FROM conversation c WHERE mal.conversation_id=c.id AND c.tenant_id=$1::uuid AND c.subject_id=$2::uuid`,
`UPDATE upload_batch ub SET manifest_digest=repeat('0',64),last_error_code=NULL,status=CASE WHEN ub.status='COMPLETED' THEN ub.status ELSE 'EXPIRED' END,updated_at=now() FROM conversation c WHERE ub.conversation_id=c.id AND c.tenant_id=$1::uuid AND c.subject_id=$2::uuid`,
`UPDATE provider_event pe SET sanitized_payload='{}'::jsonb,last_error=NULL FROM conversation c WHERE pe.conversation_id=c.id AND c.tenant_id=$1::uuid AND c.subject_id=$2::uuid`,
`UPDATE device_binding SET unbound_at=COALESCE(unbound_at,now()),unbound_by=COALESCE(unbound_by,(SELECT requested_by FROM privacy_request WHERE id=$3::uuid)),reason='subject privacy deletion' WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
`UPDATE subject_profile SET preferred_name='[已删除]',profile_data='{}'::jsonb,conversation_preferences='{}'::jsonb,status='deleted',deleted_at=COALESCE(deleted_at,now()),profile_version=profile_version+1,updated_at=now() WHERE tenant_id=$1::uuid AND id=$2::uuid`,
`DELETE FROM web_event WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
if_,err:=tx.Exec(ctx,`UPDATE outbox_event SET payload='{"privacy_redacted":true}'::jsonb,status=CASE WHEN status IN ('pending','processing') THEN 'completed' ELSE status END,completed_at=CASE WHEN status IN ('pending','processing') THEN COALESCE(completed_at,now()) ELSE completed_at END,locked_at=NULL,locked_by=NULL,updated_at=now() WHERE tenant_id=$1::uuid AND aggregate_id IN (SELECT id FROM conversation WHERE subject_id=$2::uuid UNION SELECT id FROM call_session WHERE subject_id=$2::uuid UNION SELECT id FROM curated_memory WHERE subject_id=$2::uuid UNION SELECT id FROM export_task WHERE subject_id=$2::uuid UNION SELECT ub.public_id FROM upload_batch ub JOIN conversation c ON c.id=ub.conversation_id WHERE c.subject_id=$2::uuid)`,request.tenantID,request.subjectID);err!=nil{
if_,err:=tx.Exec(ctx,`UPDATE audit_event SET purpose=NULL,detail='{"privacy_redacted":true}'::jsonb WHERE tenant_id=$1::uuid AND (detail->>'subject_id'=(SELECT public_id::text FROM subject_profile WHERE id=$2::uuid) OR resource_id IN (SELECT public_id FROM subject_profile WHERE id=$2::uuid UNION SELECT public_id FROM subject_fact WHERE subject_id=$2::uuid UNION SELECT public_id FROM consent_record WHERE subject_id=$2::uuid UNION SELECT public_id FROM conversation WHERE subject_id=$2::uuid UNION SELECT public_id FROM story_draft WHERE subject_id=$2::uuid UNION SELECT public_id FROM curated_memory WHERE subject_id=$2::uuid UNION SELECT public_id FROM export_task WHERE subject_id=$2::uuid UNION SELECT public_id FROM privacy_request WHERE subject_id=$2::uuid UNION SELECT public_id FROM recording_access_grant WHERE subject_id=$2::uuid UNION SELECT public_id FROM sharing_grant WHERE subject_id=$2::uuid UNION SELECT ub.public_id FROM upload_batch ub JOIN conversation c ON c.id=ub.conversation_id WHERE c.subject_id=$2::uuid UNION SELECT ms.public_id FROM media_segment ms JOIN conversation c ON c.id=ms.conversation_id WHERE c.subject_id=$2::uuid))`,request.tenantID,request.subjectID);err!=nil{