0eb740a6b8
Reserve a month-or-better holdout, degrade unsigned D9/D10 to existence, and treat ledger keywords as mention-only so remaining discriminators stay in the pool. Co-authored-by: Cursor <cursoragent@cursor.com>
199 lines
7.4 KiB
TypeScript
199 lines
7.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { applyProbeOutcome, outcomeForAnswer } from "../src/lib/rectification-agentic/core/apply-probe-outcome.ts";
|
|
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
|
|
import { distinguishContractErrors } from "../src/lib/rectification-agentic/core/distinguish-contract.ts";
|
|
import { probeFromEngine } from "../src/lib/rectification-agentic/core/probes-from-engine.ts";
|
|
import { parseDiscriminatingEventProbes } from "../src/lib/rectification-agentic/v9/refinement-packet.ts";
|
|
|
|
test("CI forbids distinguish probes with empty mapping or non-positive gain", () => {
|
|
assert.deepEqual(distinguishContractErrors({
|
|
role: "distinguish",
|
|
information_gain: 0,
|
|
candidate_ids: ["05:00", "05:20"],
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] },
|
|
{ answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] },
|
|
],
|
|
}), ["distinguish_non_positive_information_gain"]);
|
|
assert.deepEqual(distinguishContractErrors({
|
|
role: "distinguish",
|
|
information_gain: 0.4,
|
|
candidate_ids: [],
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: [], conflicts: [] },
|
|
{ answer_class: "no", supports: [], conflicts: [] },
|
|
],
|
|
}), ["distinguish_empty_candidate_ids"]);
|
|
assert.deepEqual(distinguishContractErrors({
|
|
role: "distinguish",
|
|
information_gain: 0.4,
|
|
candidate_ids: ["05:00", "05:20"],
|
|
expected_outcomes: [],
|
|
}), ["distinguish_empty_expected_outcomes"]);
|
|
});
|
|
|
|
test("receipt parser drops invalid distinguish probes and known_event_quality", () => {
|
|
const parsed = parseDiscriminatingEventProbes([
|
|
{
|
|
year: 2016,
|
|
year_label: "2016 年前后",
|
|
domain: "education",
|
|
event_family: "学业变化",
|
|
source: "known_event_quality",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "clarification only",
|
|
role: "distinguish",
|
|
information_gain: 0,
|
|
},
|
|
{
|
|
year: 2018,
|
|
year_label: "2018 年前后",
|
|
domain: "career",
|
|
event_family: "职责变化",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "engine locked year and family",
|
|
role: "distinguish",
|
|
information_gain: 0.4,
|
|
candidate_ids: ["05:00", "05:20"],
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] },
|
|
{ answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] },
|
|
],
|
|
},
|
|
]);
|
|
assert.equal(parsed.length, 1);
|
|
assert.equal(parsed[0]?.domain, "career");
|
|
assert.equal(parsed[0]?.source, "dasha_activation");
|
|
assert.ok((parsed[0]?.information_gain ?? 0) > 0);
|
|
});
|
|
|
|
test("receipt parser keeps engine month on dasha boundary probes", () => {
|
|
const parsed = parseDiscriminatingEventProbes([{
|
|
year: 2018,
|
|
month: 3,
|
|
year_label: "2018 年 3 月前后",
|
|
domain: "career",
|
|
event_family: "职责变化",
|
|
source: "dasha_boundary",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "时间范围锁定 2018 年 3 月前后",
|
|
role: "distinguish",
|
|
information_gain: 0.4,
|
|
semantic_key: "career.2018.03.dasha_boundary",
|
|
candidate_ids: ["05:00", "05:20"],
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] },
|
|
{ answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] },
|
|
],
|
|
}]);
|
|
assert.equal(parsed[0]?.month, 3);
|
|
assert.equal(parsed[0]?.year_label, "2018 年 3 月前后");
|
|
assert.equal(parsed[0]?.source, "dasha_boundary");
|
|
});
|
|
|
|
test("randomized hidden mutated answers change posterior only when mapped", () => {
|
|
const probe = probeFromEngine({
|
|
year: 2018,
|
|
year_label: "2018 年前后",
|
|
domain: "career",
|
|
event_family: "职责变化",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "engine locked year and family",
|
|
role: "distinguish",
|
|
information_gain: 0.4,
|
|
semantic_key: "career.2018",
|
|
candidate_split_hash: "set:career:2018",
|
|
candidate_ids: ["05:00", "05:20"],
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] },
|
|
{ answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] },
|
|
{ answer_class: "unsure", supports: [], conflicts: [] },
|
|
],
|
|
});
|
|
assert.ok(probe);
|
|
const scores = { "05:00": 10, "05:20": 10 };
|
|
const yes = applyProbeOutcome(scores, probe, "yes");
|
|
assert.equal(yes.kind, "informative");
|
|
assert.notDeepEqual(yes.scores, scores);
|
|
assert.ok(yes.deltas["05:00"] !== 0);
|
|
const mutated = applyProbeOutcome(scores, probe, "unsure");
|
|
assert.equal(mutated.kind, "low_information");
|
|
assert.deepEqual(mutated.scores, scores);
|
|
const state = buildInferenceState({
|
|
range_start: "05:00",
|
|
range_end: "05:20",
|
|
candidates: [
|
|
{ id: "05:00", time: "05:00", relative_support: 10 },
|
|
{ id: "05:20", time: "05:20", relative_support: 10 },
|
|
],
|
|
events: [
|
|
{ id: "e1", domain: "education", year: 2016, precision: "month" },
|
|
{ id: "e2", domain: "career", year: 2018, precision: "year" },
|
|
{ id: "e3", domain: "family", year: 2020, precision: "year" },
|
|
],
|
|
probes: [probe],
|
|
answered_probes: [{
|
|
probe_id: probe.id,
|
|
semantic_key: probe.semantic_key,
|
|
candidate_split_hash: probe.candidate_split_hash,
|
|
answer_class: "unsure",
|
|
classified_from: "choice",
|
|
}],
|
|
});
|
|
assert.equal(state.last_inference_round?.kind, "low_information");
|
|
assert.deepEqual(state.last_inference_round?.scores_before, state.last_inference_round?.scores_after);
|
|
assert.equal(
|
|
state.rounds.filter((item) => item.kind === "informative").length,
|
|
0,
|
|
);
|
|
});
|
|
|
|
test("existence weak_yes shares yes mapping at half weight and never eliminates on one answer", () => {
|
|
const probe = probeFromEngine({
|
|
year: 2018,
|
|
year_label: "2018 年前后",
|
|
domain: "career",
|
|
event_family: "职责变化",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "时间范围锁定 2018 年前后",
|
|
role: "distinguish",
|
|
information_gain: 0.4,
|
|
semantic_key: "career.2018",
|
|
candidate_split_hash: "set:career:2018",
|
|
candidate_ids: ["05:00", "05:20"],
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] },
|
|
{ answer_class: "weak_yes", supports: ["05:00"], conflicts: ["05:20"] },
|
|
{ answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] },
|
|
{ answer_class: "unsure", supports: [], conflicts: [] },
|
|
],
|
|
});
|
|
assert.ok(probe);
|
|
assert.deepEqual(outcomeForAnswer(probe, "yes")?.supports, outcomeForAnswer(probe, "weak_yes")?.supports);
|
|
assert.deepEqual(outcomeForAnswer(probe, "yes")?.conflicts, outcomeForAnswer(probe, "weak_yes")?.conflicts);
|
|
const scores = { "05:00": 10, "05:20": 10 };
|
|
const yes = applyProbeOutcome(scores, probe, "yes");
|
|
const weak = applyProbeOutcome(scores, probe, "weak_yes");
|
|
assert.equal(yes.deltas["05:00"], 2);
|
|
assert.equal(yes.deltas["05:20"], -2);
|
|
assert.equal(weak.deltas["05:00"], 1);
|
|
assert.equal(weak.deltas["05:20"], -1);
|
|
assert.deepEqual(weak.eliminated_ids, []);
|
|
assert.equal(weak.kind, "informative");
|
|
});
|