feat(gmw): moderation explainability + semantic message search

- Persist structured verdict (flags/severity/confidence/evidence) on
  moderation_actions so the public web can show WHY a message was moderated.
- Add a persistent Qdrant archive collection (gmw_message_archive); embed
  every captured message at capture time (fire-and-forget, best-effort).
- Public semantic search over the archive (backend oRPC + FE toggle on the
  messages view). Both features are read-only/public and fully automatic.

Migration: 0015_add_moderation_explainability.sql
This commit is contained in:
asepharyana
2026-08-18 15:11:01 +07:00
parent d68f6b653a
commit 1ae19074ee
25 changed files with 1189 additions and 7 deletions
+12 -1
View File
@@ -1,5 +1,9 @@
import { orpc } from "@/lib/orpc/client";
import type { AttachmentRecord, MessageRecord } from "@/lib/types";
import type {
AttachmentRecord,
MessageRecord,
SemanticSearchResult,
} from "@/lib/types";
export const messagesApi = {
list: (
@@ -62,4 +66,11 @@ export const messagesApi = {
orpc.analysis.search({ q, limit }) as unknown as Promise<{
results: MessageRecord[];
}>,
// Public semantic search over the persistent message archive (Qdrant).
semanticSearch: (query: string, limit?: number) =>
orpc.messages.semanticSearch({ query, limit }) as unknown as Promise<{
results: SemanticSearchResult[];
nextCursor: null;
}>,
};
@@ -163,3 +163,17 @@ export interface AttachmentRecord {
created_at: number;
uploaded_at?: number | null;
}
// ── Semantic Search (read-only public archive search) ──────────
export interface SemanticSearchResult {
message_id: string | null;
content: string;
score: number;
created_at: number;
}
export interface SemanticSearchResponse {
results: SemanticSearchResult[];
nextCursor: null;
}
@@ -21,6 +21,14 @@ export interface ModerationAction {
executed_at: number | null;
username: string | null;
content: string | null;
// ── Explainability (structured verdict, surfaced read-only to public web) ──
flags: string[] | null;
categories: string[] | null;
severity: "none" | "low" | "medium" | "high" | "critical" | null;
confidence: number | null;
score: number | null;
evidence: string[] | null;
policy_version: string | null;
}
export interface ModerationStats {