rows,err:=s.pool.Query(ctx,`SELECT public_id::text,summary_type,content,source_turn_ids,model_ref,prompt_version,generation_version,status,created_at FROM conversation_summary WHERE tenant_id=$1::uuid AND conversation_id=$2::uuid ORDER BY generation_version DESC,summary_type`,access.tenantID,conversationID)
iferr:=tx.QueryRow(ctx,`SELECT c.status,(SELECT COALESCE(MAX(sequence_no),0) FROM conversation_turn ct WHERE ct.conversation_id=c.id AND ct.tenant_id=c.tenant_id) FROM conversation c WHERE c.id=$1::uuid`,conversationID).Scan(&status,&maxSequence);err!=nil{
iferr:=tx.QueryRow(ctx,`SELECT GREATEST($2::bigint,COALESCE(MAX(generation_version),0))+1 FROM conversation_summary WHERE tenant_id=$1::uuid AND conversation_id=$3::uuid`,access.tenantID,maxSequence,conversationID).Scan(&generationVersion);err!=nil{
rows,err:=s.pool.Query(ctx,storySelectSQL+` WHERE sd.tenant_id=$1::uuid AND sd.subject_id=$2::uuid AND (NULLIF($3,'') IS NULL OR sd.status=$3) ORDER BY sd.created_at DESC,sd.public_id`,access.tenantID,access.subjectID,status)
if_,err:=tx.Exec(ctx,`INSERT INTO fact_candidate_revision(fact_candidate_id,version_no,fact_type,content,sensitivity,status,edited_by,edit_reason)
VALUES($1::uuid,$2,$3,$4,$5,$6,$7::uuid,$8) ON CONFLICT(fact_candidate_id,version_no) DO NOTHING`,internalID,version,previousFactType,previousContent,previousSensitivity,previousStatus,access.userID,input.Reason);err!=nil{
returnerr
}
row:=tx.QueryRow(ctx,`
UPDATE fact_candidate SET fact_type=$1,content=$2,sensitivity=$3,status=$4,
if_,err:=tx.Exec(ctx,`INSERT INTO fact_candidate_revision(fact_candidate_id,version_no,fact_type,content,sensitivity,status,edited_by,edit_reason,error_type)
if_,err:=tx.Exec(ctx,`INSERT INTO content_review_signal(tenant_id,subject_id,resource_type,resource_public_id,action,error_type,reason,model_ref,prompt_version,generation_version,actor_id)
FROM story_draft sd JOIN story_revision sr ON sr.story_id=sd.id AND sr.revision_no=1 WHERE sd.public_id=$6::uuid`,factPublicID,action,input.ErrorType,input.Reason,access.userID,storyPublicID);err!=nil{
iferr:=tx.QueryRow(ctx,`INSERT INTO story_revision(story_id,revision_no,content,author_type,author_id,edit_reason,base_revision_id,error_type) SELECT id,$2,$3,'user',$4::uuid,$5,$6::uuid,NULLIF($7,'') FROM story_draft WHERE public_id=$1::uuid RETURNING id::text`,storyPublicID,current.RevisionNo+1,content,access.userID,reason,currentRevisionID,errorType).Scan(&newRevisionID);err!=nil{
if_,err:=tx.Exec(ctx,`INSERT INTO story_source(story_revision_id,conversation_turn_id,source_start_offset,source_end_offset,source_role) SELECT $1::uuid,conversation_turn_id,source_start_offset,source_end_offset,source_role FROM story_source WHERE story_revision_id=$2::uuid`,newRevisionID,currentRevisionID);err!=nil{
if_,err:=tx.Exec(ctx,`UPDATE fact_candidate SET status='REJECTED',reviewed_by=$1::uuid,reviewed_at=now(),review_reason=$2,version=version+1,updated_at=now() WHERE story_id=(SELECT id FROM story_draft WHERE public_id=$3::uuid) AND status='CANDIDATE'`,access.userID,reason,storyPublicID);err!=nil{
iferr:=tx.QueryRow(ctx,`SELECT count(*) FROM story_source WHERE story_revision_id=(SELECT current_revision_id FROM story_draft WHERE public_id=$1::uuid)`,storyPublicID).Scan(&sourceCount);err!=nil{
returnerr
}
ifsourceCount==0{
returnErrStorySourceRequired
}
varconsentbool
iferr:=tx.QueryRow(ctx,`SELECT EXISTS(SELECT 1 FROM consent_record WHERE tenant_id=$1::uuid AND subject_id=$2::uuid AND consent_type='personalized_memory' AND withdrawn_at IS NULL AND (expires_at IS NULL OR expires_at>now()))`,access.tenantID,access.subjectID).Scan(&consent);err!=nil{
iferr:=tx.QueryRow(ctx,`INSERT INTO curated_memory(tenant_id,subject_id,status,sensitivity,valid_from) VALUES($1::uuid,$2::uuid,'ACTIVE',$3,now()) RETURNING id::text,public_id::text`,access.tenantID,access.subjectID,story.Sensitivity).Scan(&memoryID,&memory.ID);err!=nil{
returnfmt.Errorf("create curated memory: %w",err)
}
iferr:=tx.QueryRow(ctx,`INSERT INTO memory_version(memory_id,version_no,content,source_refs,confirmed_by,change_reason,content_digest) VALUES($1::uuid,1,$2,$3::jsonb,$4::uuid,$5,$6) RETURNING id::text`,memoryID,story.Content,sourceJSON,access.userID,reason,hex.EncodeToString(digest[:])).Scan(&memoryVersionID);err!=nil{
returnfmt.Errorf("create memory version: %w",err)
}
if_,err:=tx.Exec(ctx,`UPDATE curated_memory SET current_version_id=$1::uuid WHERE id=$2::uuid`,memoryVersionID,memoryID);err!=nil{
returnerr
}
if_,err:=tx.Exec(ctx,`INSERT INTO memory_index_entry(tenant_id,subject_id,memory_id,memory_version_id,index_type,status) VALUES($1::uuid,$2::uuid,$3::uuid,$4::uuid,'pgvector','PENDING')`,access.tenantID,access.subjectID,memoryID,memoryVersionID);err!=nil{
if_,err:=tx.Exec(ctx,`INSERT INTO memory_index_entry(tenant_id,subject_id,memory_id,memory_version_id,index_type,status) VALUES($1::uuid,$2::uuid,$3::uuid,$4::uuid,'provider','PENDING')`,access.tenantID,access.subjectID,memoryID,memoryVersionID);err!=nil{
if_,err:=tx.Exec(ctx,`UPDATE fact_candidate SET status='CONFIRMED',reviewed_by=$1::uuid,reviewed_at=now(),review_reason=$2,version=version+1,updated_at=now() WHERE story_id=(SELECT id FROM story_draft WHERE public_id=$3::uuid) AND status='CANDIDATE'`,access.userID,reason,storyPublicID);err!=nil{
rows,err:=s.pool.Query(ctx,memorySelectSQL+` WHERE cm.tenant_id=$1::uuid AND cm.subject_id=$2::uuid AND cm.status<>'DELETED' AND (NULLIF($3,'') IS NULL OR strpos(lower(mv.content),lower($3))>0) ORDER BY cm.updated_at DESC,cm.public_id`,access.tenantID,access.subjectID,strings.TrimSpace(query))
result,err:=scanMemory(s.pool.QueryRow(ctx,memorySelectSQL+` JOIN household h ON h.id=sp.household_id AND h.tenant_id=sp.tenant_id JOIN household_member hm ON hm.household_id=h.id AND hm.status='active' JOIN user_account member_account ON member_account.id=hm.user_id AND member_account.tenant_id=h.tenant_id AND member_account.status='active' WHERE cm.public_id=$1::uuid AND sp.status IN ('active','suspended') AND member_account.oidc_issuer=$2 AND member_account.oidc_subject=$3`,memoryPublicID,principal.Issuer,principal.Subject))
iferr:=tx.QueryRow(ctx,`SELECT EXISTS(SELECT 1 FROM consent_record WHERE tenant_id=$1::uuid AND subject_id=$2::uuid AND consent_type='personalized_memory' AND withdrawn_at IS NULL AND (expires_at IS NULL OR expires_at>now()))`,tenantID,subjectID).Scan(&consent);err!=nil{
if_,err:=tx.Exec(ctx,`UPDATE memory_index_entry SET embedding=NULL,status='STALE',last_error='memory version or status changed',updated_at=now() WHERE memory_id=$1::uuid AND status IN ('PENDING','ACTIVE')`,internalID);err!=nil{
returnerr
}
iftargetStatus=="ACTIVE"{
ifnewVersionID==currentVersionID{
if_,err:=tx.Exec(ctx,`UPDATE memory_index_entry SET status='PENDING',last_error=NULL,updated_at=now() WHERE memory_version_id=$1::uuid AND index_type='pgvector'`,newVersionID);err!=nil{
returnerr
}
}elseif_,err:=tx.Exec(ctx,`INSERT INTO memory_index_entry(tenant_id,subject_id,memory_id,memory_version_id,index_type,status) VALUES($1::uuid,$2::uuid,$3::uuid,$4::uuid,'pgvector','PENDING')`,tenantID,subjectID,internalID,newVersionID);err!=nil{
ON CONFLICT(memory_version_id,index_type) DO UPDATE SET status='PENDING',last_error=NULL,updated_at=now()`,tenantID,subjectID,internalID,newVersionID);err!=nil{
if_,err:=tx.Exec(ctx,`UPDATE curated_memory SET current_version_id=$1::uuid,status=$2,version=version+1,updated_at=now() WHERE id=$3::uuid`,newVersionID,targetStatus,internalID);err!=nil{
if_,err:=tx.Exec(ctx,`INSERT INTO content_review_signal(tenant_id,subject_id,resource_type,resource_public_id,action,error_type,reason,model_ref,prompt_version,generation_version,actor_id)
if_,err:=tx.Exec(ctx,`UPDATE curated_memory SET status='PENDING_DELETE',valid_to=COALESCE(valid_to,now()),version=version+1,updated_at=now() WHERE id=$1::uuid`,internalID);err!=nil{
returnerr
}
if_,err:=tx.Exec(ctx,`UPDATE memory_index_entry SET embedding=NULL,status='STALE',last_error='memory deletion requested',updated_at=now() WHERE memory_id=$1::uuid AND status<>'DELETED'`,internalID);err!=nil{
_,err:=tx.Exec(ctx,`INSERT INTO content_review_signal(tenant_id,subject_id,resource_type,resource_public_id,action,error_type,reason,model_ref,prompt_version,generation_version,actor_id)
FROM story_draft sd JOIN story_revision sr ON sr.story_id=sd.id AND sr.revision_no=1 WHERE sd.public_id=$1::uuid`,storyPublicID,action,errorType,reason,access.userID)
sql:=`SELECT c.tenant_id::text,c.subject_id::text,ua.id::text,ua.public_id::text,hm.role,c.id::text FROM conversation c JOIN subject_profile sp ON sp.id=c.subject_id AND sp.tenant_id=c.tenant_id AND sp.status IN ('active','suspended') JOIN household h ON h.id=c.household_id AND h.tenant_id=c.tenant_id JOIN household_member hm ON hm.household_id=h.id AND hm.status='active' JOIN user_account ua ON ua.id=hm.user_id AND ua.tenant_id=h.tenant_id AND ua.status='active' WHERE c.public_id=$1::uuid AND ua.oidc_issuer=$2 AND ua.oidc_subject=$3`
sql:=storySelectSQL+` JOIN household h ON h.id=sp.household_id AND h.tenant_id=sp.tenant_id JOIN household_member hm ON hm.household_id=h.id AND hm.status='active' JOIN user_account member_account ON member_account.id=hm.user_id AND member_account.tenant_id=h.tenant_id AND member_account.status='active' WHERE sd.public_id=$1::uuid AND sp.status IN ('active','suspended') AND member_account.oidc_issuer=$2 AND member_account.oidc_subject=$3`
row:=q.QueryRow(ctx,strings.Replace(sql,`sd.subject_id::text AS subject_internal_id`,`sd.subject_id::text AS subject_internal_id,member_account.id::text,member_account.public_id::text,hm.role`,1),publicID,p.Issuer,p.Subject)
err:=row.Scan(targets...)
iferrors.Is(err,pgx.ErrNoRows){
returncontentAccess{},Story{},ErrAccessDenied
}
iferr!=nil{
returncontentAccess{},Story{},err
}
a.tenantID=tenantID
a.subjectID=subjectInternal
a.role=identity.Role(role)
if!identity.Can(a.role,cap){
returncontentAccess{},Story{},ErrAccessDenied
}
returna,story,nil
}
conststorySelectSQL=`SELECT sd.public_id::text AS story_public_id,sp.public_id::text AS subject_public_id,c.public_id::text AS conversation_public_id,sd.title,sr.content,sd.status,sd.confidence,sd.sensitivity,sd.generation_version,sr.revision_no,sr.author_type,COALESCE(sd.review_reason,''),sd.version,sd.created_at,sd.updated_at,sd.tenant_id::text AS tenant_internal_id,sd.subject_id::text AS subject_internal_id FROM story_draft sd JOIN subject_profile sp ON sp.id=sd.subject_id AND sp.tenant_id=sd.tenant_id JOIN conversation c ON c.id=sd.conversation_id AND c.tenant_id=sd.tenant_id JOIN story_revision sr ON sr.id=sd.current_revision_id`
conststoryStatusReturningSQL=`UPDATE story_draft sd SET status=$1,reviewed_by=$2::uuid,reviewed_at=now(),review_reason=$3,version=sd.version+1,updated_at=now() FROM subject_profile sp,conversation c,story_revision sr WHERE sd.public_id=$4::uuid AND sp.id=sd.subject_id AND c.id=sd.conversation_id AND sr.id=sd.current_revision_id RETURNING sd.public_id::text,sp.public_id::text,c.public_id::text,sd.title,sr.content,sd.status,sd.confidence,sd.sensitivity,sd.generation_version,sr.revision_no,sr.author_type,COALESCE(sd.review_reason,''),sd.version,sd.created_at,sd.updated_at,sd.tenant_id::text,sd.subject_id::text`
conststoryUpdateReturningSQL=`UPDATE story_draft sd SET status=$1,current_revision_id=$2::uuid,reviewed_by=$3::uuid,reviewed_at=now(),review_reason=$4,version=sd.version+1,updated_at=now() FROM subject_profile sp,conversation c,story_revision sr WHERE sd.public_id=$5::uuid AND sp.id=sd.subject_id AND c.id=sd.conversation_id AND sr.id=$2::uuid RETURNING sd.public_id::text,sp.public_id::text,c.public_id::text,sd.title,sr.content,sd.status,sd.confidence,sd.sensitivity,sd.generation_version,sr.revision_no,sr.author_type,COALESCE(sd.review_reason,''),sd.version,sd.created_at,sd.updated_at,sd.tenant_id::text,sd.subject_id::text`
rows,err:=s.pool.Query(ctx,`SELECT ct.public_id::text,ct.sequence_no,ct.speaker,ct.final_text,ss.source_start_offset,ss.source_end_offset FROM story_draft sd JOIN story_source ss ON ss.story_revision_id=sd.current_revision_id JOIN conversation_turn ct ON ct.id=ss.conversation_turn_id WHERE sd.public_id=$1::uuid ORDER BY ct.sequence_no`,story.ID)
rows,err=s.pool.Query(ctx,`SELECT fc.public_id::text,fc.fact_type,fc.content,fc.statement_basis,fc.confidence,fc.sensitivity,fc.source_turn_ids,fc.status,fc.version FROM fact_candidate fc JOIN story_draft sd ON sd.id=fc.story_id WHERE sd.public_id=$1::uuid ORDER BY fc.created_at,fc.public_id`,story.ID)
constmemorySelectSQL=`SELECT cm.public_id::text,sp.public_id::text,mv.content,mv.source_refs,cm.status,cm.sensitivity,mv.version_no,cm.version,ua.public_id::text,mv.confirmed_at,COALESCE(mv.change_reason,''),COALESCE(mi.status,'PENDING'),COALESCE(mi.last_error,''),COALESCE(provider_mi.status,'DISABLED'),COALESCE(provider_mi.last_error,''),cm.created_at,cm.updated_at FROM curated_memory cm JOIN subject_profile sp ON sp.id=cm.subject_id AND sp.tenant_id=cm.tenant_id JOIN memory_version mv ON mv.id=cm.current_version_id JOIN user_account ua ON ua.id=mv.confirmed_by LEFT JOIN memory_index_entry mi ON mi.memory_version_id=mv.id AND mi.index_type='pgvector' LEFT JOIN memory_index_entry provider_mi ON provider_mi.memory_version_id=mv.id AND provider_mi.index_type='provider'`