96 lines
4.1 KiB
TypeScript
96 lines
4.1 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import { canonicalGrowEventEnvelope, correlateEventWithServiceSession } from "../src/events/record-grow-event.js";
|
|
import { normalizeGrowEvent } from "../src/events/normalize.js";
|
|
import {
|
|
projectedSignalsFromQscoreResponse,
|
|
QSCORE_GROW_EVENT_DESTINATION,
|
|
QSCORE_GROW_EVENT_TOPIC,
|
|
} from "../src/events/qscore-event-outbox.js";
|
|
|
|
const input = {
|
|
id: "evt-real-1",
|
|
userId: "untrusted-body-user",
|
|
orgId: "growqr",
|
|
source: "interview-service",
|
|
type: "interview.review.completed",
|
|
category: "service" as const,
|
|
occurredAt: "2026-07-14T10:00:00.000Z",
|
|
mission: { instanceId: "mission-1", missionId: "interview-to-offer", stageId: "mock-interview" },
|
|
subject: { serviceId: "interview", externalId: "session-1" },
|
|
correlation: { sessionId: "session-1", taskId: "task-1" },
|
|
payload: {
|
|
review: {
|
|
overall_score: 78,
|
|
rubric_scores: { communication: 82, technical_accuracy: 70 },
|
|
},
|
|
},
|
|
dedupeKey: "interview:session-1:review",
|
|
};
|
|
|
|
const normalized = normalizeGrowEvent(input, { userId: "clerk-user-real" });
|
|
assert.equal(
|
|
normalized.type,
|
|
"interview.feedback.generated",
|
|
"backend-only consumers may retain staging's legacy event alias",
|
|
);
|
|
const canonical = canonicalGrowEventEnvelope(normalized, "clerk-user-real");
|
|
assert.deepEqual(canonical, {
|
|
...input,
|
|
userId: "clerk-user-real",
|
|
raw: input,
|
|
});
|
|
const sessionOnlyInput = { ...input, correlation: { sessionId: "session-1" } };
|
|
const sessionOnlyNormalized = normalizeGrowEvent(sessionOnlyInput, { userId: "clerk-user-real" });
|
|
const correlated = correlateEventWithServiceSession(sessionOnlyNormalized, "clerk-user-real", {
|
|
userId: "clerk-user-real",
|
|
missionInstanceId: "mission-1",
|
|
missionId: "interview-to-offer",
|
|
stageId: "mock-interview",
|
|
metadata: { task_id: "task-derived" },
|
|
});
|
|
const enrichedCanonical = canonicalGrowEventEnvelope(correlated, "clerk-user-real");
|
|
assert.deepEqual(enrichedCanonical.correlation, {
|
|
sessionId: "session-1",
|
|
task_id: "task-derived",
|
|
missionInstanceId: "mission-1",
|
|
missionId: "interview-to-offer",
|
|
stageId: "mock-interview",
|
|
});
|
|
assert.deepEqual(enrichedCanonical.raw, sessionOnlyInput, "raw producer correlation must remain unchanged");
|
|
assert.equal(QSCORE_GROW_EVENT_TOPIC, "qscore.grow_event.raw");
|
|
assert.equal(QSCORE_GROW_EVENT_DESTINATION, "/v1/events/ingest");
|
|
|
|
const projected = projectedSignalsFromQscoreResponse({
|
|
projected_signals: [
|
|
{ signal_id: "interview.overall_score", score: 45, present: true, evidence: { eventId: input.id } },
|
|
{ signal_id: "interview.sessions_completed", evidence: { eventId: input.id } },
|
|
],
|
|
});
|
|
assert.deepEqual(projected, [
|
|
{ signalId: "interview.overall_score", score: 45, present: true, raw: { eventId: input.id } },
|
|
{ signalId: "interview.sessions_completed", raw: { eventId: input.id } },
|
|
]);
|
|
|
|
const projectorSource = await readFile(new URL("../src/events/projectors/qscore-projector.ts", import.meta.url), "utf8");
|
|
for (const forbidden of ["clampScore", "BASELINE_SCORE", "extractResumeSignals", "forwardSignalsToQscoreService"]) {
|
|
assert.equal(projectorSource.includes(forbidden), false, `projector must not contain ${forbidden}`);
|
|
}
|
|
|
|
const serviceAgentSource = await readFile(new URL("../src/services/service-agents.ts", import.meta.url), "utf8");
|
|
assert.equal(/q_score\s*:\s*\d/.test(serviceAgentSource), false, "service agents must not inject numeric QScore data");
|
|
|
|
const homeSource = await readFile(new URL("../src/home/home-feed.ts", import.meta.url), "utf8");
|
|
for (const forbidden of ["|| 47", "score - 29", "Math.max(35", "ensureOnboardingBaselineQscore"]) {
|
|
assert.equal(homeSource.includes(forbidden), false, `Home must not contain synthetic QScore fallback ${forbidden}`);
|
|
}
|
|
|
|
const servicesSource = await readFile(new URL("../src/routes/services.ts", import.meta.url), "utf8");
|
|
assert.equal(
|
|
/app\.get\("\/qscore\/current"[\s\S]*?qscore\.review\.opened/.test(servicesSource),
|
|
false,
|
|
"polling the current QScore must remain a read and must not emit a synthetic review event",
|
|
);
|
|
|
|
console.log("qscore raw-event ownership contract: ok");
|