test: verify conversational birth-time rectification

This commit is contained in:
Jesse_Chen
2026-07-21 14:40:43 +08:00
parent de9a687779
commit ffaa7dc9c5
9 changed files with 1138 additions and 22 deletions
@@ -1,6 +1,118 @@
import { z } from "zod";
import type { VersionedJourneyResponse } from "./birth-time-journey-service.ts";
export const conversationalRectificationTelemetryProtocols = [
"conversational-evidence-v3",
] as const;
export const conversationalRectificationTelemetryPhases = [
"entry",
"collecting_evidence",
"paused",
"confirming",
"completed",
"abandoned",
] as const;
export const conversationalRectificationTelemetryActionKinds = [
"start",
"resume",
"answer",
"pause",
"abandon",
"confirm",
"unknown",
] as const;
export const conversationalRectificationTelemetryResultCategories = [
"success",
"rejected",
"conflict",
"failed",
] as const;
export const conversationalRectificationTelemetryLatencyBuckets = [
"lt_100ms",
"100_499ms",
"500_1999ms",
"2s_plus",
] as const;
export const conversationalRectificationTelemetryBillingStates = [
"not_applicable",
"charged",
"released",
"migration_waived",
"unchanged",
"unknown",
] as const;
export const conversationalRectificationTelemetryErrorCategories = [
"none",
"authentication",
"validation",
"conflict",
"billing",
"dependency",
"unknown",
] as const;
const deploymentShaSchema = z.union([
z.string().regex(/^[0-9a-f]{7,64}$/),
z.literal("unknown"),
]);
export const conversationalRectificationTelemetryPayloadSchema = z.object({
protocol: z.enum(conversationalRectificationTelemetryProtocols),
phase: z.enum(conversationalRectificationTelemetryPhases),
actionKind: z.enum(conversationalRectificationTelemetryActionKinds),
resultCategory: z.enum(conversationalRectificationTelemetryResultCategories),
latencyBucket: z.enum(conversationalRectificationTelemetryLatencyBuckets),
billingState: z.enum(conversationalRectificationTelemetryBillingStates),
errorCategory: z.enum(conversationalRectificationTelemetryErrorCategories),
deploymentSha: deploymentShaSchema,
}).strict().readonly();
export type ConversationalRectificationTelemetryPayload = z.infer<
typeof conversationalRectificationTelemetryPayloadSchema
>;
export type ConversationalRectificationTelemetrySink = (
payload: ConversationalRectificationTelemetryPayload,
) => void;
function conversationalRectificationConsoleSink(
payload: ConversationalRectificationTelemetryPayload,
): void {
console.info("[conversational-rectification]", JSON.stringify(payload));
}
export function createConversationalRectificationTelemetry(
sink: ConversationalRectificationTelemetrySink = conversationalRectificationConsoleSink,
): ConversationalRectificationTelemetrySink {
return (input) => {
const payload = conversationalRectificationTelemetryPayloadSchema.parse(input);
try {
sink(payload);
} catch { // no-excuse-ok: observability cannot break the product request
return;
}
};
}
export const recordConversationalRectificationTelemetry =
createConversationalRectificationTelemetry();
export function conversationalRectificationLatencyBucket(
latencyMs: number,
): ConversationalRectificationTelemetryPayload["latencyBucket"] {
if (!Number.isFinite(latencyMs) || latencyMs < 0) return "2s_plus";
if (latencyMs < 100) return "lt_100ms";
if (latencyMs < 500) return "100_499ms";
if (latencyMs < 2_000) return "500_1999ms";
return "2s_plus";
}
export function safeConversationalRectificationDeploymentSha(
value: string | undefined,
): ConversationalRectificationTelemetryPayload["deploymentSha"] {
const normalized = value?.trim().toLowerCase() ?? "";
return /^[0-9a-f]{7,64}$/.test(normalized) ? normalized : "unknown";
}
export const journeyMetricNames = [
"turn_advanced",
"draft_corrected",
@@ -71,6 +71,7 @@ export type ConversationalRectificationServicePorts = Readonly<{
& Partial<Pick<ConversationalRectificationStore, "importLegacy">>;
billing: Pick<ConversationalRectificationBilling, "reserve" | "complete" | "release">;
rectificationPriceCredits: number;
allowNewCaseCreation?: boolean;
loadDeclaredProfile(userId: string): Promise<ConversationalRectificationProfile>;
loadLegacyCase?(
userId: string,
@@ -518,6 +519,9 @@ export function createConversationalRectificationService(
&& current.billingState === "migration_waived") {
throw new ConversationalRectificationError("action_conflict");
}
if (ports.allowNewCaseCreation === false) {
throw new ConversationalRectificationError("service_unavailable");
}
const legacy = await loadLegacy(userId, legacyCaseId);
if (!legacy) throw new ConversationalRectificationError("case_not_found");
@@ -690,6 +694,9 @@ export function createConversationalRectificationService(
}
return publicTurn(existing);
}
if (ports.allowNewCaseCreation === false) {
throw new ConversationalRectificationError("service_unavailable");
}
let reserved = false;
try {