This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"data-terminal-cloud/internal/audit"
|
||||
"data-terminal-cloud/internal/database"
|
||||
"data-terminal-cloud/internal/outbox"
|
||||
"data-terminal-cloud/internal/providermemory"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
@@ -24,10 +25,11 @@ type deletionObjectStore interface {
|
||||
}
|
||||
|
||||
type DeleteProcessor struct {
|
||||
pool *pgxpool.Pool
|
||||
redis *redis.Client
|
||||
store deletionObjectStore
|
||||
audit *audit.Repository
|
||||
pool *pgxpool.Pool
|
||||
redis *redis.Client
|
||||
store deletionObjectStore
|
||||
audit *audit.Repository
|
||||
providerMemory providermemory.Client
|
||||
}
|
||||
|
||||
type deletionRequest struct {
|
||||
@@ -38,11 +40,18 @@ type deletionSubtask struct {
|
||||
id, subsystem, status string
|
||||
}
|
||||
|
||||
func NewDeleteProcessor(pool *pgxpool.Pool, redisClient *redis.Client, store deletionObjectStore) (*DeleteProcessor, error) {
|
||||
func NewDeleteProcessor(pool *pgxpool.Pool, redisClient *redis.Client, store deletionObjectStore, providerClients ...providermemory.Client) (*DeleteProcessor, error) {
|
||||
if pool == nil || redisClient == nil || store == nil {
|
||||
return nil, errors.New("database, redis and object store are required")
|
||||
}
|
||||
return &DeleteProcessor{pool: pool, redis: redisClient, store: store, audit: audit.NewRepository()}, nil
|
||||
if len(providerClients) > 1 {
|
||||
return nil, errors.New("at most one provider memory client is allowed")
|
||||
}
|
||||
var providerClient providermemory.Client
|
||||
if len(providerClients) == 1 {
|
||||
providerClient = providerClients[0]
|
||||
}
|
||||
return &DeleteProcessor{pool: pool, redis: redisClient, store: store, audit: audit.NewRepository(), providerMemory: providerClient}, nil
|
||||
}
|
||||
|
||||
func (p *DeleteProcessor) Handle(ctx context.Context, event outbox.Event) error {
|
||||
@@ -196,15 +205,37 @@ func (p *DeleteProcessor) executeSubtask(ctx context.Context, request deletionRe
|
||||
_, 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)
|
||||
return "COMPLETED", "", err
|
||||
case "provider_index":
|
||||
var unresolved int
|
||||
if err := p.pool.QueryRow(ctx, `SELECT count(*) 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'`, request.tenantID, request.subjectID).Scan(&unresolved); err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if unresolved > 0 {
|
||||
type providerRef struct{ versionID, value string }
|
||||
refs := []providerRef{}
|
||||
for rows.Next() {
|
||||
var ref providerRef
|
||||
if err := rows.Scan(&ref.versionID, &ref.value); err != nil {
|
||||
rows.Close()
|
||||
return "", "", err
|
||||
}
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
rows.Close()
|
||||
if err := rows.Err(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if len(refs) > 0 && p.providerMemory == nil {
|
||||
return "", "", errors.New("external provider index references exist but no deletion adapter is configured")
|
||||
}
|
||||
_, err := p.pool.Exec(ctx, `UPDATE memory_index_entry SET embedding=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)
|
||||
return "SKIPPED", "no external provider index references were present", err
|
||||
for _, ref := range refs {
|
||||
if err := p.providerMemory.Delete(ctx, ref.value, "privacy-provider-memory-delete:"+request.id+":"+ref.versionID); err != nil {
|
||||
return "", "", outbox.Retryable(err)
|
||||
}
|
||||
}
|
||||
_, 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)
|
||||
if len(refs) == 0 {
|
||||
return "SKIPPED", "no external provider index references were present", err
|
||||
}
|
||||
return "COMPLETED", "", err
|
||||
case "postgresql":
|
||||
return "COMPLETED", "", p.redactPostgreSQL(ctx, request)
|
||||
default:
|
||||
@@ -238,9 +269,13 @@ func (p *DeleteProcessor) redactPostgreSQL(ctx context.Context, request deletion
|
||||
`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',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 conversation_memory_use WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
|
||||
`DELETE FROM memory_tool_call WHERE tenant_id=$1::uuid AND subject_id=$2::uuid`,
|
||||
`UPDATE content_review_signal SET reason='[privacy-redacted]' 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`,
|
||||
|
||||
Reference in New Issue
Block a user