Move-only (TASK-rectification-code-split-20260926 T2/T4). No behavior, API, copy, DB, Skill, billing or scoring change. - route.ts: 1077 -> 152 lines; POST 927 -> 114. It builds the Supabase clients (so the setup-failure mapping stays here) and assembles: agent-route-request.ts (auth / product / schema / flag / Case-Session binding), agent-route-typed-message.ts (declared-window reply, typed-answer preflight, unfocused classification), agent-route-structured-choice.ts, agent-route-agent-turn.ts (opening / read-only / typed agent stream and its exit gate), agent-route-billing.ts, agent-route-support.ts (schema, one-shot NDJSON reply, context types). - The four mutable preflight lets (expectedWrite, collectIntent, writeClassified, classifierDiagnostic) that crossed branches are one turnState object; values and flow unchanged. - Tests: whole-source contracts read tests/rectification-agent-route-surface.ts; the typed fast-path slice is rebuilt from the new files; billing, declared-window and structured-choice slices now call the handlers (billing in a child process because feature-pricing imports server-only), each with 原值/新值/原因. Route growth caps added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
62 lines
3.1 KiB
TypeScript
62 lines
3.1 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { productIds, productRegistry } from "../src/lib/product-domain-registry.ts";
|
|
import { consultationDomainIds } from "../src/lib/consultation-domain-registry.ts";
|
|
|
|
const root = path.resolve(import.meta.dirname, "..");
|
|
|
|
async function source(relative: string) {
|
|
return readFile(path.join(root, relative), "utf8");
|
|
}
|
|
|
|
test("independent products stay separate from ordinary consultation domains", () => {
|
|
assert.deepEqual(productIds, [
|
|
"report_center",
|
|
"rectification",
|
|
"compatibility",
|
|
"prashna",
|
|
"muhurta",
|
|
"annual_report",
|
|
]);
|
|
assert.equal(productIds.some((id) => consultationDomainIds.includes(id as never)), false);
|
|
assert.equal(consultationDomainIds.includes("annual"), true);
|
|
assert.equal(productIds.includes("annual_report"), true);
|
|
});
|
|
|
|
test("unreleased products default closed while existing product surfaces remain explicitly registered", () => {
|
|
const enabled = productRegistry.filter((item) => item.defaultEnabled).map((item) => item.id);
|
|
const disabled = productRegistry.filter((item) => !item.defaultEnabled).map((item) => item.id);
|
|
assert.deepEqual(enabled, ["report_center", "rectification", "compatibility"]);
|
|
assert.deepEqual(disabled, ["prashna", "muhurta", "annual_report"]);
|
|
});
|
|
|
|
test("migration publishes the exact product flags and keeps unreleased products disabled", async () => {
|
|
const sql = await source("supabase/migrations/20260814060000_product_feature_flags.sql");
|
|
for (const product of productRegistry) {
|
|
assert.match(sql, new RegExp(`'${product.featureFlagKey.replaceAll(".", "\\.")}'`));
|
|
}
|
|
for (const product of ["prashna", "muhurta", "annual_report"]) {
|
|
assert.match(sql, new RegExp(`\\('product\\.${product}', 1, false, 0`));
|
|
}
|
|
});
|
|
|
|
test("write and compute APIs enforce their independent product gates", async () => {
|
|
const expected: Array<[string, string]> = [
|
|
// 原值: ["src/app/api/rectification/agent/route.ts", 'isProductEnabled("rectification")']
|
|
// 新值: 同一道闸在 route.ts 调用的请求闸 agent-route-request.ts 里(route.ts 在任何分支之前先走它)
|
|
// 原因: TASK-rectification-code-split-20260926 POST 的请求闸原样搬出 route.ts
|
|
["src/lib/rectification-agentic/v9/agent-route-request.ts", 'isProductEnabled("rectification")'],
|
|
["src/app/api/rectification/agent/route.ts", "await resolveRectificationAgentRequest({"],
|
|
["src/app/api/rectification/cases/open/route.ts", 'isProductEnabled("rectification")'],
|
|
["src/app/api/rectification/cases/[caseId]/candidates/accept/route.ts", 'isProductEnabled("rectification")'],
|
|
["src/app/api/synastry/route.ts", 'isProductEnabled("compatibility")'],
|
|
["src/app/api/synastry-reports/route.ts", 'isProductEnabled("compatibility")'],
|
|
["src/app/api/reports/route.ts", 'isProductEnabled("report_center")'],
|
|
];
|
|
for (const [relative, gate] of expected) {
|
|
assert.match(await source(relative), new RegExp(gate.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
}
|
|
});
|