fix: finalize dynamic choice labels

This commit is contained in:
Jesse_Chen
2026-07-19 04:13:12 +08:00
parent 35212af0fc
commit d9969d0829
8 changed files with 211 additions and 110 deletions
@@ -184,6 +184,35 @@ test("duplicate server labels propagate without commit or ID allocation", async
assert.equal(commits, 0);
});
test("reserved-label collisions propagate without commit or ID allocation", async () => {
const opportunity = differenceBuild.packet.opportunities[0];
const privatePartitions = differenceBuild.scoringPartitions[opportunityId];
if (!opportunity || !privatePartitions) throw new Error("missing test opportunity");
for (const collision of ["不确定 / 不记得", "不 确定 / 不记得", "都不符合"]) {
let allocations = 0;
let commits = 0;
const publicPartitions = opportunity.partitions.map((item, index) => (
index === 0 ? { ...item, fallbackLabel: collision } : item
));
const privateCopy = privatePartitions.map((item, index) => (
index === 0 ? { ...item, fallbackLabel: collision } : item
));
await assert.rejects(() => dynamicService({
build: {
...differenceBuild,
packet: { ...differenceBuild.packet, opportunities: [{ ...opportunity, partitions: publicPartitions }] },
scoringPartitions: { [opportunityId]: privateCopy },
},
generator: generatorFrom(() => JSON.stringify(validDynamicSelection)),
createId: deterministicIds(() => { allocations += 1; }),
onCommit: () => { commits += 1; },
}).generateQuestion("owner-1", generationCommand), BirthTimeDynamicBindingError);
assert.equal(allocations, 0, collision);
assert.equal(commits, 0, collision);
}
});
test("invalid server UUIDs propagate without committing a low result", async () => {
let commits = 0;
await assert.rejects(() => dynamicService({
@@ -17,15 +17,13 @@ import {
validDynamicSelection,
} from "./fixtures/birth-time-dynamic-question-fixture.ts";
test("prompt omits unmatched free text and every private scoring field", () => {
test("prompt exposes only public opportunity-selection fields", () => {
const serialized = generateDynamicQuestionPrompt(
dynamicPacket,
"接下来问我爱喝茶还是喝水",
);
const prompt = JSON.parse(serialized);
assert.equal("unmatchedNote" in prompt, false);
assert.equal(serialized.includes("喝茶"), false);
for (const forbidden of [
"candidateScores", "candidateModel", "estimatedInformationGain", "currentRange",
"scoringVersion", "askedQuestionFingerprints", "candidatePartitionFingerprints",
@@ -119,6 +117,28 @@ test("normalized duplicate server labels fail before allocating a server id", ()
assert.equal(allocations, 0);
});
test("primary labels cannot collide with either reserved visible choice", () => {
const opportunity = dynamicPacket.opportunities[0];
const privatePartitions = differenceBuild.scoringPartitions[opportunityId];
if (!opportunity || !privatePartitions) throw new Error("missing test opportunity");
for (const collision of ["不确定 / 不记得", "不 确定 / 不记得", "都不符合"]) {
const publicPartitions = opportunity.partitions.map((item, index) => (
index === 0 ? { ...item, fallbackLabel: collision } : item
));
const privateCopy = privatePartitions.map((item, index) => (
index === 0 ? { ...item, fallbackLabel: collision } : item
));
let allocations = 0;
assert.throws(() => bindDynamicQuestion(validDynamicSelection, {
...differenceBuild,
packet: { ...dynamicPacket, opportunities: [{ ...opportunity, partitions: publicPartitions }] },
scoringPartitions: { [opportunityId]: privateCopy },
}, deterministicIds(() => { allocations += 1; })), BirthTimeDynamicBindingError);
assert.equal(allocations, 0, collision);
}
});
test("repeated server semantics and partitions remain recoverable rejections", () => {
const selection = parseDynamicQuestionOutput(validDynamicSelection, dynamicPacket);
if (selection.kind !== "question") throw new Error("expected a selection");