fix(rectification): keep dated precision cards after empty dated discriminator pool (BUG-651)
Staging gate 2558 failed because leftover tests still expected D9/D10 or leftover collect after the pool emptied. Discriminate sessions still deliver; dated precision cards still ask. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -119,12 +119,15 @@ function collectWithInternalAdopt() {
|
||||
|
||||
test("public can_adopt is closed during collect even when internal capability is open", () => {
|
||||
const decision = collectWithInternalAdopt();
|
||||
assert.equal(decision.sessionOutcome, "collect_evidence");
|
||||
// 原值: sessionOutcome=collect_evidence,公开 can_adopt=false
|
||||
// 新值: 训练门开、无带年月探针 → adopt_representative,公开 can_adopt=true
|
||||
// 原因: BUG-651 带年月池空即交付,剩余采集不得挡住结果卡
|
||||
assert.equal(decision.sessionOutcome, "adopt_representative");
|
||||
assert.equal(decision.canAdopt, true);
|
||||
const fields = publicDecisionFields(decision);
|
||||
assert.equal(fields.can_adopt, false);
|
||||
assert.equal(fields.can_adopt, true);
|
||||
assert.equal(fields.selection_allowed, decision.selectionAllowed);
|
||||
assert.equal(publicCanAdopt(decision), false);
|
||||
assert.equal(publicCanAdopt(decision), true);
|
||||
});
|
||||
|
||||
test("public can_adopt stays open only for adopt outcomes", () => {
|
||||
|
||||
@@ -1229,12 +1229,15 @@ test("family collect declined vs extra distinguish declined leaves the same adop
|
||||
nextAction: publicNextAction(right),
|
||||
birthDate: "1997-08-08",
|
||||
});
|
||||
// 原值: 两侧都 persist 财务采集
|
||||
// 新值: 两侧同一套 S3 交付,不写 leftover 带年份焦点
|
||||
// 原因: 训练门开后拒答家人与拒答额外区分题不再回到领域轮盘(BUG-648)
|
||||
assert.equal(narratedLeft.hostNarration, narratedRight.hostNarration);
|
||||
// 原值: 两侧旁白逐字相同(同一条 2020 年工作锚点)
|
||||
// 新值: 两侧都是 S3 交付句式;「再收一截」锚点可随剩余采集池变化
|
||||
// 原因: BUG-651 交付,拒答区分题不得把一侧打回采集
|
||||
assertAdoptTemplate(narratedLeft.hostNarration);
|
||||
assertAdoptTemplate(narratedRight.hostNarration);
|
||||
assert.match(narratedLeft.hostNarration, /再收一截/);
|
||||
assert.match(narratedRight.hostNarration, /再收一截/);
|
||||
assert.equal(narratedLeft.persisted, false);
|
||||
assert.equal(narratedRight.persisted, false);
|
||||
});
|
||||
|
||||
test("range-reading explain uses theme_sensitivity labels and the unique-minute boundary", () => {
|
||||
|
||||
@@ -680,11 +680,12 @@ test("occupation collect denial declines the focus and advances coverage to hora
|
||||
{ target_domain: "occupation", status: "declined" },
|
||||
],
|
||||
}), { askedProbeKeys: askedYearless });
|
||||
// 原值: leftover 学业采集
|
||||
// 新值: 方法层仍可问占问;不是学业/家人轮盘
|
||||
// 原因: leftover dated 关闭后 horary 仍可 collect_method_evidence
|
||||
assert.equal(nextCollect.next_followup?.domain, "horary");
|
||||
assert.equal(nextCollect.next_followup?.intent, "collect_method_evidence");
|
||||
// 原值: leftover 学业采集;后改为 horary
|
||||
// 新值: discriminate 路径带年月池空不再问占问/性格题
|
||||
// 原因: BUG-651 不得把性格题或 leftover 采集当下一道区分题
|
||||
assert.notEqual(nextCollect.next_followup?.domain, "education");
|
||||
assert.notEqual(nextCollect.next_followup?.domain, "family");
|
||||
assert.notEqual(nextCollect.next_followup?.choice_kind, "varga_style");
|
||||
});
|
||||
|
||||
test("collect denial persists the next stem on the turn and binds asked_turn_id", async () => {
|
||||
|
||||
@@ -454,13 +454,13 @@ test("collect_evidence keeps public can_adopt closed while selection_allowed fol
|
||||
],
|
||||
confirmationAllowed: false,
|
||||
});
|
||||
// 旧:collect() 把 capability.canAdopt 原样投影成 can_adopt。
|
||||
// 新:publicDecisionFields 再按 session_outcome 收紧;内部 canAdopt 仍可 true。
|
||||
// 保留语义:selection_allowed 不因此关闭;确认门仍 fail-closed。
|
||||
assert.equal(decision.sessionOutcome, "collect_evidence");
|
||||
// 原值: sessionOutcome=collect_evidence,公开 can_adopt=false
|
||||
// 新值: 训练门开、无带年月探针 → adopt_representative,公开 can_adopt=true
|
||||
// 原因: BUG-651 带年月池空即交付;确认门仍 fail-closed
|
||||
assert.equal(decision.sessionOutcome, "adopt_representative");
|
||||
assert.equal(decision.canAdopt, true);
|
||||
const fields = publicDecisionFields(decision);
|
||||
assert.equal(fields.can_adopt, false);
|
||||
assert.equal(fields.can_adopt, true);
|
||||
assert.equal(fields.selection_allowed, decision.selectionAllowed);
|
||||
});
|
||||
|
||||
|
||||
@@ -387,14 +387,20 @@ test("nonterminal invariant 2: exhausted probes take the next available server-o
|
||||
holdoutValidation: "unavailable" as const,
|
||||
datedMethodCollectOpen: true,
|
||||
nakshatraBoundaryProbe: null,
|
||||
nextAction: "ask_fact_collection",
|
||||
// 原值: ask_fact_collection
|
||||
// 新值: ready_to_adopt
|
||||
// 原因: BUG-651 训练门开且无带年月探针时剩余采集不得挡住交付
|
||||
nextAction: "ready_to_adopt",
|
||||
},
|
||||
{
|
||||
name: "nakshatra boundary",
|
||||
holdoutValidation: "unavailable" as const,
|
||||
datedMethodCollectOpen: false,
|
||||
nakshatraBoundaryProbe: nakshatraProbe,
|
||||
nextAction: "ask_candidate_discriminator",
|
||||
// 原值: ask_candidate_discriminator(月宿性格题)
|
||||
// 新值: ready_to_adopt
|
||||
// 原因: BUG-651 月宿不得挡在结果前面
|
||||
nextAction: "ready_to_adopt",
|
||||
},
|
||||
{
|
||||
name: "explicit range exit",
|
||||
|
||||
@@ -261,7 +261,7 @@ function deadlockDossier(): { dossier: DecisionDossier; state: ReturnType<typeof
|
||||
return { dossier, state };
|
||||
}
|
||||
|
||||
test("deadlock case: decision and followup agree and still render a D9/D10 style card", () => {
|
||||
test("deadlock case: dated pool empty delivers instead of a D9/D10 style card", () => {
|
||||
const { dossier } = deadlockDossier();
|
||||
const inspected = inspectDiscriminatorProbes(contrastPacketFromDossier(dossier));
|
||||
assert.equal(inspected.selected?.semanticKey, D9_STYLE_NO_OPTIONS.semantic_key);
|
||||
@@ -281,20 +281,26 @@ test("deadlock case: decision and followup agree and still render a D9/D10 style
|
||||
...catalog,
|
||||
candidatesSeparated: false,
|
||||
});
|
||||
assert.equal(followupAsksRenderableDiscriminator(plan.next_followup), true);
|
||||
assert.equal(plan.next_followup?.intent, "distinguish_candidates");
|
||||
assert.ok(plan.next_followup?.choice_frame);
|
||||
assert.equal(plan.next_followup?.choice_kind, "varga_style");
|
||||
assert.match(plan.next_followup?.semantic_key ?? "", /^varga\.d(9|10)\./);
|
||||
assert.equal(followupAsksRenderableDiscriminator(plan.next_followup), false);
|
||||
assert.notEqual(plan.next_followup?.choice_kind, "varga_style");
|
||||
assert.equal(
|
||||
plan.dropped_probes.some((item) => item.semantic_key.startsWith("varga.d9.") || item.semantic_key.startsWith("varga.d10.")),
|
||||
false,
|
||||
true,
|
||||
JSON.stringify(plan.dropped_probes),
|
||||
);
|
||||
|
||||
const decision = decideFromDossier(dossier);
|
||||
assert.equal(decision.nextAction, "ask_candidate_discriminator");
|
||||
assert.equal(decision.sessionOutcome, "discriminate_candidates");
|
||||
// 原值: ask_candidate_discriminator,出 D9/D10 性格卡
|
||||
// 新值: 带年月池空即交付,性格题 yearless_deferred
|
||||
// 原因: BUG-651 性格题不得挡在结果前面
|
||||
assert.ok(
|
||||
decision.nextAction === "offer_provisional_range"
|
||||
|| decision.nextAction === "ready_to_adopt"
|
||||
|| decision.nextAction === "complete_with_range"
|
||||
|| decision.nextAction === "ask_fact_collection",
|
||||
decision.nextAction,
|
||||
);
|
||||
assert.notEqual(decision.sessionOutcome, "discriminate_candidates");
|
||||
assert.equal(decision.canConfirmExactMinute, false);
|
||||
});
|
||||
|
||||
@@ -319,10 +325,18 @@ test("varga_style probes missing style_options complete the same way in inspect
|
||||
probes: [probe],
|
||||
},
|
||||
});
|
||||
assert.equal(plan.next_followup?.semantic_key, probe.semanticKey);
|
||||
assert.equal(plan.next_followup?.choice_kind, "varga_style");
|
||||
assert.ok(plan.next_followup?.choice_frame);
|
||||
assert.equal(plan.dropped_probes.some((item) => item.semantic_key === probe.semanticKey), false);
|
||||
assert.notEqual(plan.next_followup?.semantic_key, probe.semanticKey);
|
||||
assert.notEqual(plan.next_followup?.choice_kind, "varga_style");
|
||||
// 原值: 补全 style_options 后仍把 D9 当下一道区分题
|
||||
// 新值: D9 记 yearless_deferred,不再占用 discriminator
|
||||
// 原因: BUG-651
|
||||
assert.equal(
|
||||
plan.dropped_probes.some((item) => (
|
||||
item.semantic_key === probe.semanticKey && item.reason === "yearless_deferred"
|
||||
)),
|
||||
true,
|
||||
JSON.stringify(plan.dropped_probes),
|
||||
);
|
||||
});
|
||||
|
||||
test("method coverage incomplete still asks a renderable D9 style card", () => {
|
||||
@@ -338,11 +352,18 @@ test("method coverage incomplete still asks a renderable D9 style card", () => {
|
||||
});
|
||||
assert.equal(plan.methods.find((item) => item.method_id === "relatives")?.status, "uncovered");
|
||||
assert.equal(plan.methods.find((item) => item.method_id === "occupation")?.status, "uncovered");
|
||||
assert.equal(decision.nextAction, "ask_candidate_discriminator");
|
||||
assert.equal(plan.next_followup?.intent, "distinguish_candidates");
|
||||
assert.ok(plan.next_followup?.choice_frame);
|
||||
assert.notEqual(plan.next_followup?.source, "method_coverage");
|
||||
assert.equal(decision.probe?.semanticKey, plan.next_followup?.semantic_key);
|
||||
// 原值: 方法覆盖未齐仍问 D9 性格卡
|
||||
// 新值: 训练门开、带年月池空即交付
|
||||
// 原因: BUG-651 性格题不得挡在结果前面
|
||||
assert.ok(
|
||||
decision.nextAction === "offer_provisional_range"
|
||||
|| decision.nextAction === "ready_to_adopt"
|
||||
|| decision.nextAction === "complete_with_range"
|
||||
|| decision.nextAction === "ask_fact_collection",
|
||||
decision.nextAction,
|
||||
);
|
||||
assert.notEqual(plan.next_followup?.choice_kind, "varga_style");
|
||||
assert.equal(decision.probe, null);
|
||||
});
|
||||
|
||||
test("clock-key varga_style without signs fail-closes to dated family collect instead of a dead-end discriminator", () => {
|
||||
@@ -491,10 +512,13 @@ test("decision.probe matches the followup the plan will actually ask", () => {
|
||||
candidatesSeparated: false,
|
||||
});
|
||||
const decision = decideFromDossier(dossier);
|
||||
assert.equal(decision.nextAction, "ask_candidate_discriminator");
|
||||
assert.equal(decision.probe?.semanticKey, plan.next_followup?.semantic_key);
|
||||
assert.match(plan.next_followup?.semantic_key ?? "", /^varga\.d(9|10)\./);
|
||||
assert.notEqual(decision.probe?.semanticKey, D24.semantic_key);
|
||||
// 原值: 下一问是 D9/D10,decision.probe 对得上
|
||||
// 新值: 性格题 deferred,decision.probe 与 next 都不是性格卡
|
||||
// 原因: BUG-651
|
||||
assert.notEqual(decision.nextAction, "ask_candidate_discriminator");
|
||||
assert.equal(decision.probe, null);
|
||||
assert.notEqual(plan.next_followup?.choice_kind, "varga_style");
|
||||
assert.notEqual(plan.next_followup?.semantic_key, D24.semantic_key);
|
||||
});
|
||||
|
||||
test("birthDate gating matches the real followup plan on an under-age event probe", () => {
|
||||
|
||||
@@ -2053,35 +2053,31 @@ test("D9/D10 contrast after occupation coverage asks a discriminator, not adopt"
|
||||
}],
|
||||
},
|
||||
});
|
||||
assert.equal(plan.next_followup?.intent, "distinguish_candidates");
|
||||
assert.match(plan.next_followup?.user_prompt_hint ?? "", /职业前事|事业盘/);
|
||||
assert.equal(conversationalSessionOutcome({
|
||||
// 原值: 职业覆盖后仍问 D10 性格对照
|
||||
// 新值: 性格题 deferred,不再作为淘汰区分题
|
||||
// 原因: BUG-651
|
||||
assert.notEqual(plan.next_followup?.choice_kind, "varga_style");
|
||||
assert.notEqual(plan.next_followup?.intent, "distinguish_candidates");
|
||||
const outcome = conversationalSessionOutcome({
|
||||
selectionAllowed: true,
|
||||
proposeAllowed: true,
|
||||
confirmationAllowed: false,
|
||||
nextFollowup: plan.next_followup,
|
||||
methods: plan.methods,
|
||||
discriminatorProbe: {
|
||||
probeId: "contrast:varga.d10",
|
||||
candidateSetVersion: "05:00-05:02",
|
||||
question: "核对一段还没用进评分的职业前事",
|
||||
expectedOutcomes: [
|
||||
{ outcomeId: "a", supportsCandidateIds: ["05:00"], conflictsCandidateIds: ["05:01"] },
|
||||
{ outcomeId: "b", supportsCandidateIds: ["05:01"], conflictsCandidateIds: ["05:00"] },
|
||||
],
|
||||
candidateSplitHash: "varga.d10",
|
||||
informationGain: 0.12,
|
||||
sourceFeatures: [{ technique: "D10", calculationResultId: RESULT_ID }],
|
||||
domain: "career",
|
||||
year: null,
|
||||
semanticKey: "varga.d10",
|
||||
},
|
||||
discriminatorProbe: null,
|
||||
candidateScores: [
|
||||
{ time: "05:00", score: 34 },
|
||||
{ time: "05:01", score: 33 },
|
||||
{ time: "05:02", score: 33 },
|
||||
],
|
||||
}), "discriminate_candidates");
|
||||
});
|
||||
// 原值: discriminate_candidates(把 D10 性格题当区分探针)
|
||||
// 新值: 无带年月探针即交付
|
||||
// 原因: BUG-651
|
||||
assert.ok(
|
||||
outcome === "adopt_representative" || outcome === "provisional_range",
|
||||
outcome,
|
||||
);
|
||||
});
|
||||
|
||||
test("answered duty language skips window D10 and does not re-ask covered education from a yearless D24 card", () => {
|
||||
@@ -2781,7 +2777,18 @@ test("same-year existence probes are skipped; a different-year dasha still ranks
|
||||
},
|
||||
candidatesSeparated: false,
|
||||
});
|
||||
assert.equal(d24Wins.next_followup?.semantic_key, d10.semanticKey);
|
||||
assert.notEqual(d24Wins.next_followup?.semantic_key, d10.semanticKey);
|
||||
assert.notEqual(d24Wins.next_followup?.choice_kind, "varga_style");
|
||||
// 原值: D10 增益更高时压过 D24 成为 next
|
||||
// 新值: D10 性格题 deferred,不得挡在带年月题或交付前面
|
||||
// 原因: BUG-651
|
||||
assert.equal(
|
||||
d24Wins.dropped_probes.some((item) => (
|
||||
item.semantic_key === d10.semanticKey && item.reason === "yearless_deferred"
|
||||
)),
|
||||
true,
|
||||
JSON.stringify(d24Wins.dropped_probes),
|
||||
);
|
||||
});
|
||||
|
||||
test("three dated events with one holdout keep collecting instead of discriminating", () => {
|
||||
|
||||
@@ -282,7 +282,7 @@ test("training gate open does not mint yearless existence or quality varga cards
|
||||
assert.notEqual(plan.next_followup?.method_id, "d2_finance");
|
||||
});
|
||||
|
||||
test("training gate open still asks a signed yearless D10 style card", () => {
|
||||
test("training gate open does not ask a signed yearless D10 style card", () => {
|
||||
const d10 = {
|
||||
probeId: "contrast:varga.d10.巨蟹座/狮子座",
|
||||
candidateSetVersion: "04:47-05:15",
|
||||
@@ -309,10 +309,19 @@ test("training gate open still asks a signed yearless D10 style card", () => {
|
||||
probes: [...YEARLESS_PACKET.probes, d10],
|
||||
},
|
||||
});
|
||||
assert.equal(plan.next_followup?.intent, "distinguish_candidates");
|
||||
assert.ok(plan.next_followup?.choice_frame);
|
||||
assert.equal(plan.next_followup?.semantic_key, d10.semanticKey);
|
||||
assert.equal(plan.next_followup?.choice_kind, "varga_style");
|
||||
assert.notEqual(plan.next_followup?.intent, "distinguish_candidates");
|
||||
assert.notEqual(plan.next_followup?.semantic_key, d10.semanticKey);
|
||||
assert.notEqual(plan.next_followup?.choice_kind, "varga_style");
|
||||
// 原值: 训练门开后仍问签名 D10 性格卡
|
||||
// 新值: D10 记 yearless_deferred
|
||||
// 原因: BUG-651 性格题不得挡在结果前面
|
||||
assert.equal(
|
||||
plan.dropped_probes.some((item) => (
|
||||
item.semantic_key === d10.semanticKey && item.reason === "yearless_deferred"
|
||||
)),
|
||||
true,
|
||||
JSON.stringify(plan.dropped_probes),
|
||||
);
|
||||
});
|
||||
|
||||
test("training gate closed still withholds yearless varga cards", () => {
|
||||
|
||||
@@ -166,10 +166,18 @@ test("unanchored D10 varga_style cards are dropped; anchored cards mention the l
|
||||
contrastPacket: packet,
|
||||
candidatesSeparated: false,
|
||||
});
|
||||
assert.equal(anchored.next_followup?.semantic_key, D10_STYLE.semanticKey);
|
||||
assert.equal(anchored.next_followup?.choice_kind, "varga_style");
|
||||
assert.match(anchored.next_followup?.user_prompt_hint ?? "", /2018 年 7 月/);
|
||||
assert.doesNotMatch(anchored.next_followup?.choice_frame?.prompt ?? "", /2018/);
|
||||
assert.notEqual(anchored.next_followup?.semantic_key, D10_STYLE.semanticKey);
|
||||
assert.notEqual(anchored.next_followup?.choice_kind, "varga_style");
|
||||
// 原值: 有同年职业账本时把 D10 性格卡当下一问,题干提 2018 年 7 月
|
||||
// 新值: 即使有账本锚点,D10 仍 deferred
|
||||
// 原因: BUG-651 性格题不得挡在结果前面
|
||||
assert.equal(
|
||||
anchored.dropped_probes.some((item) => (
|
||||
item.semantic_key === D10_STYLE.semanticKey && item.reason === "yearless_deferred"
|
||||
)),
|
||||
true,
|
||||
JSON.stringify(anchored.dropped_probes),
|
||||
);
|
||||
});
|
||||
|
||||
test("after a D9-style answer the compare request body stays legal", () => {
|
||||
@@ -563,7 +571,12 @@ test("accident replay: fifth card is not the 2023 activation after four answers"
|
||||
assert.ok(
|
||||
decided.probe?.semanticKey === D10_LOW.semanticKey
|
||||
|| decided.nextAction === "ready_to_adopt"
|
||||
|| decided.sessionOutcome === "adopt_representative",
|
||||
|| decided.nextAction === "offer_provisional_range"
|
||||
|| decided.nextAction === "complete_with_range"
|
||||
|| decided.nextAction === "ask_fact_collection"
|
||||
|| decided.sessionOutcome === "adopt_representative"
|
||||
|| decided.sessionOutcome === "provisional_range",
|
||||
`${decided.nextAction}/${decided.sessionOutcome}/${decided.probe?.semanticKey}`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ function assertChoiceInvariants(input: {
|
||||
}
|
||||
}
|
||||
|
||||
test("walkthrough D9 probe question is in the 4-80 window and must survive frame assembly", () => {
|
||||
test("walkthrough D9 probe is deferred instead of blocking delivery", () => {
|
||||
const packet = d9WalkthroughPacket();
|
||||
const probe = packet.probes.find((item) => item.semanticKey.startsWith("varga.d9."));
|
||||
assert.ok(probe);
|
||||
@@ -184,18 +184,18 @@ test("walkthrough D9 probe question is in the 4-80 window and must survive frame
|
||||
candidatesSeparated: false,
|
||||
});
|
||||
const followup = plan.next_followup;
|
||||
assert.equal(followup?.intent, "distinguish_candidates", JSON.stringify({
|
||||
intent: followup?.intent,
|
||||
source: followup?.source,
|
||||
method_id: followup?.method_id,
|
||||
dropped: plan.dropped_probes,
|
||||
}));
|
||||
assert.equal(followup?.method_id, "d9_relationship");
|
||||
const frame = followup?.choice_frame ?? null;
|
||||
assert.ok(frame, "D9 probe must assemble a choice_frame");
|
||||
assert.equal(frame?.prompt, D9_QUESTION);
|
||||
const copy = frame ? serverOwnedChoiceCopy(frame) : null;
|
||||
assert.ok(copy?.prompt, "assembled D9 copy must keep the probe question");
|
||||
// 原值: 走查 D9 性格卡必须拼出 choice_frame
|
||||
// 新值: D9 记 yearless_deferred,不再作为必答区分题
|
||||
// 原因: BUG-651
|
||||
assert.notEqual(followup?.choice_kind, "varga_style");
|
||||
assert.notEqual(followup?.intent, "distinguish_candidates");
|
||||
assert.equal(
|
||||
plan.dropped_probes.some((item) => (
|
||||
item.semantic_key.startsWith("varga.d9.") && item.reason === "yearless_deferred"
|
||||
)),
|
||||
true,
|
||||
JSON.stringify(plan.dropped_probes),
|
||||
);
|
||||
});
|
||||
|
||||
test("D9 probe shape with unwired prompt makes the choice invariants red", async () => {
|
||||
|
||||
Reference in New Issue
Block a user