fix(ai): pertahankan status warn di cache moderasi + bersihkan prompt stale

- normalizeStoredStatus(): exact-hash & semantic (Qdrant/PG) cache reader
  sebelumnya menipiskan 'warn' jadi 'flagged'/'clean' (type narrowing
  legacy clean|flagged) — merusak gating auto-delete & label dashboard.
  Kini status tersimpan dipertahankan penuh (clean/warn/flagged).
- prompts: hapus referensi <user_history> yang tak pernah di-inject,
  SearXNG -> Wikipedia (sudah migrasi), referensi section yang tak ada,
  typo 'secifik', dan baris list rusak '|-'.
- moderationBuilders: buang dead code buildUserProfilesBlock/
  buildUserProfileRef/UserProfileEntry/buildUserHistoryXml (tanpa caller
  produksi sejak context minimization) + test-nya.
- test baru: tests/storedStatusNormalization.test.ts (regresi warn).
This commit is contained in:
asepharyana
2026-08-22 16:17:55 +07:00
parent 4ffc99b3fe
commit 1397380fe9
7 changed files with 115 additions and 217 deletions
@@ -1,11 +1,10 @@
// ═══════════════════════════════════════════════════════════════════════════
// Context enrichment builders — <user_history>, <user_profiles> as_of,
// bot/edited detection (pure, no DB)
// Context enrichment builders — bot/edited detection (pure, no DB)
// (buildUserHistoryXml / buildUserProfilesBlock were removed with the
// per-user context minimization; their tests went with them.)
// ═══════════════════════════════════════════════════════════════════════════
import { describe, expect, it } from "vitest";
import {
buildUserHistoryXml,
buildUserProfilesBlock,
resolveIsBot,
resolveIsEdited,
} from "../src/modules/ai-moderation/moderationBuilders.js";
@@ -39,85 +38,6 @@ function msg(overrides: Partial<MessageRecord> = {}): MessageRecord {
};
}
const DAY_MS = 24 * 60 * 60 * 1000;
describe("buildUserHistoryXml — last flagged messages for repeat offenders", () => {
it("returns empty when there is no real history", () => {
expect(buildUserHistoryXml([])).toBe("");
expect(
buildUserHistoryXml([{ content: " ", severity: "low", created_at: 1 }]),
).toBe("");
});
it("renders <infraction> rows with severity and recency", () => {
const xml = buildUserHistoryXml(
[
{
content: "beli barang murah disini https://scam.example",
severity: "high",
created_at: NOW - 3 * DAY_MS,
},
],
NOW,
);
expect(xml).toContain("<user_history>");
expect(xml).toContain('severity="high"');
expect(xml).toContain('time_ago_days="3"');
expect(xml).toContain("beli barang murah disini");
});
it("caps long snippets and XML-escapes content", () => {
const xml = buildUserHistoryXml(
[
{
content: "x".repeat(300),
severity: "low",
created_at: NOW - DAY_MS,
},
],
NOW,
);
expect(xml.length).toBeLessThan(250);
});
});
describe("buildUserProfilesBlock — deduplicated map with staleness", () => {
it("emits as_of when the profile has a last-generated timestamp", () => {
const block = buildUserProfilesBlock(
new Map([
[
"u1",
{
text: "Developer teknis, bahasa Indonesia",
asOf: NOW - 3 * DAY_MS,
},
],
]),
);
expect(block).toContain('<user_profile user_id="u1"');
expect(block).toContain(
`as_of="${new Date(NOW - 3 * DAY_MS).toISOString()}"`,
);
expect(block).toContain("Developer teknis");
});
it("omits as_of when absent, and drops empty profiles", () => {
const block = buildUserProfilesBlock(
new Map([
["u1", { text: "profil aktif", asOf: null }],
["u2", { text: " " }],
]),
);
expect(block).toContain('user_id="u1"');
expect(block).not.toContain("as_of");
expect(block).not.toContain("u2");
});
it("returns empty for no profiles", () => {
expect(buildUserProfilesBlock(new Map())).toBe("");
});
});
describe("resolveIsBot / resolveIsEdited — message flags", () => {
it("reads author.bot from captured metadata", () => {
const bot = msg({