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>
255 lines
10 KiB
TypeScript
255 lines
10 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);
|
|
assert.equal(yesDelta, 2);
|
|
assert.equal(weakDelta, 2);
|
|
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);
|
|
});
|
|
assert.deepEqual(scored, [2, 2, 2]);
|
|
});
|
|
|
|
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);
|
|
}
|
|
assert.equal(styleCounts[styleConflicted], 3);
|
|
assert.equal(eliminated.has(styleConflicted), true);
|
|
|
|
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);
|
|
});
|