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
+54
View File
@@ -16,6 +16,13 @@ import type {
CreateArticleRequest,
CreateKolPackageRequest,
CreateKolPromptRequest,
CreateAckRequest,
ComplianceAckRecord,
ComplianceCheckArticleRequest,
ComplianceCheckPlainRequest,
ComplianceCheckResult,
ComplianceManualReview,
ComplianceRuntimeStatus,
CreatePluginSessionRequest,
CreatePluginSessionResponse,
CreatePublishBatchRequest,
@@ -1081,6 +1088,53 @@ export const publishJobsApi = {
},
}
export const complianceApi = {
runtimeStatus() {
return apiClient.get<ComplianceRuntimeStatus>('/api/tenant/compliance/runtime-status')
},
checkPlain(payload: ComplianceCheckPlainRequest) {
return apiClient.post<ComplianceCheckResult, ComplianceCheckPlainRequest>(
'/api/tenant/compliance/check',
payload,
)
},
checkArticle(articleId: number, payload: ComplianceCheckArticleRequest) {
return apiClient.post<ComplianceCheckResult, ComplianceCheckArticleRequest>(
`/api/tenant/articles/${articleId}/compliance/check`,
payload,
)
},
latestArticleCheck(articleId: number) {
return apiClient.get<ComplianceCheckResult>(`/api/tenant/articles/${articleId}/compliance/latest`)
},
createAck(recordId: number, payload: CreateAckRequest) {
return apiClient.post<ComplianceAckRecord, CreateAckRequest>(
`/api/tenant/compliance/check-records/${recordId}/ack`,
payload,
)
},
submitManualReview(articleId: number, payload: { check_record_id: number; note: string }) {
return apiClient.post<ComplianceManualReview, typeof payload>(
`/api/tenant/articles/${articleId}/compliance/manual-reviews`,
payload,
)
},
latestManualReview(articleId: number, articleVersionId?: number | null) {
return apiClient.get<ComplianceManualReview>(
`/api/tenant/articles/${articleId}/compliance/manual-reviews/latest`,
{
params: articleVersionId ? { article_version_id: articleVersionId } : undefined,
},
)
},
cancelManualReview(articleId: number, reviewId: number, payload: { reason?: string }) {
return apiClient.post<ComplianceManualReview, typeof payload>(
`/api/tenant/articles/${articleId}/compliance/manual-reviews/${reviewId}/cancel`,
payload,
)
},
}
function removedMediaApi<T>(action: string): Promise<T> {
return Promise.reject(
new ApiClientError({