refine answerMeaning contract around user-supported meaning
This commit is contained in:
@@ -1123,7 +1123,7 @@ describe("applyValidatedProposal", () => {
|
||||
expect(result.errors.join(" ")).toContain("conditional qualification");
|
||||
});
|
||||
|
||||
it("accepts equivalent conditional category labels and still applies the existing Regression B guard", () => {
|
||||
it("accepts the Experiment 56A supportCategory wording variant and still applies the existing Regression B guard", () => {
|
||||
const { graph, riskUnknownId } = makeRiskClarificationFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
@@ -1156,8 +1156,53 @@ describe("applyValidatedProposal", () => {
|
||||
"The user would normally avoid more risk, but for the right opportunity might accept some.",
|
||||
possibleInference:
|
||||
"This may support eventual clarification, but the qualifying condition remains material.",
|
||||
supportCategory: "conditional_tradeoff",
|
||||
resolutionGuidance: "may_resolve",
|
||||
supportCategory: "conditional_qualification",
|
||||
resolutionGuidance: "may resolve once the condition is clarified",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.stage).toBe("proposal_compatibility");
|
||||
expect(result.errors.join(" ")).toContain("conditional qualification");
|
||||
});
|
||||
|
||||
it("accepts the Experiment 56B live wording variants and still applies the existing Regression B guard", () => {
|
||||
const { graph, riskUnknownId } = makeRiskClarificationFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
answer:
|
||||
"I'd normally avoid more risk, but for the right opportunity I might accept some.",
|
||||
previousQuestion:
|
||||
"Is avoiding additional risk a hard constraint or a preference/trade-off?",
|
||||
proposal: {
|
||||
addedNodes: [],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: riskUnknownId,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue:
|
||||
"Avoiding additional risk is a preference or trade-off rather than a hard constraint.",
|
||||
reason:
|
||||
"The answer shows a preference or trade-off rather than a hard constraint.",
|
||||
},
|
||||
],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [riskUnknownId],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: null,
|
||||
answerMeaning: {
|
||||
userSupportedMeaning:
|
||||
"The user would normally avoid more risk, but for the right opportunity might accept some.",
|
||||
possibleInference:
|
||||
"This may support eventual clarification, but the qualifying condition remains material.",
|
||||
supportCategory: "conditional_preference",
|
||||
resolutionGuidance:
|
||||
"Identify and quantify the threshold conditions that trigger risk acceptance.",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1296,6 +1341,50 @@ describe("applyValidatedProposal", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("fails safely when answerMeaning does not clearly establish one of the protected categories", () => {
|
||||
const { graph, riskUnknownId } = makeRiskClarificationFixture();
|
||||
|
||||
const result = applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
answer: "Risk and growth are both important.",
|
||||
previousQuestion:
|
||||
"Is avoiding additional risk a hard constraint or a preference/trade-off?",
|
||||
proposal: {
|
||||
addedNodes: [],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: riskUnknownId,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue:
|
||||
"Avoiding additional risk is a preference rather than a hard constraint.",
|
||||
reason:
|
||||
"The answer was interpreted as a balanced trade-off with no hard constraint.",
|
||||
},
|
||||
],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [riskUnknownId],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: null,
|
||||
answerMeaning: {
|
||||
userSupportedMeaning: "Risk and growth are both important.",
|
||||
possibleInference:
|
||||
"The user may be describing a broad balance, but no constraint boundary is established.",
|
||||
supportCategory: "constraint_preference_mix",
|
||||
resolutionGuidance: "needs more nuance",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.stage).toBe("proposal_compatibility");
|
||||
expect(result.errors.join(" ")).toContain(
|
||||
"does not clearly establish one of the protected reasoning categories",
|
||||
);
|
||||
});
|
||||
|
||||
it("active unknown matches selected question node", () => {
|
||||
const { graph, ids } = makeApplicationFixture();
|
||||
|
||||
|
||||
@@ -122,7 +122,10 @@ describe("buildGraphUpdatePrompt", () => {
|
||||
"Put any stronger interpretation in answerMeaning.possibleInference",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"If the answer is only a relative priority statement, use supportCategory=relative_priority_only and resolutionGuidance=must_remain_unresolved",
|
||||
"supportCategory and resolutionGuidance are optional descriptive hints only",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"leave them null rather than inventing rigid category labels",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -212,8 +212,8 @@ describe("graphUpdateSchema", () => {
|
||||
userSupportedMeaning: "Risk matters more to me.",
|
||||
possibleInference:
|
||||
"This may imply caution, but does not establish a hard constraint.",
|
||||
supportCategory: "relative_priority_only",
|
||||
resolutionGuidance: "must_remain_unresolved",
|
||||
supportCategory: "relative priority only",
|
||||
resolutionGuidance: "leave unresolved",
|
||||
},
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
@@ -75,7 +75,7 @@ describe("parseGraphUpdateProposal", () => {
|
||||
expect(result.normalisationsApplied.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("normalises confirmed enum alias and preserves IDs", () => {
|
||||
it("normalises confirmed node-kind alias and preserves IDs", () => {
|
||||
const result = parseGraphUpdateProposal({
|
||||
...makeValidProposal(),
|
||||
addedNodes: [
|
||||
@@ -96,12 +96,35 @@ describe("parseGraphUpdateProposal", () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.proposal.addedNodes[0].kind).toBe("reported_claim");
|
||||
expect(result.proposal.addedNodes[0].id).toBe("n-new");
|
||||
});
|
||||
|
||||
it("normalises equivalent answerMeaning supportCategory labels to the canonical contract", () => {
|
||||
it("accepts live-style free-text answerMeaning hints without requiring enum tokens", () => {
|
||||
const result = parseGraphUpdateProposal({
|
||||
...makeValidProposal(),
|
||||
answerMeaning: {
|
||||
userSupportedMeaning:
|
||||
"I'd normally avoid more risk, but for the right opportunity I might accept some.",
|
||||
possibleInference:
|
||||
"This may support later clarification, but the condition remains material.",
|
||||
supportCategory: "conditional_preference",
|
||||
resolutionGuidance:
|
||||
"Identify and quantify the threshold conditions that trigger risk acceptance.",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.proposal.answerMeaning).toMatchObject({
|
||||
supportCategory: "conditional_preference",
|
||||
resolutionGuidance:
|
||||
"Identify and quantify the threshold conditions that trigger risk acceptance.",
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts the earlier live Regression B wording variant without special aliasing", () => {
|
||||
const result = parseGraphUpdateProposal({
|
||||
...makeValidProposal(),
|
||||
answerMeaning: {
|
||||
@@ -110,25 +133,18 @@ describe("parseGraphUpdateProposal", () => {
|
||||
possibleInference:
|
||||
"This may support later clarification, but the condition remains material.",
|
||||
supportCategory: "conditional_qualification",
|
||||
resolutionGuidance: "may_resolve",
|
||||
resolutionGuidance: "may resolve once the condition is clarified",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.proposal.answerMeaning.supportCategory).toBe(
|
||||
"conditional_tradeoff",
|
||||
);
|
||||
expect(result.normalisationsApplied).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
path: ["answerMeaning", "supportCategory"],
|
||||
change: "Converted conditional_qualification to conditional_tradeoff",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(result.proposal.answerMeaning).toMatchObject({
|
||||
supportCategory: "conditional_qualification",
|
||||
resolutionGuidance: "may resolve once the condition is clarified",
|
||||
});
|
||||
});
|
||||
|
||||
it("unknown enum values still fail", () => {
|
||||
it("unknown node enum values still fail", () => {
|
||||
const result = parseGraphUpdateProposal({
|
||||
...makeValidProposal(),
|
||||
addedNodes: [
|
||||
@@ -152,25 +168,22 @@ describe("parseGraphUpdateProposal", () => {
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("still rejects unsupported answerMeaning supportCategory labels", () => {
|
||||
it("does not reject unsupported free-text answerMeaning labels at parse time", () => {
|
||||
const result = parseGraphUpdateProposal({
|
||||
...makeValidProposal(),
|
||||
answerMeaning: {
|
||||
userSupportedMeaning: "Risk matters more to me.",
|
||||
userSupportedMeaning: "Risk and growth are both important.",
|
||||
possibleInference: null,
|
||||
supportCategory: "constraint_preference_mix",
|
||||
resolutionGuidance: "must_remain_unresolved",
|
||||
resolutionGuidance: "needs more nuance",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
path: ["answerMeaning", "supportCategory"],
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.proposal.answerMeaning).toMatchObject({
|
||||
supportCategory: "constraint_preference_mix",
|
||||
resolutionGuidance: "needs more nuance",
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults missing selectedQuestion to null", () => {
|
||||
|
||||
Reference in New Issue
Block a user