feat(compliance): add content compliance detection across tenant, ops, and clients

Implements the Revision 8/9 compliance design: tenant + ops backends, ops-web
content-safety pages, admin-web editor/publish gate integration, desktop publish
block surfacing, and supporting migrations / shared types / config plumbing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 20:48:14 +08:00
parent 81577b6154
commit 745cdd79cf
73 changed files with 12747 additions and 1892 deletions
@@ -211,7 +211,9 @@ INSERT INTO desktop_publish_jobs (
created_by_user_id,
title,
content_ref,
scheduled_at
scheduled_at,
article_id,
article_version_id
)
VALUES (
$1,
@@ -220,19 +222,23 @@ VALUES (
$4,
$5,
$6,
$7
$7,
$8,
$9
)
RETURNING id, desktop_id, tenant_id, workspace_id, created_by_user_id, title, content_ref, scheduled_at, created_at, updated_at
RETURNING id, desktop_id, tenant_id, workspace_id, created_by_user_id, title, content_ref, scheduled_at, created_at, updated_at, article_id, article_version_id, status, compliance_blocked_record_id, compliance_blocked_at, compliance_blocked_reason
`
type CreateDesktopPublishJobParams struct {
DesktopID pgtype.UUID `json:"desktop_id"`
TenantID int64 `json:"tenant_id"`
WorkspaceID int64 `json:"workspace_id"`
CreatedByUserID int64 `json:"created_by_user_id"`
Title string `json:"title"`
ContentRef []byte `json:"content_ref"`
ScheduledAt pgtype.Timestamptz `json:"scheduled_at"`
DesktopID pgtype.UUID `json:"desktop_id"`
TenantID int64 `json:"tenant_id"`
WorkspaceID int64 `json:"workspace_id"`
CreatedByUserID int64 `json:"created_by_user_id"`
Title string `json:"title"`
ContentRef []byte `json:"content_ref"`
ScheduledAt pgtype.Timestamptz `json:"scheduled_at"`
ArticleID pgtype.Int8 `json:"article_id"`
ArticleVersionID pgtype.Int8 `json:"article_version_id"`
}
func (q *Queries) CreateDesktopPublishJob(ctx context.Context, arg CreateDesktopPublishJobParams) (DesktopPublishJob, error) {
@@ -244,6 +250,8 @@ func (q *Queries) CreateDesktopPublishJob(ctx context.Context, arg CreateDesktop
arg.Title,
arg.ContentRef,
arg.ScheduledAt,
arg.ArticleID,
arg.ArticleVersionID,
)
var i DesktopPublishJob
err := row.Scan(
@@ -257,6 +265,12 @@ func (q *Queries) CreateDesktopPublishJob(ctx context.Context, arg CreateDesktop
&i.ScheduledAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArticleID,
&i.ArticleVersionID,
&i.Status,
&i.ComplianceBlockedRecordID,
&i.ComplianceBlockedAt,
&i.ComplianceBlockedReason,
)
return i, err
}
@@ -544,12 +558,18 @@ const leaseNextQueuedDesktopTask = `-- name: LeaseNextQueuedDesktopTask :one
WITH candidate AS (
SELECT dt.desktop_id
FROM desktop_tasks AS dt
LEFT JOIN desktop_publish_jobs AS j ON j.desktop_id = dt.job_id
WHERE dt.target_client_id = $3
AND dt.status = 'queued'
AND (
$4::text IS NULL
OR dt.kind = $4::text
)
AND (
dt.kind <> 'publish'
OR j.desktop_id IS NULL
OR j.status = 'queued'
)
ORDER BY dt.lane_weight DESC,
dt.priority DESC,
COALESCE(dt.enqueued_at, dt.created_at) ASC,
@@ -87,6 +87,12 @@ type ArticleVersion struct {
WordCount int32 `json:"word_count"`
SourceLabel pgtype.Text `json:"source_label"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
// Plaintext extracted at save time, used by compliance gate to skip diff reconstruction.
PlaintextSnapshot pgtype.Text `json:"plaintext_snapshot"`
// Hot-path manual review stamp: none|pending|approved|rejected.
ManualReviewStatus string `json:"manual_review_status"`
ManualReviewID pgtype.Int8 `json:"manual_review_id"`
ManualReviewUpdatedAt pgtype.Timestamptz `json:"manual_review_updated_at"`
}
type AuditLog struct {
@@ -153,6 +159,105 @@ type Competitor struct {
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
}
type ComplianceAckRecord struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
ArticleID int64 `json:"article_id"`
ArticleVersionID int64 `json:"article_version_id"`
CheckRecordID int64 `json:"check_record_id"`
ContentHash string `json:"content_hash"`
PolicyFingerprint string `json:"policy_fingerprint"`
AcknowledgedBy int64 `json:"acknowledged_by"`
AckViolationIds []int64 `json:"ack_violation_ids"`
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
ConsumedAt pgtype.Timestamptz `json:"consumed_at"`
ConsumedByJobID pgtype.UUID `json:"consumed_by_job_id"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type ComplianceCheckRecord struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
ArticleID pgtype.Int8 `json:"article_id"`
ArticleVersionID pgtype.Int8 `json:"article_version_id"`
TargetPlatforms []string `json:"target_platforms"`
ContentHash string `json:"content_hash"`
PolicyFingerprint string `json:"policy_fingerprint"`
TriggerSource string `json:"trigger_source"`
EnforcementMode string `json:"enforcement_mode"`
HighestLevel pgtype.Text `json:"highest_level"`
Passed bool `json:"passed"`
HitCount int32 `json:"hit_count"`
StoredHitCount int32 `json:"stored_hit_count"`
Truncated bool `json:"truncated"`
DictVersions []byte `json:"dict_versions"`
LlmUsed bool `json:"llm_used"`
DurationMs int32 `json:"duration_ms"`
CheckedBy pgtype.Int8 `json:"checked_by"`
CheckedAt pgtype.Timestamptz `json:"checked_at"`
ManualReviewStatus string `json:"manual_review_status"`
ManualReviewID pgtype.Int8 `json:"manual_review_id"`
}
type ComplianceManualReview struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
ArticleID int64 `json:"article_id"`
ArticleVersionID int64 `json:"article_version_id"`
OriginalCheckRecordID pgtype.Int8 `json:"original_check_record_id"`
OriginalContentHash string `json:"original_content_hash"`
OriginalPolicyFingerprint string `json:"original_policy_fingerprint"`
OriginalTargetPlatforms []string `json:"original_target_platforms"`
OriginalViolationSummary []byte `json:"original_violation_summary"`
RequestedBy int64 `json:"requested_by"`
RequestedAt pgtype.Timestamptz `json:"requested_at"`
RequestNote pgtype.Text `json:"request_note"`
Status string `json:"status"`
CancelledBy pgtype.Int8 `json:"cancelled_by"`
CancelledAt pgtype.Timestamptz `json:"cancelled_at"`
CancelReason pgtype.Text `json:"cancel_reason"`
ReviewedBy pgtype.Int8 `json:"reviewed_by"`
ReviewedAt pgtype.Timestamptz `json:"reviewed_at"`
DecisionReason pgtype.Text `json:"decision_reason"`
}
type ComplianceReviewJob struct {
ID int64 `json:"id"`
MessageID pgtype.UUID `json:"message_id"`
TenantID int64 `json:"tenant_id"`
ArticleID int64 `json:"article_id"`
ArticleVersionID int64 `json:"article_version_id"`
CheckRecordID int64 `json:"check_record_id"`
Status string `json:"status"`
Attempts int32 `json:"attempts"`
AvailableAt pgtype.Timestamptz `json:"available_at"`
LockedAt pgtype.Timestamptz `json:"locked_at"`
LockedBy pgtype.Text `json:"locked_by"`
LastError pgtype.Text `json:"last_error"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type ComplianceViolation struct {
ID int64 `json:"id"`
RecordID int64 `json:"record_id"`
TenantID int64 `json:"tenant_id"`
PlatformCode pgtype.Text `json:"platform_code"`
Source string `json:"source"`
DictionaryCode pgtype.Text `json:"dictionary_code"`
DictionaryName pgtype.Text `json:"dictionary_name"`
TermPattern pgtype.Text `json:"term_pattern"`
MatchedText string `json:"matched_text"`
StartOffset int32 `json:"start_offset"`
EndOffset int32 `json:"end_offset"`
DomPath pgtype.Text `json:"dom_path"`
Level string `json:"level"`
Hint pgtype.Text `json:"hint"`
ReferenceLaw pgtype.Text `json:"reference_law"`
AcknowledgedInAckID pgtype.Int8 `json:"acknowledged_in_ack_id"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type DesktopClient struct {
ID pgtype.UUID `json:"id"`
TenantID int64 `json:"tenant_id"`
@@ -180,16 +285,22 @@ type DesktopClientPrimaryLease struct {
}
type DesktopPublishJob struct {
ID int64 `json:"id"`
DesktopID pgtype.UUID `json:"desktop_id"`
TenantID int64 `json:"tenant_id"`
WorkspaceID int64 `json:"workspace_id"`
CreatedByUserID int64 `json:"created_by_user_id"`
Title string `json:"title"`
ContentRef []byte `json:"content_ref"`
ScheduledAt pgtype.Timestamptz `json:"scheduled_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
ID int64 `json:"id"`
DesktopID pgtype.UUID `json:"desktop_id"`
TenantID int64 `json:"tenant_id"`
WorkspaceID int64 `json:"workspace_id"`
CreatedByUserID int64 `json:"created_by_user_id"`
Title string `json:"title"`
ContentRef []byte `json:"content_ref"`
ScheduledAt pgtype.Timestamptz `json:"scheduled_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
ArticleID pgtype.Int8 `json:"article_id"`
ArticleVersionID pgtype.Int8 `json:"article_version_id"`
Status string `json:"status"`
ComplianceBlockedRecordID pgtype.Int8 `json:"compliance_blocked_record_id"`
ComplianceBlockedAt pgtype.Timestamptz `json:"compliance_blocked_at"`
ComplianceBlockedReason pgtype.Text `json:"compliance_blocked_reason"`
}
type DesktopTask struct {