feat(gateway): remove Jev (System One) analyzer, restore LLM-only text moderation

Jev (oc/jev-1.13-free via 9router /v1/systemone) added as primary text
analyzer was underperforming. Delete the whole feature:
- jevAnalyzer.ts + its unit & live-smoke tests
- Jev-first branch in textBatchProcessor, restore pure callModerationLLM path
- AI_LLM_JEV_* config vars (zod) and .env.example entries
- @typesafe-ai/sdk dependency (+ lockfile)

Behavior: text moderation is LLM-only again, exactly as before the
Jev feature; AGENTS.md invariant 'LLM is the only judge' holds.
This commit is contained in:
asepharyana
2026-09-24 12:06:36 +07:00
parent 8583bcdf17
commit c7f53e4f7e
8 changed files with 13 additions and 1158 deletions
@@ -1,94 +0,0 @@
/**
* jev-live-smoke.test.ts — LIVE smoke test for the Jev analyzer (real 9router).
*
* Gated behind `AI_LLM_JEV_SMOKE=1` (and the Jev env vars) so plain
* `pnpm test` / CI skips it. Verifies the ACTUAL question set + state
* builder + acceptance gate + mapper end-to-end against the real
* `analyzeBatchWithJev` module (no gateway restart needed).
*
* Run:
* AI_LLM_JEV_SMOKE=1 AI_LLM_JEV_API_KEY=sk-... npx vitest run tests/jev-live-smoke.test.ts
*/
import { describe, expect, it } from "vitest";
import { analyzeBatchWithJev } from "../src/modules/ai-moderation/jevAnalyzer.js";
import type { AnalysisResult } from "../src/modules/message-capture/types.js";
const SMOKE = process.env.AI_LLM_JEV_SMOKE === "1";
const SKIP_REASON =
"AI_LLM_JEV_SMOKE != 1 — set it + AI_LLM_JEV_API_KEY to run the live smoke against 9router";
describe("Jev live smoke (real 9router /v1/systemone)", () => {
it.runIf(SMOKE)(
"analyzes 4 mixed messages with the production question set",
async () => {
const targets = [
{
id: "m_vulgar",
user: "alerte",
content: "kontol lu semua pada goblok, dasar tolol",
},
{
id: "m_clean",
user: "devganteng",
content: "gimana hasil compile nya bang? error terus nih",
},
{
id: "m_sara",
user: "anon",
content: "agama lu palestina itu sampah, mending dibom habis",
},
{
id: "m_help",
user: "helper",
content:
"nggak papa bang, nanti gw bantu debug, coba pnpm install dulu",
},
];
const outcome = await analyzeBatchWithJev(
targets,
{
contextBlock:
"<location_context channel_id='42424242' channel_name='dev-chat'/>\n" +
"<conversation_context>\n[conversation_flow] status=sparse context_msgs=3 dropped=0\n" +
"[context] id='c1' time='2026-09-23T08:00:00Z' user='alice': lagi pada error compile nih\n" +
"</conversation_context>",
webSearchBlock: "",
glossaryBlock: "",
channelCulture: "channel santai developer coding",
},
undefined,
"",
);
expect(outcome.error).toBeNull();
expect(outcome.rejectedIds).toHaveLength(0);
const byId = Object.fromEntries(
outcome.results.map((r) => [r.messageId, r]),
);
// Vulgar attack → flagged, vulgar_language
expect(byId.m_vulgar?.status).toBe("flagged");
expect(byId.m_vulgar?.categories).toContain("vulgar_language");
// SARA religious slur → flagged (zero-tolerance rule)
expect(byId.m_sara?.status).toBe("flagged");
// Clean technical messages → clean
expect(byId.m_clean?.status).toBe("clean");
expect(byId.m_help?.status).toBe("clean");
// Verdicts are calibration-honest (typed by the real pipeline shape)
const verdicts = outcome.results as AnalysisResult[];
for (const r of verdicts) {
expect(r.confidence).toBeGreaterThanOrEqual(0.9);
if (r.status === "clean") expect(r.score).toBe(0);
expect(r.analysis).toContain("[Jev]");
expect(r.analysis).toContain("p_melanggar=");
}
},
SMOKE ? 60_000 : 0,
);
it.skipIf(!SMOKE)("skipped when AI_LLM_JEV_SMOKE is unset", () => {
expect(SKIP_REASON).toBeTruthy();
});
});
@@ -1,382 +0,0 @@
/**
* jevAnalyzer.test.ts — pure unit tests for the Jev analyzer (no network).
*
* Tests the question builder, state builder, acceptance gate, and answer
* mapper against hand-crafted `JevAnswers` objects (the shape `systemOne`
* returns). `analyzeBatchWithJev`'s network path is exercised separately
* by the live smoke harness (no gateway restart).
*/
import { describe, expect, it } from "vitest";
import {
buildJevQuestions,
buildJevState,
isJevAccepted,
JEV_CATEGORIES,
JEV_POLICY_VERSION,
type JevAnswers,
mapJevAnswersToResult,
} from "../src/modules/ai-moderation/jevAnalyzer.js";
const MID = "m1";
const cleanAnswers: JevAnswers = {
[`${MID}__v`]: { type: "noul", noul: 0.03 },
[`${MID}__status`]: { type: "choice", choice: "clean", confidence: 0.99 },
[`${MID}__severity`]: { type: "choice", choice: "none" },
[`${MID}__category`]: { type: "choice", choice: "none" },
[`${MID}__action`]: { type: "choice", choice: "none" },
};
const flaggedAnswers: JevAnswers = {
...cleanAnswers,
[`${MID}__v`]: { type: "noul", noul: 0.98 },
[`${MID}__status`]: { type: "choice", choice: "flagged", confidence: 0.99 },
[`${MID}__severity`]: { type: "choice", choice: "high" },
[`${MID}__category`]: { type: "choice", choice: "vulgar_language" },
[`${MID}__action`]: { type: "choice", choice: "delete" },
};
function answersFor(overrides: Partial<JevAnswers>): JevAnswers {
return { ...cleanAnswers, ...overrides };
}
describe("buildJevQuestions", () => {
it("emits 5 id-keyed questions per target", () => {
const q = buildJevQuestions([
{ id: "a", user: "alice", content: "hi" },
{ id: "b", user: "bob", content: "hey" },
]);
const keys = Object.keys(q);
expect(keys).toHaveLength(10);
expect(keys.sort()).toEqual(
[
"a__v",
"a__status",
"a__severity",
"a__category",
"a__action",
"b__v",
"b__status",
"b__severity",
"b__category",
"b__action",
].sort(),
);
for (const k of keys) {
if (k.endsWith("__v")) expect(q[k].type).toBe("noul");
else expect(q[k].type).toBe("choice");
}
// status question names the message id (not an index)
expect(
(q["a__status"] as { instructions?: string }).instructions,
).toContain("a");
});
});
describe("buildJevState", () => {
it("includes the distilled policy + messages + stripped context facts", () => {
const state = buildJevState(
[
{ id: "m1", user: "alice", content: "kontol" },
{ id: "m2", user: "bob", content: "halo <b>bro</b>" },
],
{
contextBlock:
"<location_context channel_id='1'/>\nLokasi: channel umum ramai.",
webSearchBlock: "<web_searches/>\nHasil: tidak ada.",
glossaryBlock: "<term_glossary/>\nIstilah: none.",
channelCulture: "channel santai",
},
"## contoh koreksi",
);
expect(state).toContain("KEBIJAKAN SERVER");
expect(state).toContain('- Pesan "m1" dari "alice": "kontol"');
expect(state).toContain("KULTUR CHANNEL");
expect(state).toContain("KONTEKS:");
expect(state).toContain("HASIL PENCARIAN WEB:");
expect(state).toContain("GLOSARIUM:");
expect(state).toContain("KOREKSI SEBELUMNYA");
// NO chat-scaffolding artifacts (this is the declarative contract)
expect(state).not.toContain("<messages_to_analyze>");
expect(state).not.toContain("<location_context");
});
});
describe("isJevAccepted", () => {
it("accepts a clean verdict with high confidence and noul<0.5", () => {
expect(isJevAccepted(cleanAnswers, MID, 0.9)).toBe(true);
});
it("accepts a flagged verdict with noul>=0.5", () => {
expect(isJevAccepted(flaggedAnswers, MID, 0.9)).toBe(true);
});
it("rejects low-confidence status", () => {
expect(
isJevAccepted(
answersFor({
[`${MID}__status`]: {
type: "choice",
choice: "clean",
confidence: 0.5,
},
}),
MID,
0.9,
),
).toBe(false);
});
it("rejects contradictory clean-with-high-noul", () => {
expect(
isJevAccepted(
answersFor({ [`${MID}__v`]: { type: "noul", noul: 0.95 } }),
MID,
0.9,
),
).toBe(false);
});
it("rejects flagged-with-low-noul", () => {
expect(
isJevAccepted(
answersFor({
[`${MID}__v`]: { type: "noul", noul: 0.1 },
[`${MID}__status`]: {
type: "choice",
choice: "flagged",
confidence: 0.99,
},
}),
MID,
0.9,
),
).toBe(false);
});
it("rejects clean with non-none severity/category/action", () => {
expect(
isJevAccepted(
answersFor({
[`${MID}__severity`]: { type: "choice", choice: "low" },
}),
MID,
0.9,
),
).toBe(false);
expect(
isJevAccepted(
answersFor({
[`${MID}__category`]: { type: "choice", choice: "spam" },
}),
MID,
0.9,
),
).toBe(false);
expect(
isJevAccepted(
answersFor({
[`${MID}__action`]: { type: "choice", choice: "monitor" },
}),
MID,
0.9,
),
).toBe(false);
});
it("rejects flagged with none severity/category/action", () => {
expect(
isJevAccepted(
answersFor({
[`${MID}__v`]: { type: "noul", noul: 0.9 },
[`${MID}__status`]: {
type: "choice",
choice: "flagged",
confidence: 0.99,
},
[`${MID}__severity`]: { type: "choice", choice: "none" },
}),
MID,
0.9,
),
).toBe(false);
expect(
isJevAccepted(
answersFor({
[`${MID}__v`]: { type: "noul", noul: 0.9 },
[`${MID}__status`]: {
type: "choice",
choice: "flagged",
confidence: 0.99,
},
[`${MID}__category`]: { type: "choice", choice: "none" },
}),
MID,
0.9,
),
).toBe(false);
expect(
isJevAccepted(
answersFor({
[`${MID}__v`]: { type: "noul", noul: 0.9 },
[`${MID}__status`]: {
type: "choice",
choice: "flagged",
confidence: 0.99,
},
[`${MID}__action`]: { type: "choice", choice: "none" },
}),
MID,
0.9,
),
).toBe(false);
});
it("rejects warn with delete/escalate action", () => {
expect(
isJevAccepted(
answersFor({
[`${MID}__v`]: { type: "noul", noul: 0.6 },
[`${MID}__status`]: {
type: "choice",
choice: "warn",
confidence: 0.92,
},
[`${MID}__severity`]: { type: "choice", choice: "low" },
[`${MID}__category`]: { type: "choice", choice: "spam" },
[`${MID}__action`]: { type: "choice", choice: "delete" },
}),
MID,
0.9,
),
).toBe(false);
});
it("rejects unknown status/severity/category/action labels", () => {
expect(
isJevAccepted(
answersFor({
[`${MID}__status`]: {
type: "choice",
choice: "banned",
confidence: 0.99,
},
}),
MID,
0.9,
),
).toBe(false);
expect(
isJevAccepted(
answersFor({
[`${MID}__severity`]: { type: "choice", choice: "severe" },
}),
MID,
0.9,
),
).toBe(false);
expect(
isJevAccepted(
answersFor({
[`${MID}__category`]: { type: "choice", choice: "doxxing" },
}),
MID,
0.9,
),
).toBe(false);
expect(
isJevAccepted(
answersFor({
[`${MID}__action`]: { type: "choice", choice: "destroy" },
}),
MID,
0.9,
),
).toBe(false);
});
it("rejects missing questions", () => {
const { [`${MID}__action`]: _drop, ...partial } = answersFor({});
expect(isJevAccepted(partial as JevAnswers, MID, 0.9)).toBe(false);
});
});
describe("mapJevAnswersToResult", () => {
it("maps a clean answer to a zero-score analysis result", () => {
const r = mapJevAnswersToResult(cleanAnswers, MID);
expect(r).toMatchObject({
messageId: MID,
status: "clean",
flags: [],
score: 0,
categories: [],
severity: "none",
recommendedAction: "none",
policyVersion: JEV_POLICY_VERSION,
confidence: 0.99,
});
expect(r.analysis).toContain("status=clean");
expect(r.analysis).toContain("[Jev]");
});
it("maps a flagged answer with calibrated score + category flag", () => {
const r = mapJevAnswersToResult(flaggedAnswers, MID);
expect(r.status).toBe("flagged");
expect(r.flags).toEqual(["vulgar_language"]);
expect(r.categories).toEqual(["vulgar_language"]);
expect(r.severity).toBe("high");
expect(r.recommendedAction).toBe("delete");
expect(r.score).toBeGreaterThanOrEqual(0.9); // clamped to >=0.7, noul 0.98
expect(r.analysis).toContain("p_melanggar=0.98");
expect(r.evidence).toEqual([]);
});
it("warns become score 0.45 with no flags", () => {
const r = mapJevAnswersToResult(
answersFor({
[`${MID}__status`]: {
type: "choice",
choice: "warn",
confidence: 0.92,
},
[`${MID}__severity`]: { type: "choice", choice: "low" },
[`${MID}__category`]: {
type: "choice",
choice: "conflict_instigation",
},
[`${MID}__action`]: { type: "choice", choice: "warn" },
[`${MID}__v`]: { type: "noul", noul: 0.6 },
}),
MID,
);
expect(r.status).toBe("warn");
expect(r.score).toBe(0.45);
expect(r.flags).toEqual(["conflict_instigation"]);
expect(r.recommendedAction).toBe("warn");
});
it("every category label in the vocab is accepted (no unknown rejection)", () => {
// A self-consistent FLAGGED base (flagged needs non-none severity/action).
const flaggedBase = {
[`${MID}__v`]: { type: "noul", noul: 0.9 },
[`${MID}__status`]: {
type: "choice",
choice: "flagged",
confidence: 0.99,
},
[`${MID}__severity`]: { type: "choice", choice: "medium" },
[`${MID}__category`]: { type: "choice", choice: "none" },
[`${MID}__action`]: { type: "choice", choice: "monitor" },
} satisfies JevAnswers;
for (const c of JEV_CATEGORIES) {
// "none" is only valid with clean status (flagged+none is contradictory
// and MUST be rejected — covered by the cross-consistency tests above).
if (c === "none") continue;
const answers = {
...flaggedBase,
[`${MID}__category`]: { type: "choice", choice: c },
} satisfies JevAnswers;
expect(isJevAccepted(answers, MID, 0.9)).toBe(true);
}
});
});