feat(campaigns): add governed collaboration thread
Module Package Release / publish-packages (push) Successful in 13s

This commit is contained in:
2026-08-22 00:09:34 +02:00
parent 5a21067e44
commit c2f083e5f6
25 changed files with 2315 additions and 24 deletions
@@ -10,6 +10,7 @@ from sqlalchemy.orm import Session
from govoplan_campaign.backend.db.models import (
AttachmentInstance,
Campaign,
CampaignCollaborationEntry,
CampaignIssue,
CampaignJob,
CampaignMessageAction,
@@ -646,6 +647,62 @@ class CampaignDsarProvider:
version_ids: set[str],
) -> None:
if subject_user_id is not None:
collaboration_rows = _bounded_rows(
db.query(CampaignCollaborationEntry)
.filter(
CampaignCollaborationEntry.tenant_id == tenant_id,
or_(
CampaignCollaborationEntry.actor_user_id == subject_user_id,
CampaignCollaborationEntry.withdrawn_by_user_id == subject_user_id,
CampaignCollaborationEntry.redacted_by_user_id == subject_user_id,
cast(CampaignCollaborationEntry.mention_user_ids, Text).contains(
f'"{subject_user_id}"'
),
),
)
.order_by(CampaignCollaborationEntry.id)
)
for entry in collaboration_rows:
match_fields = _matching_fields(
entry,
subject_user_id,
(
"actor_user_id",
"withdrawn_by_user_id",
"redacted_by_user_id",
),
)
if subject_user_id in (entry.mention_user_ids or []):
match_fields.append("mention_user_ids")
authored = entry.actor_user_id == subject_user_id
append( # type: ignore[operator]
_record(
"campaign_collaboration_entry",
entry.id,
"campaign_collaboration_evidence",
"Campaign collaboration entry",
{
"match_fields": match_fields,
"campaign_id": entry.campaign_id,
"campaign_version_id": entry.campaign_version_id,
"visibility": entry.visibility,
"reference_kind": entry.reference_kind,
"reference_id": entry.reference_id,
"posted_text": entry.content if authored else None,
"content_disclosed": authored and entry.content is not None,
"content_sha256": entry.content_sha256,
"withdrawn_at": _iso(entry.withdrawn_at),
"redacted_at": _iso(entry.redacted_at),
},
observed_at=entry.updated_at,
immutable=True,
retention_reason=(
"Append-only discussion identity, reference, hash, tombstone, and moderation evidence are retained with Campaign access and Audit evidence."
),
source_path=f"/campaigns/{entry.campaign_id}/activity",
)
)
shares = _bounded_rows(
db.query(CampaignShare)
.filter(