feat(rectification): remove the rectification regenerate button and endpoint (BUG-1056)

T2 of TASK-rectification-grounding-20260927 (product decision P1).
The rectification 「重试(重新生成)」 was a blind agent.generate rewrite that
persisted unchecked text (invented range / fit rate reproduced). Removed:
the regenerate route, regenerate-turn.ts, the regeneration agent and its
read-only tool set, the client regenerate action/state and the
regenerating/canRegenerate props. ChatMessageActions renders the regenerate
button only when onRegenerate is passed; ordinary consultation is unchanged.
The DB function regenerate_agentic_rectification_turn is kept (AGENTS §7.6,
retire in a later round). DESIGN.md / VOICE.md updated; source-contract
tests follow with 原值/新值/原因 notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-27 03:28:27 +08:00
co-authored by Claude Opus 5.5
parent 5a8084494b
commit 84eb23152a
37 changed files with 200 additions and 961 deletions
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
import test from "node:test";
import { homeSurface as page } from "./home-surface.ts";
import { createElement } from "react";
@@ -86,12 +86,12 @@ const upgradeSkillRoute = readFileSync(
new URL("../src/app/api/rectification/cases/[caseId]/upgrade-skill/route.ts", import.meta.url),
"utf8",
);
const regenerateRoute = readFileSync(
new URL(
"../src/app/api/rectification/cases/[caseId]/turns/[turnId]/regenerate/route.ts",
import.meta.url,
),
"utf8",
// 原值: 模块级读取 cases/[caseId]/turns/[turnId]/regenerate/route.ts 供动作条用例断言。
// 新值: 不再读取;动作条用例改为断言该路由不存在、校正行不传 onRegenerate。
// 原因: BUG-1056(TASK-rectification-grounding-20260927 P1)删除生时校正的重新生成接口与按钮。
const regenerateRoutePath = new URL(
"../src/app/api/rectification/cases/[caseId]/turns/[turnId]/regenerate/route.ts",
import.meta.url,
);
test("birth-time rectification entry mounts the V9 case-ref chat", () => {
@@ -461,18 +461,25 @@ test("rectification keeps receipts for the varga sentence and shows live tool pr
assert.match(styles, /\.message-thinking-body/);
});
test("completed Agent replies restore feedback, copy and safe in-place regeneration actions", () => {
// 原值: 名称「… restore feedback, copy and safe in-place regeneration actions」;断言容器里有
// /turns/.../regenerate 请求与 randomUUID 请求号、重新生成路由不计费且按绑定 Skill 取包。
// 新值: 名称改为「feedback and copy, no regeneration」;断言校正行不传 onRegenerate、容器不再请求
// 重新生成、路由文件不存在。赞 / 踩 / 复制与普通对话的「重新生成回答」按钮断言保留。
// 原因: BUG-1056(TASK-rectification-grounding-20260927 P1)删除生时校正的重新生成接口与按钮(盲写且不校验就落库)。
test("completed Agent replies restore feedback and copy, no regeneration (BUG-1056)", () => {
for (const label of ["赞", "踩", "复制回答", "重新生成回答"]) {
assert.match(messageActions, new RegExp(`aria-label="${label}"`));
}
assert.match(messageActions, /\{onRegenerate && \(/);
// 原值: `<ChatMessageActions` 写在容器 messages.map 里。
// 新值: 动作条随行组件抽出;容器仍切换 feedback。
// 原因: BUG-725 动作回调走 actionsRef,组件本身仍要渲染动作条。
assert.match(messageEntry, /<ChatMessageActions/);
assert.match(chat, /toggleRectificationFeedback/);
assert.match(chat, /navigator\.clipboard\.writeText\(copyTextForMessage\(message\.text, message\.question\)\)/);
assert.match(chat, /\/turns\/\$\{encodeURIComponent\(message\.turnId\)\}\/regenerate/);
assert.match(chat, /requestId: globalThis\.crypto\.randomUUID\(\)/);
assert.doesNotMatch(chat, /\/regenerate/);
assert.doesNotMatch(messageEntry, /onRegenerate|canRegenerate|regenerat/);
assert.equal(existsSync(regenerateRoutePath), false);
const messageRender = chat.slice(
chat.indexOf("{messages.map((message) => {"),
@@ -486,13 +493,6 @@ test("completed Agent replies restore feedback, copy and safe in-place regenerat
assert.doesNotMatch(messageEntry, /<CompletedActivityReceipt/);
assert.doesNotMatch(chat, /send\("message",\s*(?:oldText|previousText)/);
assert.doesNotMatch(regenerateRoute, /consultation-billing|reserveConsultation|billing\./);
assert.match(regenerateRoute, /loadV9CaseSkillIdentity\(accounting, user\.id, caseId\)/);
assert.match(regenerateRoute, /resolveExactSkillPackage\(/);
assert.match(regenerateRoute, /skillPackage\.sourceCommit !== boundIdentity\.sourceCommit/);
assert.match(regenerateRoute, /getRectificationV9RegenerationAgent\([\s\S]*skillPackage\)/);
assert.match(regenerateRoute, /regenerateV9AssistantTurn/);
assert.match(regenerateRoute, /skillPackage,/);
});
test("candidate state renders from the snapshot API and never from sentinels", () => {