fix(rectification): keep hour-window tail clusters and lock the search window (BUG-623, BUG-624, BUG-625)
Independent Staging Quality Gate / validate (push) Successful in 17m15s
Independent Staging Quality Gate / publish (push) Successful in 2m15s

Hour windows no longer drop later signature clusters. Credible range uses cluster coverage, and a mid-session spoken birth window gets a fixed reply without calling the model.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-09 20:45:11 +08:00
co-authored by Cursor
parent a31a5e2426
commit 6008c07c81
41 changed files with 1164 additions and 44 deletions
@@ -463,8 +463,8 @@ function rpcDossier(decision: DecisionDossier, activeFocus?: Record<string, unkn
});
}
test("skill version is 10.0.19 after the delivery UI simplify bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
test("skill version is 10.0.20 after the delivery UI simplify bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
});
test("revision 5 with uncovered relatives asks the dated family collect, not a yearless D12 card", () => {
@@ -359,7 +359,7 @@ test("holdout not_ready forbids unique-minute copy and still blocks confirm", as
assert.match(agentSource, /不得宣称唯一出生分钟/);
assert.doesNotMatch(agentSource, /±2 分钟/);
assert.equal(PUBLIC_RECTIFICATION_TOOLS.length, 14);
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
const accounting = fakeAccounting({
...receiptHandlers,
@@ -199,12 +199,12 @@ test("delivery report gives 04:53 D10 as Cancer instead of letting the model inf
assert.match(report.markdown, /04:53 \| .*巨蟹座/);
});
test("skill 10.0.19 forbids computing varga signs from transition times", () => {
test("skill 10.0.20 forbids computing varga signs from transition times", () => {
const skillDir = fileURLToPath(new URL("../../skills/jyotish-birth-time-rectification", import.meta.url));
const skill = readFileSync(`${skillDir}/SKILL.md`, "utf8");
const comparison = readFileSync(`${skillDir}/references/candidate-comparison.md`, "utf8");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
assert.match(skill, /^version: 10\.0\.19$/m);
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
assert.match(skill, /^version: 10\.0\.20$/m);
assert.match(skill, new RegExp(SKILL_SIGN_SENTENCE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
assert.match(comparison, new RegExp(SKILL_SIGN_SENTENCE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
});
@@ -1454,9 +1454,9 @@ test("rescore failure does not fail the evidence write", async () => {
assert.ok(result.rescore.error_code);
});
test("public tool surface stays at 14 and new cases bind 10.0.19", () => {
test("public tool surface stays at 14 and new cases bind 10.0.20", () => {
assert.equal(PUBLIC_RECTIFICATION_TOOLS.length, 14);
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
const deprecated = resolveExactSkillPackage(
"jyotish-birth-time-rectification",
"10.0.2",
@@ -576,8 +576,8 @@ function warnLines(run: () => Promise<unknown> | unknown) {
}).then((result) => ({ result, lines }));
}
test("skill version is 10.0.19 after the delivery UI simplify bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
test("skill version is 10.0.20 after the delivery UI simplify bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
});
test("USER_COLLECT_QUESTION no longer has an other fallback", () => {
@@ -213,9 +213,9 @@ test("read-case evidence context keeps day labels and confirm does not rewrite d
assert.equal("p_occurred_from" in confirmCall.args, false);
});
test("new-case skill identity is 10.0.19 and the prompt prefers batch ingest", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
assert.match(skill, /^version: 10\.0\.19$/m);
test("new-case skill identity is 10.0.20 and the prompt prefers batch ingest", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
assert.match(skill, /^version: 10\.0\.20$/m);
assert.match(skill, /不要对同一句用户消息里的多件事件逐条 propose\+confirm/);
assert.match(agentSource, /新事件走 rectification-record-evidence-batch/);
assert.doesNotMatch(agentSource, /分别调用 rectification-propose-evidence 和 rectification-confirm-evidence/);
@@ -184,8 +184,8 @@ const CANDIDATE_IDS = [
"88888888-8888-4888-8888-888888888882",
] as const;
test("skill version is 10.0.19 after the delivery UI simplify bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
test("skill version is 10.0.20 after the delivery UI simplify bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
});
test("nineteen-row ledger opens the training gate with four scoreable domains", () => {
@@ -426,8 +426,8 @@ function rpcDossier(decision: DecisionDossier) {
});
}
test("skill version is 10.0.19 after the delivery UI simplify bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
test("skill version is 10.0.20 after the delivery UI simplify bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
});
test("pre-fix dual-exit constant is gone; range narration carries numbers and the disclaimer", () => {
@@ -94,8 +94,8 @@ function collectPersistResult(overrides: {
};
}
test("skill version is 10.0.19 after the delivery UI simplify bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
test("skill version is 10.0.20 after the delivery UI simplify bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
});
test("cases current_question remains the submit contract, not a visual slot", () => {
@@ -96,11 +96,11 @@ test("system prompt carries only high-priority boundaries, never the method copy
test("agent pins the dedicated rectification skill and its fixed version", () => {
assert.equal(RECTIFICATION_V9_SKILL_NAME, "jyotish-birth-time-rectification");
assert.equal(basename(RECTIFICATION_V9_SKILL_PATH), RECTIFICATION_V9_SKILL_NAME);
assert.ok(RECTIFICATION_V9_PACKAGE_PATH.endsWith("skills/jyotish-birth-time-rectification/versions/10.0.19"));
assert.ok(RECTIFICATION_V9_PACKAGE_PATH.endsWith("skills/jyotish-birth-time-rectification/versions/10.0.20"));
assert.notEqual(RECTIFICATION_V9_SKILL_PATH, RECTIFICATION_V9_PACKAGE_PATH);
assert.equal(realpathSync(RECTIFICATION_V9_SKILL_PATH), RECTIFICATION_V9_PACKAGE_PATH);
assert.equal(RECTIFICATION_SKILL_NAME, "jyotish-birth-time-rectification");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
});
test("step budgets are bounded per action with a hard ceiling", () => {
@@ -95,9 +95,9 @@ test("terminal transitions are one-way and evidence writes stop at terminal", ()
test("the active rectification skill pins the v10 identity and lives in the right directory", () => {
assert.equal(RECTIFICATION_SKILL_NAME, "jyotish-birth-time-rectification");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.19");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
assert.match(skill, /^---\nname: jyotish-birth-time-rectification/m);
assert.match(skill, /^version: 10\.0\.19$/m);
assert.match(skill, /^version: 10\.0\.20$/m);
assert.match(skill, /至多一个主问题且唯一来源:[\s\S]*不得自行提出、复述、改写或预告问题/);
for (const reference of references) {
const content = readFileSync(`${skillDirectory}/references/${reference}`, "utf8");
@@ -209,7 +209,7 @@ test("open RPC passes the pinned skill and server-derived baseline only", async
session_id: SESSION_ID,
status: "draft",
should_start_opening: true,
skill_version: "10.0.19",
skill_version: "10.0.20",
};
}
return null;
@@ -247,11 +247,11 @@ test("open RPC passes the pinned skill and server-derived baseline only", async
});
assert.equal(response.disposition, "created");
assert.equal(response.shouldStartOpening, true);
assert.equal(response.skillVersion, "10.0.19");
assert.equal(response.skillVersion, "10.0.20");
const openCall = accounting.calls.find((call) => call.fn === "open_agentic_rectification_case_v2");
assert.ok(openCall);
assert.equal(openCall.args.p_skill_name, "jyotish-birth-time-rectification");
assert.equal(openCall.args.p_skill_version, "10.0.19");
assert.equal(openCall.args.p_skill_version, "10.0.20");
assert.equal(openCall.args.p_user_id, "user-1");
// The server derives the baseline; the request never carries it from the browser.
assert.equal("birth_date" in openCall.args, false);
@@ -0,0 +1,84 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
import { unionStillValidRange } from "../src/lib/rectification-agentic/core/credible-range.ts";
import { RECTIFICATION_USER_COPY } from "../src/lib/rectification-agentic/user-copy.ts";
import { MACHINE_VOICE_LEXICON } from "../src/lib/rectification-agentic/v9/agent-voice-lexicon.ts";
import { stripVerbalWindowChange } from "../src/lib/rectification-agentic/v9/collect-prompt.ts";
import { parseDeclaredBirthWindow } from "../src/lib/rectification-agentic/v9/declared-window-utterance.ts";
import { RECTIFICATION_SKILL_VERSION } from "../src/lib/rectification-agentic/v9/case-status.ts";
const ROUTE = readFileSync(
new URL("../src/app/api/rectification/agent/route.ts", import.meta.url),
"utf8",
);
const SKILL = readFileSync(
new URL("../../skills/jyotish-birth-time-rectification/SKILL.md", import.meta.url),
"utf8",
);
test("declared birth-window utterances parse ranges and around-times", () => {
assert.deepEqual(
parseDeclaredBirthWindow("我的出生时间是 14 点 45 到 14 点 50"),
{ kind: "range", start: "14:45", end: "14:50" },
);
assert.deepEqual(
parseDeclaredBirthWindow("14:4514:50"),
{ kind: "range", start: "14:45", end: "14:50" },
);
assert.deepEqual(
parseDeclaredBirthWindow("14:47 左右"),
{ kind: "around", time: "14:47" },
);
assert.equal(parseDeclaredBirthWindow("没有"), null);
assert.equal(parseDeclaredBirthWindow("2014年入学,大概秋天"), null);
assert.equal(parseDeclaredBirthWindow("继续吧"), null);
});
test("choice-focus declared window is answered from the route before the model", () => {
const intercept = ROUTE.indexOf('if (action === "message" && parseDeclaredBirthWindow');
const selectedModel = ROUTE.indexOf("const selectedModel = isStructuredChoice");
const classifyCall = ROUTE.indexOf("await classifyRectificationTurnIntent");
assert.ok(intercept > 0);
assert.ok(selectedModel > intercept);
assert.ok(classifyCall > intercept);
assert.match(ROUTE, /declaredWindowLockedReply/);
assert.match(ROUTE, /persistV9DeterministicTurn/);
assert.equal(
RECTIFICATION_USER_COPY.declaredWindowLockedReply,
"搜索范围是开始时按你的资料定的,校正过程中不改。想按别的时间段重来,请先到资料里改出生时间,再新建一次校正。",
);
});
test("engine cluster coverage becomes the credible-range right edge", () => {
const state = buildInferenceState({
range_start: "14:00",
range_end: "15:00",
candidates: [{
id: "14:40",
time: "14:40",
relative_support: 40,
cluster_times: ["14:40", "14:41", "14:42", "14:43", "14:44", "14:45"],
cluster_start: "14:40",
cluster_end: "14:45",
}],
events: [],
probes: [],
});
assert.deepEqual(state.candidates[0]?.cluster_range, ["14:40", "14:45"]);
assert.deepEqual(unionStillValidRange(state.candidates), ["14:40", "14:45"]);
});
test("agent body cannot verbally accept a spoken birth window", () => {
assert.ok(MACHINE_VOICE_LEXICON.includes("以你说的为准"));
assert.equal(
stripVerbalWindowChange("明白了,出生时间以你说的 14:4514:50 为准。"),
"明白了,出生时间。",
);
assert.equal(stripVerbalWindowChange("以你说的为准。"), "");
assert.equal(stripVerbalWindowChange("明白了,以你说的为准。"), "明白了。");
assert.match(SKILL, /不得回答『以你说的为准』或改写搜索窗口/);
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.20");
});
+2 -2
View File
@@ -85,8 +85,8 @@ test("checked-in registry verifies hashed product packages and leaves consult on
[
{
name: "jyotish-birth-time-rectification",
version: "10.0.19",
sha256: "a68885d3cf2110ee456bff65210c60e6bef4da4ea45e2a790a5462fd6d7307c8",
version: "10.0.20",
sha256: "9c09867591cc9e8726f3577346230339b5a6dd4b4f3d634a9cda3e66170d6ac6",
},
{
name: "jyotish-personal-report",