b43808b016
Only sign-bound varga_style questions may omit a concrete period. Remaining-layer existence and quality probes now drop as yearless_ungrounded_contrast instead of scoring by group order. Co-authored-by: Cursor <cursoragent@cursor.com>
236 lines
10 KiB
TypeScript
236 lines
10 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { readFileSync } from "node:fs";
|
|
|
|
import {
|
|
buildCandidateContrastPacket,
|
|
inspectDiscriminatorProbes,
|
|
isSameCandidateSplit,
|
|
mentionedVargaKeysFromLedgerEvidence,
|
|
remainingVargaSplits,
|
|
selectDiscriminatorProbe,
|
|
splitHashForCandidateSet,
|
|
volunteeredDomainsFromEvidence,
|
|
} from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts";
|
|
|
|
test("remaining D10 three-way outranks a skewed D24 split", () => {
|
|
const packet = buildCandidateContrastPacket({
|
|
candidateSetVersion: "05:00-05:04",
|
|
candidateTimes: ["05:00", "05:03", "05:04"],
|
|
transitions: [
|
|
{ layer: "d10", at: "05:03", from_sign: "巨蟹座", to_sign: "狮子座" },
|
|
{ layer: "d10", at: "05:04", from_sign: "狮子座", to_sign: "处女座" },
|
|
{ layer: "d24", at: "05:04", from_sign: "白羊座", to_sign: "金牛座" },
|
|
],
|
|
});
|
|
const probe = selectDiscriminatorProbe(packet);
|
|
assert.ok(probe);
|
|
assert.equal(probe.domain, "career");
|
|
assert.equal(probe.choiceKind, "varga_style");
|
|
assert.match(probe.semanticKey, /varga\.d10/);
|
|
assert.doesNotMatch(probe.semanticKey, /varga\.d24/);
|
|
assert.ok(packet.probes.some((item) => item.semanticKey.startsWith("varga.d24.")));
|
|
assert.ok(packet.probes.some((item) => item.semanticKey.startsWith("varga.d10.")));
|
|
assert.equal(probe.styleOptions?.length, 4);
|
|
assert.equal(probe.expectedOutcomes.length, 4);
|
|
assert.deepEqual(probe.expectedOutcomes.filter((row) => row.outcomeId !== "unsure").map((row) => row.supportsCandidateIds), [
|
|
["05:00"],
|
|
["05:03"],
|
|
["05:04"],
|
|
]);
|
|
});
|
|
|
|
test("two-way remaining D9 keeps the other minute on weak_yes, not no", () => {
|
|
const packet = buildCandidateContrastPacket({
|
|
candidateSetVersion: "05:00-05:04",
|
|
candidateTimes: ["05:00", "05:04"],
|
|
transitions: [
|
|
{ layer: "d9", at: "05:04", from_sign: "巨蟹座", to_sign: "狮子座" },
|
|
],
|
|
});
|
|
const probe = selectDiscriminatorProbe(packet);
|
|
assert.ok(probe);
|
|
assert.equal(probe.choiceKind, "varga_style");
|
|
assert.equal(probe.styleOptions?.length, 4);
|
|
assert.deepEqual(probe.expectedOutcomes.find((row) => row.outcomeId === "yes")?.supportsCandidateIds, ["05:00"]);
|
|
assert.deepEqual(probe.expectedOutcomes.find((row) => row.outcomeId === "weak_yes")?.supportsCandidateIds, ["05:04"]);
|
|
assert.deepEqual(probe.expectedOutcomes.find((row) => row.outcomeId === "no")?.supportsCandidateIds, []);
|
|
assert.equal(probe.styleOptions?.some((item) => item.answerClass === "unsure"), true);
|
|
assert.doesNotMatch(probe.question, /不得发明年份|Opportunity/);
|
|
assert.match(probe.authoringHint ?? "", /不得发明年份/);
|
|
assert.match(probe.question, /相处|关系/);
|
|
});
|
|
|
|
test("remaining D7 enters the pool as family existence", () => {
|
|
const splits = remainingVargaSplits(
|
|
["05:00", "05:04"],
|
|
[{ layer: "d7", at: "05:04", from_sign: "金牛座", to_sign: "双子座" }],
|
|
);
|
|
assert.equal(splits[0]?.layer, "d7");
|
|
const packet = buildCandidateContrastPacket({
|
|
candidateSetVersion: "05:00-05:04",
|
|
candidateTimes: ["05:00", "05:04"],
|
|
remainingSplits: splits,
|
|
});
|
|
assert.ok(packet.probes.some((item) => item.domain === "family" && item.choiceKind === "existence"));
|
|
const inspected = inspectDiscriminatorProbes(packet);
|
|
assert.equal(inspected.selected, null);
|
|
assert.equal(inspected.dropped.some((item) => item.reason === "yearless_ungrounded_contrast"), true);
|
|
});
|
|
|
|
test("finance remaining splits stay out unless volunteered", () => {
|
|
const hidden = remainingVargaSplits(
|
|
["05:00", "05:04"],
|
|
[{ layer: "d2", at: "05:04" }],
|
|
);
|
|
assert.equal(hidden.length, 0);
|
|
const shown = remainingVargaSplits(
|
|
["05:00", "05:04"],
|
|
[{ layer: "d2", at: "05:04" }],
|
|
volunteeredDomainsFromEvidence([{ status: "confirmed", domain: "finance" }]),
|
|
);
|
|
assert.equal(shown[0]?.layer, "d2");
|
|
});
|
|
|
|
test("existence probes leave the catalog once that domain already has dated evidence", () => {
|
|
const packet = buildCandidateContrastPacket({
|
|
candidateSetVersion: "05:00-05:14",
|
|
engineProbes: [{
|
|
semantic_key: "career.2023.dasha_activation",
|
|
domain: "career",
|
|
year: 2023,
|
|
user_meaning: "时间范围锁定 2023 年前后。",
|
|
information_gain: 0.56,
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:14"] },
|
|
{ answer_class: "no", supports: ["05:14"], conflicts: ["05:00"] },
|
|
],
|
|
}],
|
|
providedDomains: ["career"],
|
|
candidateTimes: ["05:00", "05:07", "05:14"],
|
|
transitions: [
|
|
{ layer: "d24", at: "05:07", from_sign: "白羊座", to_sign: "金牛座" },
|
|
{ layer: "d24", at: "05:14", from_sign: "金牛座", to_sign: "双子座" },
|
|
],
|
|
});
|
|
assert.equal(packet.probes.some((item) => item.semanticKey.includes("career.2023")), false);
|
|
assert.ok(packet.probes.some((item) => item.semanticKey.startsWith("varga.d24.")));
|
|
const inspected = inspectDiscriminatorProbes(packet);
|
|
assert.equal(inspected.selected, null);
|
|
assert.equal(inspected.dropped.some((item) => (
|
|
item.semantic_key.startsWith("varga.d24.") && item.reason === "yearless_ungrounded_contrast"
|
|
)), true);
|
|
});
|
|
|
|
test("D9 remaining split without signs stays in the packet as existence but is not a card", () => {
|
|
const packet = buildCandidateContrastPacket({
|
|
candidateSetVersion: "05:00-05:04",
|
|
candidateTimes: ["05:00", "05:04"],
|
|
transitions: [{ layer: "d9", at: "05:04" }],
|
|
});
|
|
const inPacket = packet.probes.find((item) => item.semanticKey.startsWith("varga.d9."));
|
|
assert.ok(inPacket);
|
|
assert.equal(inPacket.choiceKind, "existence");
|
|
const inspected = inspectDiscriminatorProbes(packet);
|
|
assert.equal(inspected.selected, null);
|
|
assert.equal(inspected.dropped.some((item) => (
|
|
item.semantic_key.startsWith("varga.d9.") && item.reason === "yearless_ungrounded_contrast"
|
|
)), true);
|
|
});
|
|
|
|
test("occupation mention does not skip remaining D10; answered varga.d10 still does", () => {
|
|
const evidence = [{
|
|
status: "confirmed",
|
|
domain: "occupation",
|
|
eventKind: "occupation_note",
|
|
summary: "互联网程序员 / 前端",
|
|
}];
|
|
const mentioned = mentionedVargaKeysFromLedgerEvidence(evidence);
|
|
assert.ok(mentioned.includes("varga.d10"));
|
|
const transitions = [
|
|
{ layer: "d10", at: "05:03", from_sign: "巨蟹座", to_sign: "狮子座" },
|
|
{ layer: "d10", at: "05:04", from_sign: "狮子座", to_sign: "处女座" },
|
|
{ layer: "d4", at: "05:04", from_sign: "金牛座", to_sign: "双子座" },
|
|
];
|
|
const times = ["05:00", "05:03", "05:04"];
|
|
const mentionedPacket = buildCandidateContrastPacket({
|
|
candidateSetVersion: "05:00-05:04",
|
|
candidateTimes: times,
|
|
transitions,
|
|
mentionedKeys: mentioned,
|
|
});
|
|
assert.ok(mentionedPacket.probes.some((item) => item.semanticKey.startsWith("varga.d10.")));
|
|
assert.match(
|
|
selectDiscriminatorProbe(mentionedPacket, { mentionedKeys: mentioned })?.semanticKey ?? "",
|
|
/^varga\.d10\./,
|
|
);
|
|
const answeredPacket = buildCandidateContrastPacket({
|
|
candidateSetVersion: "05:00-05:04",
|
|
candidateTimes: times,
|
|
transitions,
|
|
askedKeys: ["varga.d10"],
|
|
});
|
|
assert.equal(answeredPacket.probes.some((item) => item.semanticKey.startsWith("varga.d10.")), false);
|
|
assert.equal(selectDiscriminatorProbe(answeredPacket)?.semanticKey.startsWith("varga.d4.") ?? false, false);
|
|
});
|
|
|
|
test("a stale candidate-set prefix is re-minted instead of stacked", () => {
|
|
const current = "05:00-05:07:05:00,05:06,05:07";
|
|
const stale = "05:00-05:07:05:00,05:05,05:07";
|
|
const key = "varga.d9.05:00|05:06/05:07";
|
|
assert.equal(splitHashForCandidateSet(`${stale}:${key}`, key, current), `${current}:${key}`);
|
|
assert.equal(splitHashForCandidateSet(`${current}:${key}`, key, current), `${current}:${key}`);
|
|
assert.equal(splitHashForCandidateSet(null, key, current), `${current}:${key}`);
|
|
// opaque engine hashes stay scoped exactly once
|
|
assert.equal(splitHashForCandidateSet("f4cc4975", "career.2023", current), `${current}:f4cc4975`);
|
|
assert.equal(
|
|
splitHashForCandidateSet(`${current}:f4cc4975`, "career.2023", current),
|
|
`${current}:f4cc4975`,
|
|
);
|
|
});
|
|
|
|
test("engine probes keep one candidate-set prefix after the set changes", () => {
|
|
const current = "05:00-05:07:05:00,05:06,05:07";
|
|
const stale = "05:00-05:07:05:00,05:05,05:07";
|
|
const key = "varga.d9.05:00|05:06/05:07";
|
|
const packet = buildCandidateContrastPacket({
|
|
candidateSetVersion: current,
|
|
candidateTimes: ["05:00", "05:06", "05:07"],
|
|
engineProbes: [{
|
|
semantic_key: key,
|
|
candidate_split_hash: `${stale}:${key}`,
|
|
domain: "relationship",
|
|
user_meaning: "亲密关系里更接近下面哪一种相处方式?",
|
|
information_gain: 1.53,
|
|
choice_kind: "varga_style",
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:06", "05:07"] },
|
|
{ answer_class: "no", supports: ["05:07"], conflicts: ["05:00", "05:06"] },
|
|
],
|
|
}],
|
|
});
|
|
const probe = packet.probes.find((item) => item.semanticKey === key);
|
|
assert.ok(probe);
|
|
assert.equal(probe.candidateSplitHash, `${current}:${key}`);
|
|
assert.doesNotMatch(probe.candidateSplitHash, /05:05/);
|
|
});
|
|
|
|
test("split identity ignores the candidate-set prefix but not the probe", () => {
|
|
const key = "varga.d9.05:00|05:06/05:07";
|
|
assert.equal(isSameCandidateSplit(`a:${key}`, `b:${key}`, key), true);
|
|
assert.equal(isSameCandidateSplit(`a:${key}`, `a:varga.d10.05:00|05:06/05:07`, key), false);
|
|
assert.equal(isSameCandidateSplit("f4cc4975", "a:f4cc4975", "career.2023"), false);
|
|
assert.equal(isSameCandidateSplit("f4cc4975", "f4cc4975", "career.2023"), true);
|
|
});
|
|
|
|
test("scoring scopes the contrast packet to the candidate set it just produced", () => {
|
|
const source = readFileSync(new URL("../src/mastra/rectification-v9-tools.ts", import.meta.url), "utf8");
|
|
const packetCall = source.slice(
|
|
source.indexOf("const scoredCandidateSetId = candidateSetId("),
|
|
source.indexOf("const inference = buildCaseInferenceState("),
|
|
);
|
|
assert.ok(packetCall.includes("candidateSetVersion: scoredCandidateSetId"));
|
|
assert.doesNotMatch(packetCall, /candidateSetVersion:\s*previousInferenceFromReceipt/);
|
|
});
|