fix(rectification): make delivery report use inference-window facts (BUG-593)
Independent Staging Quality Gate / validate (push) Successful in 15m5s
Independent Staging Quality Gate / publish (push) Has been cancelled

The verification template was still quoting the engine's pre-inference span and eliminated dasha tops, and D9/D10 signs were left for the model to guess from transition clocks.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-08 15:33:48 +08:00
co-authored by Cursor
parent f17af254c5
commit 06e4410431
35 changed files with 1049 additions and 65 deletions
@@ -239,3 +239,11 @@ test("E-2 display copy keeps engine meaning out of instruction tone", () => {
assert.match(board, /engineMeaningToDisplayCopy/);
assert.match(gate, /engineMeaningToDisplayCopy/);
});
test("skill forbids computing D9/D10 signs from transition clocks", () => {
const skill = readFileSync(new URL("../../skills/jyotish-birth-time-rectification/SKILL.md", import.meta.url), "utf8");
const comparison = readFileSync(new URL("../../skills/jyotish-birth-time-rectification/references/candidate-comparison.md", import.meta.url), "utf8");
const sentence = "分盘上升只抄 `skill_verification_report.sign_by_candidate`,不得自行按换升时刻推算";
assert.match(skill, new RegExp(sentence.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
assert.match(comparison, new RegExp(sentence.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
});
@@ -462,8 +462,8 @@ function rpcDossier(decision: DecisionDossier, activeFocus?: Record<string, unkn
});
}
test("skill version stays 10.0.15 after the range-delivery bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.15");
test("skill version stays 10.0.16 after the delivery-report-facts bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.16");
});
test("revision 5 with uncovered relatives asks the dated family collect, not a yearless D12 card", () => {
@@ -269,7 +269,7 @@ test("a 25-minute plateau stays confirmation-blocked as an indistinguishable ran
algorithmVersion: "rectification-v5",
}, collectDecision(PLATEAU));
const gate = projection.confirmation_gate as ReturnType<typeof buildConfirmationGate>;
assert.ok(Number(projection.indistinguishable_width_minutes) >= 25);
assert.ok(Number(projection.engine_indistinguishable_width_minutes) >= 25);
assert.equal(projection.confirmation_allowed, false);
assert.equal(gate.confirmation_allowed, false);
assert.equal(blocker(gate, "adjacent_minutes_indistinguishable").status, "blocked");
@@ -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.15");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.16");
const accounting = fakeAccounting({
...receiptHandlers,
@@ -0,0 +1,210 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import test from "node:test";
import { candidateSetId } from "../src/lib/rectification-agentic/core/build-state.ts";
import { decideRectification } from "../src/lib/rectification-agentic/core/rectification-decision.ts";
import { INFERENCE_ALGORITHM_VERSION } from "../src/lib/rectification-agentic/core/types.ts";
import { signFromTransitions } from "../src/lib/rectification-agentic/v9/divergence-panel.ts";
import { RECTIFICATION_SKILL_VERSION } from "../src/lib/rectification-agentic/v9/case-status.ts";
import { latestResultToolProjection } from "../src/mastra/rectification-v9-tools.ts";
import {
OPEN_ENGINE_CAPABILITY_CEILING,
CANDIDATE_ID,
RESULT_ID,
SECOND_CANDIDATE_ID,
} from "./rectification-v9-test-support.ts";
const ELIMINATED_059_ID = "88888888-8888-4888-8888-888888888884";
const ELIMINATED_506_ID = "88888888-8888-4888-8888-888888888885";
const SKILL_SIGN_SENTENCE = "分盘上升只抄 `skill_verification_report.sign_by_candidate`,不得自行按换升时刻推算";
const ENGINE_CANDIDATES = [
{ candidateId: CANDIDATE_ID, time: "04:51", rank: 1, relativeSupport: 38, tiedMinuteCount: 29 },
{ candidateId: SECOND_CANDIDATE_ID, time: "04:53", rank: 2, relativeSupport: 36, tiedMinuteCount: 29 },
{ candidateId: ELIMINATED_059_ID, time: "04:59", rank: 3, relativeSupport: 14, tiedMinuteCount: 29 },
{ candidateId: ELIMINATED_506_ID, time: "05:06", rank: 4, relativeSupport: 12, tiedMinuteCount: 29 },
] as const;
function accidentInferenceState() {
const times = ENGINE_CANDIDATES.map((item) => item.time);
return {
algorithm_version: INFERENCE_ALGORITHM_VERSION,
candidate_set_id: candidateSetId("04:45", "05:13", times),
revision: 1,
phase: "discrimination" as const,
result_status: "credible_range" as const,
range_start: "04:45",
range_end: "05:13",
candidates: [
{
id: CANDIDATE_ID,
time: "04:51",
cluster_range: ["04:51", "04:53"] as const,
prior_score: 38,
posterior_score: 48,
probability: 0.4,
status: "active" as const,
rank: 2,
strong_conflict_count: 0,
},
{
id: SECOND_CANDIDATE_ID,
time: "04:53",
cluster_range: ["04:51", "04:53"] as const,
prior_score: 36,
posterior_score: 52,
probability: 0.52,
status: "active" as const,
rank: 1,
strong_conflict_count: 0,
},
{
id: ELIMINATED_059_ID,
time: "04:59",
cluster_range: ["04:59", "04:59"] as const,
prior_score: 14,
posterior_score: 8,
probability: 0.04,
status: "eliminated" as const,
rank: 3,
strong_conflict_count: 3,
},
{
id: ELIMINATED_506_ID,
time: "05:06",
cluster_range: ["05:06", "05:06"] as const,
prior_score: 12,
posterior_score: 6,
probability: 0.04,
status: "eliminated" as const,
rank: 4,
strong_conflict_count: 3,
},
],
events: [],
probes: [],
answered_probes: [],
rounds: [],
entropy: 1.1,
representative_time: "04:53",
credible_range: ["04:51", "04:53"] as const,
};
}
function accidentProjection() {
return latestResultToolProjection({
resultId: RESULT_ID,
candidates: ENGINE_CANDIDATES,
selectionAllowed: true,
confirmationAllowed: false,
representativeTime: "05:06",
selectedTime: null,
selectionKind: null,
algorithmVersion: "rectification-v5",
decisionReceipt: {
acceptance_allowed: true,
selection_allowed: true,
propose_allowed: true,
confirmation_allowed: false,
inference_state: accidentInferenceState(),
dasha_agreement: {
status: "conflict",
vimshottari_top: "05:06",
narayana_top: "04:59",
user_meaning: "主限更偏向 05:06,分盘大运更偏向 04:59。冲突时不能按更高把握收口。",
},
window_scan: {
scanned: true,
d9_lagna_count: 1,
d10_lagna_count: 2,
d9_candidates_differ: false,
d10_candidates_differ: true,
d9_sign_names: ["天秤座"],
d10_sign_names: ["巨蟹座", "狮子座"],
transitions: [
{
layer: "d10",
at: "05:00",
from_sign: "巨蟹座",
to_sign: "狮子座",
user_meaning: "D10 在 05:00 发生变化",
},
],
},
},
}, decideRectification({
engineCeiling: OPEN_ENGINE_CAPABILITY_CEILING,
methodCoverageAll: true,
trainingGateOpen: true,
candidateScores: ENGINE_CANDIDATES.map((item) => ({ time: item.time, score: item.relativeSupport })),
}));
}
function reportPacket(value: unknown): {
width_minutes: number | null;
dasha_agreement: { status: string; vimshottari_top: string | null; narayana_top: string | null; user_meaning: string } | null;
sign_by_candidate: Record<string, { d9: string | null; d10: string | null }>;
markdown: string;
} {
assert.ok(value && typeof value === "object" && !Array.isArray(value));
const row = value as Record<string, unknown>;
assert.equal(typeof row.markdown, "string");
assert.ok(row.sign_by_candidate && typeof row.sign_by_candidate === "object");
return row as ReturnType<typeof reportPacket>;
}
test("D10 still Cancer at 04:53 when the scan only flips to Leo at 05:00", () => {
const transitions = [
{ layer: "d10" as const, at: "05:00", from_sign: "巨蟹座", to_sign: "狮子座", user_meaning: "D10 在 05:00 发生变化" },
];
assert.equal(signFromTransitions(transitions, "d10", "04:51"), "巨蟹座");
assert.equal(signFromTransitions(transitions, "d10", "04:53"), "巨蟹座");
assert.equal(signFromTransitions(transitions, "d10", "05:00"), "狮子座");
});
test("delivery report uses the inference window and active dasha tops, not the pre-inference engine span", () => {
const projection = accidentProjection();
const report = reportPacket(projection.skill_verification_report);
assert.equal(report.width_minutes, 3);
assert.equal(projection.engine_indistinguishable_width_minutes, 29);
assert.equal(projection.indistinguishable_width_minutes, undefined);
const dasha = report.dasha_agreement;
assert.ok(dasha);
const tops = [dasha.vimshottari_top, dasha.narayana_top].filter(Boolean);
for (const time of tops) {
assert.ok(time === "04:51" || time === "04:53", time ?? "");
}
assert.notEqual(dasha.status, "conflict");
assert.doesNotMatch(report.markdown, /29 分钟/);
assert.doesNotMatch(report.markdown, /05:06/);
assert.doesNotMatch(report.markdown, /04:59/);
assert.match(report.markdown, /3 分钟/);
assert.match(report.markdown, /不是已确认的唯一出生分钟/);
const pre = projection.dasha_agreement_pre_inference as { vimshottari_top?: string; narayana_top?: string } | null;
assert.equal(pre?.vimshottari_top, "05:06");
assert.equal(pre?.narayana_top, "04:59");
const live = projection.dasha_agreement as { vimshottari_top?: string | null; narayana_top?: string | null };
assert.notEqual(live.vimshottari_top, "05:06");
assert.notEqual(live.narayana_top, "04:59");
});
test("delivery report gives 04:53 D10 as Cancer instead of letting the model infer Leo", () => {
const projection = accidentProjection();
const report = reportPacket(projection.skill_verification_report);
assert.equal(report.sign_by_candidate["04:53"]?.d10, "巨蟹座");
assert.equal(report.sign_by_candidate["04:51"]?.d10, "巨蟹座");
assert.doesNotMatch(report.markdown, /04:53[^\n]*狮子座/);
assert.match(report.markdown, /04:53 \| .*巨蟹座/);
});
test("skill 10.0.16 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.16");
assert.match(skill, /^version: 10\.0\.16$/m);
assert.match(skill, new RegExp(SKILL_SIGN_SENTENCE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
assert.match(comparison, new RegExp(SKILL_SIGN_SENTENCE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
});
@@ -993,7 +993,7 @@ test("read-case follows method plan and keeps D9/D10 type tables when SQL missin
internal_observations: Array<{ layer: string; ask_theme: string | null }>;
latest_result: {
confirmation_allowed: boolean;
indistinguishable_width_minutes: number;
engine_indistinguishable_width_minutes: number;
window_scan: { d9_candidates_differ: boolean } | null;
session_outcome: string;
};
@@ -1018,7 +1018,7 @@ test("read-case follows method plan and keeps D9/D10 type tables when SQL missin
assert.equal(projection.latest_result.session_outcome, "collect_evidence");
assert.equal(projection.internal_observations.find((item) => item.layer === "d9")?.ask_theme, "relationship_style");
assert.equal(projection.latest_result.confirmation_allowed, false);
assert.ok(projection.latest_result.indistinguishable_width_minutes >= 25);
assert.ok(projection.latest_result.engine_indistinguishable_width_minutes >= 25);
assert.equal(projection.latest_result.window_scan?.d9_candidates_differ, true);
assert.doesNotMatch(JSON.stringify(projection), UNIQUE_MINUTE_COPY);
assert.doesNotMatch(JSON.stringify(projection), /A\/B\/C\/D/);
@@ -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.15", () => {
test("public tool surface stays at 14 and new cases bind 10.0.16", () => {
assert.equal(PUBLIC_RECTIFICATION_TOOLS.length, 14);
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.15");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.16");
const deprecated = resolveExactSkillPackage(
"jyotish-birth-time-rectification",
"10.0.2",
@@ -1495,9 +1495,9 @@ test("public tool surface stays at 14 and new cases bind 10.0.15", () => {
}, collectDecision(plateauCandidates));
assert.equal(plateau.confirmation_allowed, false);
assert.equal(plateau.unique_minute_claim, false);
assert.match(String(plateau.skill_verification_report), /Dasha \+ Gochara/);
assert.match(String(plateau.skill_verification_report), /candidate_range_not_birth_time_truth/);
assert.doesNotMatch(String(plateau.skill_verification_report), UNIQUE_MINUTE_COPY);
assert.match(String((plateau.skill_verification_report as { markdown?: string }).markdown ?? plateau.skill_verification_report), /Dasha \+ Gochara/);
assert.match(String((plateau.skill_verification_report as { markdown?: string }).markdown ?? plateau.skill_verification_report), /candidate_range_not_birth_time_truth/);
assert.doesNotMatch(String((plateau.skill_verification_report as { markdown?: string }).markdown ?? plateau.skill_verification_report), UNIQUE_MINUTE_COPY);
assert.equal(plateau.session_outcome, "collect_evidence");
// Previously this asserted multi_adapter_consensus from execution names alone.
// The execution ledger does not prove same-range support or raw-result agreement.
@@ -576,8 +576,8 @@ function warnLines(run: () => Promise<unknown> | unknown) {
}).then((result) => ({ result, lines }));
}
test("skill version stays 10.0.15 after the range-delivery bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.15");
test("skill version stays 10.0.16 after the delivery-report-facts bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.16");
});
test("USER_COLLECT_QUESTION no longer has an other fallback", () => {
@@ -134,7 +134,7 @@ test("a 25-minute tied plateau projects width and forbids unique-minute confirma
trainingGateOpen: false,
candidateScores: candidates.map((item) => ({ time: item.time, score: item.relativeSupport })),
}));
assert.equal(projection.indistinguishable_width_minutes, width);
assert.equal(projection.engine_indistinguishable_width_minutes, width);
assert.equal(projection.confirmation_allowed, false);
assert.equal(
(projection.confirmation_gate as { confirmation_allowed: boolean }).confirmation_allowed,
@@ -194,13 +194,13 @@ test("read-case evidence context keeps day labels and confirm does not rewrite d
const projection = await (tools["rectification-read-case"] as unknown as {
execute(input: unknown): Promise<{
evidence_context: Array<{ date_precision: string; display_date_label: string }>;
latest_result: { confirmation_allowed: boolean; indistinguishable_width_minutes: number };
latest_result: { confirmation_allowed: boolean; engine_indistinguishable_width_minutes: number };
}>;
}).execute({ caseId: CASE_ID, projection: "full_diagnostics" });
assert.equal(projection.evidence_context[0]?.date_precision, "day");
assert.equal(projection.evidence_context[0]?.display_date_label, "2024-08-08");
assert.doesNotMatch(projection.evidence_context[0]?.display_date_label ?? "", /年份已确定为/);
assert.ok(projection.latest_result.indistinguishable_width_minutes >= 25);
assert.ok(projection.latest_result.engine_indistinguishable_width_minutes >= 25);
assert.equal(projection.latest_result.confirmation_allowed, false);
await (tools["rectification-confirm-evidence"] as unknown as {
@@ -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.15 and the prompt prefers batch ingest", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.15");
assert.match(skill, /^version: 10\.0\.15$/m);
test("new-case skill identity is 10.0.16 and the prompt prefers batch ingest", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.16");
assert.match(skill, /^version: 10\.0\.16$/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 stays 10.0.15 after the range-delivery bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.15");
test("skill version stays 10.0.16 after the delivery-report-facts bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.16");
});
test("nineteen-row ledger opens the training gate with four scoreable domains", () => {
@@ -426,8 +426,8 @@ function rpcDossier(decision: DecisionDossier) {
});
}
test("skill version stays 10.0.15 after the range-delivery bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.15");
test("skill version stays 10.0.16 after the delivery-report-facts bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.16");
});
test("pre-fix dual-exit constant is gone; range narration carries numbers and the disclaimer", () => {
@@ -85,8 +85,8 @@ function collectPersistResult(overrides: {
};
}
test("skill version stays 10.0.15 after the range-delivery bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.15");
test("skill version stays 10.0.16 after the delivery-report-facts bump", () => {
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.16");
});
test("cases current_question remains the submit contract, not a visual slot", () => {
@@ -88,11 +88,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.15"));
assert.ok(RECTIFICATION_V9_PACKAGE_PATH.endsWith("skills/jyotish-birth-time-rectification/versions/10.0.16"));
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.15");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.16");
});
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.15");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.16");
assert.match(skill, /^---\nname: jyotish-birth-time-rectification/m);
assert.match(skill, /^version: 10\.0\.15$/m);
assert.match(skill, /^version: 10\.0\.16$/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.15",
skill_version: "10.0.16",
};
}
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.15");
assert.equal(response.skillVersion, "10.0.16");
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.15");
assert.equal(openCall.args.p_skill_version, "10.0.16");
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);
+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.15",
sha256: "4cadf231520907b6faea9d06f698141e592e525c6bb91df43bc555be9dc5cf8c",
version: "10.0.16",
sha256: "b25dca7fe3630aceaf152ec99d37a2d946e4d71f1f191fc9071e11bc297e763f",
},
{
name: "jyotish-personal-report",