Ask dated dasha probes first; D9/D10 and nakshatra wait until that pool is empty, score at half weight, and never eliminate. Skill 10.0.21. Co-authored-by: Cursor <cursoragent@cursor.com>
264 lines
11 KiB
TypeScript
264 lines
11 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import test from "node:test";
|
||
|
||
import { applyProbeOutcome } from "../src/lib/rectification-agentic/core/apply-probe-outcome.ts";
|
||
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
|
||
import {
|
||
buildCandidateContrastPacket,
|
||
conflictProbesFromContrast,
|
||
inspectDiscriminatorProbes,
|
||
} from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts";
|
||
import { asInferenceState } from "../src/lib/rectification-agentic/core/compose-receipt.ts";
|
||
import { decisionStateFingerprint } from "../src/lib/rectification-agentic/core/decision-fingerprint.ts";
|
||
import type { AnswerClass, ConflictProbe } from "../src/lib/rectification-agentic/core/types.ts";
|
||
|
||
function twoGroupStylePacket() {
|
||
return buildCandidateContrastPacket({
|
||
candidateSetVersion: "05:00-05:04",
|
||
candidateTimes: ["05:00", "05:04"],
|
||
transitions: [
|
||
{ layer: "d9", at: "05:04", from_sign: "巨蟹座", to_sign: "狮子座" },
|
||
],
|
||
});
|
||
}
|
||
|
||
function threeGroupStylePacket() {
|
||
return 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: "处女座" },
|
||
],
|
||
});
|
||
}
|
||
|
||
function styleProbeFrom(packet: ReturnType<typeof buildCandidateContrastPacket>): ConflictProbe {
|
||
const probe = conflictProbesFromContrast(packet).find((item) => item.source === "varga_contrast");
|
||
assert.ok(probe);
|
||
return probe;
|
||
}
|
||
|
||
function absSupportDelta(probe: ConflictProbe, answer: AnswerClass, time: string): number {
|
||
const scores = Object.fromEntries(probe.candidate_ids.map((id) => [id, 10]));
|
||
const applied = applyProbeOutcome(scores, probe, answer);
|
||
return Math.abs(applied.deltas[time] ?? 0);
|
||
}
|
||
|
||
test("two-group varga_style A and B move their groups by the same absolute delta", () => {
|
||
const probe = styleProbeFrom(twoGroupStylePacket());
|
||
assert.equal(probe.choice_kind, "varga_style");
|
||
const yesGroup = probe.expected_outcomes.find((row) => row.answer_class === "yes")?.supports[0];
|
||
const weakGroup = probe.expected_outcomes.find((row) => row.answer_class === "weak_yes")?.supports[0];
|
||
assert.ok(yesGroup);
|
||
assert.ok(weakGroup);
|
||
assert.notEqual(yesGroup, weakGroup);
|
||
const yesDelta = absSupportDelta(probe, "yes", yesGroup);
|
||
const weakDelta = absSupportDelta(probe, "weak_yes", weakGroup);
|
||
// 原值: 2 and 2(与带年月题同权)
|
||
// 新值: 1 and 1(仍相等)
|
||
// 原因: BUG-629 决策 2,PROBE_WEIGHT.yearless = 0.5
|
||
assert.equal(yesDelta, 1);
|
||
assert.equal(weakDelta, 1);
|
||
assert.equal(yesDelta, weakDelta);
|
||
const unsure = applyProbeOutcome(
|
||
Object.fromEntries(probe.candidate_ids.map((id) => [id, 10])),
|
||
probe,
|
||
"unsure",
|
||
);
|
||
assert.ok(Object.values(unsure.deltas).every((value) => value === 0));
|
||
});
|
||
|
||
test("three-group varga_style A/B/C each carry full peer weight", () => {
|
||
const probe = styleProbeFrom(threeGroupStylePacket());
|
||
assert.equal(probe.choice_kind, "varga_style");
|
||
const scored = (["yes", "weak_yes", "no"] as const).map((answer) => {
|
||
const support = probe.expected_outcomes.find((row) => row.answer_class === answer)?.supports[0];
|
||
assert.ok(support, answer);
|
||
return absSupportDelta(probe, answer, support);
|
||
});
|
||
// 原值: [2, 2, 2]
|
||
// 新值: [1, 1, 1]
|
||
// 原因: BUG-629 决策 2,三组性格题同样减半
|
||
assert.deepEqual(scored, [1, 1, 1]);
|
||
});
|
||
|
||
test("old ConflictProbe receipts without choice_kind keep half-weight weak_yes", () => {
|
||
const produced = styleProbeFrom(twoGroupStylePacket());
|
||
const legacy: ConflictProbe = {
|
||
id: produced.id,
|
||
semantic_key: produced.semantic_key,
|
||
candidate_split_hash: produced.candidate_split_hash,
|
||
domain: produced.domain,
|
||
year: produced.year,
|
||
question: produced.question,
|
||
candidate_ids: produced.candidate_ids,
|
||
expected_outcomes: produced.expected_outcomes,
|
||
information_gain: produced.information_gain,
|
||
source: produced.source,
|
||
};
|
||
assert.equal(legacy.choice_kind, undefined);
|
||
|
||
const state = buildInferenceState({
|
||
range_start: "05:00",
|
||
range_end: "05:04",
|
||
candidates: [
|
||
{ id: "05:00", time: "05:00", relative_support: 34 },
|
||
{ id: "05:04", time: "05:04", relative_support: 33 },
|
||
],
|
||
events: [{ id: "e-rel", domain: "relationship", year: 2024, precision: "year" }],
|
||
probes: [legacy],
|
||
});
|
||
const loaded = asInferenceState(JSON.parse(JSON.stringify(state)));
|
||
assert.ok(loaded);
|
||
const loadedProbe = loaded.probes.find((item) => item.id === produced.id);
|
||
assert.ok(loadedProbe);
|
||
assert.equal(loadedProbe.choice_kind, undefined);
|
||
|
||
const weakGroup = loadedProbe.expected_outcomes.find((row) => row.answer_class === "weak_yes")?.supports[0];
|
||
const yesGroup = loadedProbe.expected_outcomes.find((row) => row.answer_class === "yes")?.supports[0];
|
||
assert.ok(weakGroup);
|
||
assert.ok(yesGroup);
|
||
assert.equal(absSupportDelta(loadedProbe, "weak_yes", weakGroup), 1);
|
||
assert.equal(absSupportDelta(loadedProbe, "yes", yesGroup), 2);
|
||
assert.equal(loaded.candidate_set_id, state.candidate_set_id);
|
||
assert.equal(loaded.revision, state.revision);
|
||
});
|
||
|
||
test("varga_style B-option weak_yes counts as strong conflict; existence weak_yes does not", () => {
|
||
const style = styleProbeFrom(twoGroupStylePacket());
|
||
assert.equal(style.choice_kind, "varga_style");
|
||
const styleConflicted = style.expected_outcomes.find((row) => row.answer_class === "weak_yes")?.conflicts[0];
|
||
assert.ok(styleConflicted);
|
||
let styleScores = Object.fromEntries(style.candidate_ids.map((id) => [id, 10]));
|
||
let styleCounts: Record<string, number> = {};
|
||
let eliminated = new Set<string>();
|
||
for (let round = 0; round < 3; round += 1) {
|
||
const applied = applyProbeOutcome(styleScores, style, "weak_yes", {
|
||
strongConflictCounts: styleCounts,
|
||
eliminatedIds: eliminated,
|
||
});
|
||
styleScores = { ...applied.scores };
|
||
styleCounts = { ...applied.strong_conflict_counts };
|
||
eliminated = new Set(applied.eliminated_ids);
|
||
}
|
||
// 原值: 三次 B 选项计 3 次强冲突并淘汰
|
||
// 新值: strong_conflict_count 仍为 0,不淘汰
|
||
// 原因: BUG-629 决策 2,性格题不计淘汰
|
||
assert.equal(styleCounts[styleConflicted], 0);
|
||
assert.equal(eliminated.has(styleConflicted), false);
|
||
|
||
const existencePacket = buildCandidateContrastPacket({
|
||
candidateSetVersion: "05:00-05:04",
|
||
candidateTimes: ["05:00", "05:04"],
|
||
transitions: [{ layer: "d9", at: "05:04" }],
|
||
});
|
||
const existence = conflictProbesFromContrast(existencePacket).find((item) => item.choice_kind === "existence");
|
||
assert.ok(existence);
|
||
const existenceConflicted = existence.expected_outcomes.find((row) => row.answer_class === "weak_yes")?.conflicts[0];
|
||
assert.ok(existenceConflicted);
|
||
let existenceScores = Object.fromEntries(existence.candidate_ids.map((id) => [id, 10]));
|
||
let existenceCounts: Record<string, number> = {};
|
||
for (let round = 0; round < 3; round += 1) {
|
||
const applied = applyProbeOutcome(existenceScores, existence, "weak_yes", {
|
||
strongConflictCounts: existenceCounts,
|
||
});
|
||
existenceScores = { ...applied.scores };
|
||
existenceCounts = { ...applied.strong_conflict_counts };
|
||
assert.equal(applied.eliminated_ids.length, 0);
|
||
}
|
||
assert.equal(existenceCounts[existenceConflicted], 0);
|
||
});
|
||
|
||
test("varga_style contrast writeback keeps style_options for the next round", () => {
|
||
const probe = styleProbeFrom(twoGroupStylePacket());
|
||
assert.equal(probe.choice_kind, "varga_style");
|
||
assert.ok((probe.style_options?.length ?? 0) >= 2);
|
||
assert.ok(probe.style_options?.every((item) => item.label.trim().length > 0));
|
||
});
|
||
|
||
test("engine varga.d9/d10 without style_options scores with the render effective kind", () => {
|
||
for (const semanticKey of ["varga.d9", "varga.d10"] as const) {
|
||
const packet = {
|
||
candidateSetVersion: "05:00-05:04",
|
||
vargaDifferences: [],
|
||
probes: [{
|
||
probeId: `contrast:${semanticKey}.engine-no-signs`,
|
||
candidateSetVersion: "05:00-05:04",
|
||
question: "那几年相处更接近哪一种?",
|
||
expectedOutcomes: [
|
||
{ outcomeId: "supports_05:00", supportsCandidateIds: ["05:00"], conflictsCandidateIds: ["05:04"] },
|
||
{ outcomeId: "supports_05:04", supportsCandidateIds: ["05:04"], conflictsCandidateIds: ["05:00"] },
|
||
],
|
||
candidateSplitHash: `05:00-05:04:${semanticKey}.engine-no-signs`,
|
||
informationGain: 1.2,
|
||
sourceFeatures: [{ technique: semanticKey.slice(6).toUpperCase(), calculationResultId: null }],
|
||
domain: "relationship",
|
||
year: null,
|
||
semanticKey,
|
||
choiceKind: "varga_style" as const,
|
||
}],
|
||
};
|
||
const scored = conflictProbesFromContrast(packet);
|
||
const inspected = inspectDiscriminatorProbes(packet);
|
||
assert.equal(scored.length, 1, semanticKey);
|
||
assert.equal(inspected.selected, null, semanticKey);
|
||
assert.equal(inspected.dropped.some((item) => (
|
||
item.semantic_key === semanticKey && item.reason === "yearless_ungrounded_contrast"
|
||
)), true, semanticKey);
|
||
assert.equal(scored[0]?.choice_kind, "existence", semanticKey);
|
||
}
|
||
});
|
||
|
||
test("replaying a varga_style probe keeps candidate_set_id and a monotonic revision", () => {
|
||
const probe = styleProbeFrom(twoGroupStylePacket());
|
||
const before = buildInferenceState({
|
||
range_start: "05:00",
|
||
range_end: "05:04",
|
||
candidates: [
|
||
{ id: "05:00", time: "05:00", relative_support: 34 },
|
||
{ id: "05:04", time: "05:04", relative_support: 33 },
|
||
],
|
||
events: [{ id: "e-rel", domain: "relationship", year: 2024, precision: "year" }],
|
||
probes: [probe],
|
||
});
|
||
const after = buildInferenceState({
|
||
range_start: before.range_start,
|
||
range_end: before.range_end,
|
||
candidates: before.candidates.map((item) => ({
|
||
id: item.id,
|
||
time: item.time,
|
||
relative_support: item.prior_score,
|
||
})),
|
||
events: before.events,
|
||
probes: before.probes,
|
||
previous: before,
|
||
answered_probes: [{
|
||
probe_id: probe.id,
|
||
semantic_key: probe.semantic_key,
|
||
candidate_split_hash: probe.candidate_split_hash,
|
||
answer_class: "weak_yes",
|
||
classified_from: "choice",
|
||
}],
|
||
});
|
||
assert.equal(after.candidate_set_id, before.candidate_set_id);
|
||
assert.ok(after.revision >= before.revision);
|
||
const beforeFp = decisionStateFingerprint({
|
||
caseId: "case-style-weight",
|
||
evidenceLedgerFingerprint: "fp-a",
|
||
candidateSetId: before.candidate_set_id,
|
||
inferenceRevision: before.revision,
|
||
answeredProbeIds: before.answered_probes.map((item) => item.probe_id),
|
||
scoringPolicyVersion: "policy-v2",
|
||
});
|
||
const afterFp = decisionStateFingerprint({
|
||
caseId: "case-style-weight",
|
||
evidenceLedgerFingerprint: "fp-a",
|
||
candidateSetId: after.candidate_set_id,
|
||
inferenceRevision: after.revision,
|
||
answeredProbeIds: after.answered_probes.map((item) => item.probe_id),
|
||
scoringPolicyVersion: "policy-v2",
|
||
});
|
||
assert.notEqual(afterFp, beforeFp);
|
||
});
|