chore: retire legacy rectification runtime
Staging Backend Quality Gate / validate (push) Failing after 9m57s
Staging Backend Quality Gate / publish (push) Has been skipped

This commit is contained in:
Jesse_Chen
2026-08-07 12:55:32 +08:00
parent 28147b40d0
commit f3946eaa6f
158 changed files with 166 additions and 41640 deletions
+13 -319
View File
@@ -5,7 +5,6 @@ import {
getGeneralJyotishAgent,
getJyotishAgent,
runConsultationWorkflow,
toAgentConsultationContext,
} from "@/mastra";
import { languageModelConfigurationMessage } from "@/mastra/model";
import { blocksPromptExtraction } from "@/lib/consult-safety";
@@ -35,12 +34,6 @@ import {
ConsultationProfileTruthError,
prepareConsultationRoute,
} from "@/lib/consultation-route-service";
import {
createRectificationHandoffService,
createRectificationV4HandoffService,
type RectificationHandoffExecution,
type RectificationV4HandoffExecution,
} from "@/lib/rectification-handoff-service";
import { z } from "zod";
export const runtime = "nodejs";
@@ -62,38 +55,12 @@ const chatRequestMetadataSchema = z.object({
.default([]),
});
const rectificationV3HandoffSchema = z.object({
protocol: z.literal("conversational-evidence-v3").optional(),
caseId: z.string().uuid(),
turnVersion: z.number().int().nonnegative(),
claimActionId: z.string().uuid(),
requestId: z.string().uuid(),
}).strict();
const rectificationV4HandoffSchema = z.object({
protocol: z.literal("rectification-evidence-v4"),
caseId: z.string().uuid(),
caseVersion: z.number().int().nonnegative(),
claimActionId: z.string().uuid(),
requestId: z.string().uuid(),
}).strict();
const v4ContinuationRequestSchema = z.object({
...chatRequestMetadataSchema.shape,
consultationMode: consultationBirthTimeModeSchema,
question: z.string().trim().min(1).max(500),
theme: z.enum(["career", "marriage", "wealth", "timing", "general"]),
entrypoint: z.undefined().optional(),
rectificationHandoff: rectificationV4HandoffSchema,
}).strict();
const chartChatRequestSchema = consultationInputSchema.extend({
...chatRequestMetadataSchema.shape,
consultationMode: consultationBirthTimeModeSchema.exclude(["general_no_birth_time"])
.optional()
.default("verified_chart"),
entrypoint: consultationEntrypointSchema.optional(),
rectificationHandoff: rectificationV3HandoffSchema.optional(),
}).strict();
const generalChatRequestSchema = z.object({
@@ -104,11 +71,7 @@ const generalChatRequestSchema = z.object({
entrypoint: z.undefined().optional(),
}).strict();
const chatRequestSchema = z.union([
v4ContinuationRequestSchema,
generalChatRequestSchema,
chartChatRequestSchema,
]);
const chatRequestSchema = z.union([generalChatRequestSchema, chartChatRequestSchema]);
function currentTimeContext(now = new Date()) {
const chinaTime = new Date(now.getTime() + 8 * 60 * 60 * 1000)
@@ -122,54 +85,6 @@ function chinaCalendarDate(now: Date) {
return new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString().slice(0, 10);
}
function rangeBoundaryWorkflowContext(
start: Awaited<ReturnType<typeof runConsultationWorkflow>>,
end: Awaited<ReturnType<typeof runConsultationWorkflow>>,
acceptedRange: Readonly<{ start: string; end: string }>,
) {
const startConsumer = start.consumer_context;
const endConsumer = end.consumer_context;
return {
...start,
success: start.success && end.success,
consumer_context: {
...startConsumer,
core_status: startConsumer.core_status === "blocked" || endConsumer.core_status === "blocked"
? "blocked"
: startConsumer.core_status === "degraded" || endConsumer.core_status === "degraded"
? "degraded"
: "ready",
available_layers: startConsumer.available_layers.filter((layer) =>
endConsumer.available_layers.includes(layer)),
missing_route_layers: [...new Set([
...startConsumer.missing_route_layers,
...endConsumer.missing_route_layers,
])],
hard_blockers: [...new Set([
...startConsumer.hard_blockers,
...endConsumer.hard_blockers,
])],
answer_policy: {
...startConsumer.answer_policy,
can_answer_direction: startConsumer.answer_policy.can_answer_direction
&& endConsumer.answer_policy.can_answer_direction,
can_answer_precise_timing: false,
birth_time_confidence: "accepted_candidate_range",
candidate_is_confirmed: false,
require_boundary_agreement: true,
},
},
candidate_range: {
...acceptedRange,
claim_status: "candidate_range_not_birth_time_truth",
},
range_boundary_contexts: {
start: toAgentConsultationContext(start),
end: toAgentConsultationContext(end),
},
};
}
export async function POST(request: Request) {
let supabase: Awaited<ReturnType<typeof createServerSupabaseClient>>;
let accounting: ReturnType<typeof createAdminSupabaseClient>;
@@ -252,92 +167,6 @@ export async function POST(request: Request) {
const userId = user.id;
const requestId = parsed.data.requestId;
const handoff = "rectificationHandoff" in parsed.data
? parsed.data.rectificationHandoff
: undefined;
const v4Handoff = handoff?.protocol === "rectification-evidence-v4";
let handoffExecution: RectificationHandoffExecution | RectificationV4HandoffExecution | null = null;
let settleHandoffRequest: ((emitted: boolean) => Promise<void>) | null = null;
let handoffSettlement: Promise<void> | null = null;
async function settleHandoff(emitted: boolean) {
if (!settleHandoffRequest || !handoffExecution
|| handoffExecution.status !== "ready") return;
handoffSettlement ??= settleHandoffRequest(emitted);
await handoffSettlement;
}
if (handoff) {
if ((!v4Handoff
&& !["verified_chart", "unverified_birth_time"].includes(parsed.data.consultationMode))
|| parsed.data.entrypoint !== undefined
|| requestId !== handoff.requestId) {
return NextResponse.json(
{
error: "原问题交接请求不一致",
message: "请刷新校正结果后重新点击继续,本次不会扣点。",
},
{ status: 409 },
);
}
try {
if (handoff.protocol === "rectification-evidence-v4") {
const service = createRectificationV4HandoffService(accounting);
handoffExecution = await service.beginExecution({
userId,
caseId: handoff.caseId,
caseVersion: handoff.caseVersion,
claimActionId: handoff.claimActionId,
requestId: handoff.requestId,
question: parsed.data.question,
});
settleHandoffRequest = (emitted) => service.settle({
userId,
caseId: handoff.caseId,
claimActionId: handoff.claimActionId,
requestId: handoff.requestId,
emitted,
}).then(() => undefined);
} else {
const service = createRectificationHandoffService(accounting);
handoffExecution = await service.beginExecution({
userId,
caseId: handoff.caseId,
turnVersion: handoff.turnVersion,
claimActionId: handoff.claimActionId,
requestId: handoff.requestId,
question: parsed.data.question,
});
settleHandoffRequest = (emitted) => service.settle({
userId,
caseId: handoff.caseId,
claimActionId: handoff.claimActionId,
requestId: handoff.requestId,
emitted,
}).then(() => undefined);
}
} catch {
return NextResponse.json(
{
error: "原问题状态已经变化",
message: "请刷新后查看最新状态,本次不会扣点。",
},
{ status: 409 },
);
}
if (handoffExecution.status !== "ready") {
const consumed = handoffExecution.status === "consumed";
return NextResponse.json(
{
error: consumed ? "原问题已经继续回答" : "原问题正在另一处继续",
message: consumed
? "刷新后即可查看最新状态,不会再次扣点。"
: "请等待当前回答完成后刷新,本次不会重复扣点。",
},
{ status: consumed ? 410 : 409 },
);
}
}
const userControlledPrompt = [
parsed.data.question,
@@ -346,16 +175,6 @@ export async function POST(request: Request) {
.map((message) => message.text),
].join("\n");
if (blocksPromptExtraction(userControlledPrompt)) {
if (handoffExecution?.status === "ready") {
try {
await settleHandoff(false);
} catch {
return NextResponse.json(
{ error: "暂时无法释放原问题", message: "请稍后刷新状态。" },
{ status: 503 },
);
}
}
return NextResponse.json(
{
error: "无法处理该请求",
@@ -370,10 +189,6 @@ export async function POST(request: Request) {
prepared = await prepareConsultationRoute({
userId,
mode: parsed.data.consultationMode,
...(v4Handoff && handoffExecution?.status === "ready"
&& "acceptedRange" in handoffExecution
? { candidateRange: handoffExecution.acceptedRange }
: {}),
async loadProfile(profileUserId) {
const { data, error } = await supabase
.from("profiles")
@@ -386,39 +201,20 @@ export async function POST(request: Request) {
reserve: () => reserveConsultationModel(
chatSession.model_id,
(modelId) => sessionModel?.id === modelId ? sessionModel : null,
(model) => handoffExecution?.billingReused
? Promise.resolve({
success: true,
credits: handoffExecution.credits ?? null,
error_code: null,
})
: authorizeUsage(accounting, {
userId,
requestId,
featureKey: "chat.standard",
requestedModelId: model.id,
creditCost: model.creditCost,
}).then((result) => ({
success: result.success,
credits: result.credits,
error_code: result.reason,
})),
(model) => authorizeUsage(accounting, {
userId,
requestId,
featureKey: "chat.standard",
requestedModelId: model.id,
creditCost: model.creditCost,
}).then((result) => ({
success: result.success,
credits: result.credits,
error_code: result.reason,
})),
),
});
} catch (error) {
if (handoffExecution?.status === "ready") {
try {
await settleHandoff(false);
} catch {
return NextResponse.json(
{
error: "暂时无法释放原问题",
message: "请稍后刷新状态,本次不会重复扣点。",
},
{ status: 503 },
);
}
}
if (error instanceof ConsultationProfileTruthError) {
const modeChanged = error.code === "mode_changed";
return NextResponse.json(
@@ -447,16 +243,6 @@ export async function POST(request: Request) {
const modelSelection = prepared.reservation;
if (modelSelection.status === "unavailable") {
if (handoffExecution?.status === "ready") {
try {
await settleHandoff(false);
} catch {
return NextResponse.json(
{ error: "暂时无法释放原问题", message: "请稍后刷新状态。" },
{ status: 503 },
);
}
}
return NextResponse.json(
{
error: "模型暂不可用",
@@ -470,16 +256,6 @@ export async function POST(request: Request) {
const reserveResult = modelSelection.reservation;
if (!reserveResult.success) {
if (handoffExecution?.status === "ready") {
try {
await settleHandoff(false);
} catch {
return NextResponse.json(
{ error: "暂时无法释放原问题", message: "请稍后刷新状态。" },
{ status: 503 },
);
}
}
const insufficient = reserveResult.error_code === "insufficient_credits";
return NextResponse.json(
{
@@ -493,17 +269,6 @@ export async function POST(request: Request) {
}
async function cancel() {
if (handoffExecution?.status === "ready") {
try {
await settleHandoff(false);
} catch (error) {
const reason = error instanceof Error ? error.name : "UnknownError";
console.error(
`[billing] handoff release failed request=${requestId} reason=${reason}`,
);
}
return;
}
try {
await releaseUsage(accounting, userId, requestId, "consultation_cancelled");
} catch (error) {
@@ -516,10 +281,6 @@ export async function POST(request: Request) {
const usageStartedAt = Date.now();
async function complete(usage: Promise<{ inputTokens?: number; outputTokens?: number }>) {
if (handoffExecution?.status === "ready") {
await settleHandoff(true);
return;
}
const resolved = await usage;
const inputTokens = Math.max(0, Math.trunc(resolved.inputTokens ?? 0));
const outputTokens = Math.max(0, Math.trunc(resolved.outputTokens ?? 0));
@@ -549,9 +310,7 @@ export async function POST(request: Request) {
try {
const { history } = parsed.data;
const name = prepared.serverChart?.name ?? parsed.data.name;
const consultationMode: ConsultationBirthTimeMode = v4Handoff
? "unverified_birth_time"
: prepared.consultationMode;
const consultationMode: ConsultationBirthTimeMode = prepared.consultationMode;
if (!shouldRunBirthChartWorkflow(consultationMode)) {
const result = await getGeneralJyotishAgent(selectedModel).stream([
{
@@ -579,7 +338,6 @@ export async function POST(request: Request) {
"x-jyotish-missing-layers": "birth-minute",
"x-jyotish-birth-time-mode": consultationMode,
},
...(handoff ? { onFirstOutput: () => settle(completeWithUsage) } : {}),
onComplete: () => settle(completeWithUsage),
onError: (_error, emitted) => settleInterrupted(emitted),
onCancel: settleInterrupted,
@@ -587,69 +345,6 @@ export async function POST(request: Request) {
}
if (!prepared.serverChart) throw new Error("server_chart_truth_missing");
if (v4Handoff) {
if (!handoffExecution || handoffExecution.status !== "ready"
|| !("acceptedRange" in handoffExecution)) {
throw new Error("rectification_v4_range_missing");
}
const acceptedRange = handoffExecution.acceptedRange;
const boundaryInput = (time: string) => {
const [hour, minute] = time.split(":").map(Number);
return consultationInputSchema.parse({
...prepared.serverChart?.toolInput,
hour,
minute,
entryMode: "direct_chart",
question: resolvedQuestion.modelQuestion,
theme: parsed.data.theme,
});
};
const [startWorkflow, endWorkflow] = await Promise.all([
runConsultationWorkflow(boundaryInput(acceptedRange.start)),
runConsultationWorkflow(boundaryInput(acceptedRange.end)),
]);
const workflowContext = rangeBoundaryWorkflowContext(
startWorkflow,
endWorkflow,
acceptedRange,
);
const workflowReceipt = consultationWorkflowReceipt(workflowContext);
const result = await getJyotishAgent(selectedModel, workflowContext).stream([
...history.map((message) => message.role === "user"
? { role: "user" as const, content: message.text }
: { role: "assistant" as const, content: message.text }),
{
role: "user",
content: [
currentTimeContext(requestTime),
name ? `用户称呼:${name}` : "",
resolvedQuestion.modelQuestion,
`本次只能使用已保存候选范围 ${acceptedRange.start}${acceptedRange.end} 的两个边界共同支持的结论。`,
"不得选择中点、峰值或单一代表分钟,不得把候选范围说成已确认出生时间。",
].filter(Boolean).join("\n"),
},
]);
const completeWithUsage = () => complete(result.totalUsage);
const settleInterrupted = (emitted: boolean) =>
settle(emitted ? completeWithUsage : cancel);
return streamTextResponse(result.textStream, {
transformText: createBirthTimeModeOutputGuard("unverified_birth_time", false),
mode: "mastra",
requestId,
headers: {
"x-jyotish-workflow-route": workflowReceipt.route,
"x-jyotish-workflow-status": workflowReceipt.status,
"x-jyotish-technique-truth": workflowReceipt.techniqueTruth,
"x-jyotish-precise-timing": "blocked",
"x-jyotish-missing-layers": workflowReceipt.missingLayers,
"x-jyotish-birth-time-mode": "unverified_birth_time",
},
onFirstOutput: () => settle(completeWithUsage),
onComplete: () => settle(completeWithUsage),
onError: (_error, emitted) => settleInterrupted(emitted),
onCancel: settleInterrupted,
});
}
const toolInput = consultationInputSchema.parse({
...prepared.serverChart.toolInput,
// Unverified use is still a normal chart calculation with a hard answer
@@ -697,7 +392,6 @@ export async function POST(request: Request) {
"x-jyotish-missing-layers": workflowReceipt.missingLayers,
"x-jyotish-birth-time-mode": consultationMode,
},
...(handoff ? { onFirstOutput: () => settle(completeWithUsage) } : {}),
onComplete: () => settle(completeWithUsage),
onError: (_error, emitted) => settleInterrupted(emitted),
onCancel: settleInterrupted,