refine raw-answer boundary for answer meaning
This commit is contained in:
@@ -2843,6 +2843,59 @@ function deriveAnswerMeaningProfile(userSupportedMeaning) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateAnswerMeaningCompatibilityWithRawAnswer({ answer, proposal }) {
|
||||||
|
if (!answer || !proposal.answerMeaning) return [];
|
||||||
|
|
||||||
|
const errors = [];
|
||||||
|
const rawAnswerProfile = deriveAnswerMeaningProfile(answer);
|
||||||
|
const supportedMeaningProfile = deriveAnswerMeaningProfile(
|
||||||
|
proposal.answerMeaning.userSupportedMeaning,
|
||||||
|
);
|
||||||
|
const supportedMeaningText = normaliseSemanticText(
|
||||||
|
proposal.answerMeaning.userSupportedMeaning,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rawAnswerProfile.category === "relative_priority_only") {
|
||||||
|
if (supportedMeaningProfile.category !== "relative_priority_only") {
|
||||||
|
errors.push(
|
||||||
|
"answerMeaning.userSupportedMeaning introduces a stronger reasoning category than the raw answer establishes.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containsConstraintBoundaryLanguage(supportedMeaningText)) {
|
||||||
|
errors.push(
|
||||||
|
"answerMeaning.userSupportedMeaning introduces an unsupported constraint or preference/trade-off distinction not present in the raw answer.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawAnswerProfile.category === "conditional_tradeoff") {
|
||||||
|
if (supportedMeaningProfile.category !== "conditional_tradeoff") {
|
||||||
|
errors.push(
|
||||||
|
"answerMeaning.userSupportedMeaning loses the raw answer's conditional trade-off structure.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawAnswerProfile.category === "uncertain") {
|
||||||
|
if (supportedMeaningProfile.category !== "uncertain") {
|
||||||
|
errors.push(
|
||||||
|
"answerMeaning.userSupportedMeaning overstates a raw answer that remains uncertain.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawAnswerProfile.category === "explicit_hard_constraint") {
|
||||||
|
if (supportedMeaningProfile.category !== "explicit_hard_constraint") {
|
||||||
|
errors.push(
|
||||||
|
"answerMeaning.userSupportedMeaning weakens a raw answer that explicitly states a hard constraint.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
function validateAnswerMeaningAlignment(proposal) {
|
function validateAnswerMeaningAlignment(proposal) {
|
||||||
if (!proposal.answerMeaning) return [];
|
if (!proposal.answerMeaning) return [];
|
||||||
|
|
||||||
@@ -3092,6 +3145,12 @@ export function applyValidatedProposal({
|
|||||||
validatedProposal,
|
validatedProposal,
|
||||||
);
|
);
|
||||||
proposalCompatibilityErrors.push(...selectedQuestionValidation.errors);
|
proposalCompatibilityErrors.push(...selectedQuestionValidation.errors);
|
||||||
|
proposalCompatibilityErrors.push(
|
||||||
|
...validateAnswerMeaningCompatibilityWithRawAnswer({
|
||||||
|
answer,
|
||||||
|
proposal: validatedProposal,
|
||||||
|
}),
|
||||||
|
);
|
||||||
proposalCompatibilityErrors.push(
|
proposalCompatibilityErrors.push(
|
||||||
...validateAnswerMeaningAlignment(validatedProposal),
|
...validateAnswerMeaningAlignment(validatedProposal),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1079,6 +1079,54 @@ describe("applyValidatedProposal", () => {
|
|||||||
expect(result.errors.join(" ")).toContain("must remain unresolved");
|
expect(result.errors.join(" ")).toContain("must remain unresolved");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Regression A: rejects unsupported strengthening inside userSupportedMeaning itself", () => {
|
||||||
|
const { graph, riskUnknownId } = makeRiskClarificationFixture();
|
||||||
|
|
||||||
|
const result = applyValidatedProposal({
|
||||||
|
situationGraph: graph,
|
||||||
|
answer: "Risk matters more to me.",
|
||||||
|
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/trade-off rather than a hard constraint.",
|
||||||
|
reason:
|
||||||
|
"The answer was interpreted as ruling out a hard constraint.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addedEdges: [],
|
||||||
|
removedEdgeIds: [],
|
||||||
|
resolvedUnknownNodeIds: [riskUnknownId],
|
||||||
|
affectedNodeIds: [],
|
||||||
|
selectedQuestion: null,
|
||||||
|
answerMeaning: {
|
||||||
|
userSupportedMeaning:
|
||||||
|
"Avoiding additional risk is a preference/trade-off rather than a hard constraint.",
|
||||||
|
possibleInference:
|
||||||
|
"The user prioritizes risk mitigation over aggressive growth strategies.",
|
||||||
|
supportCategory: "relative_priority_only",
|
||||||
|
resolutionGuidance: "must_remain_unresolved",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(result.stage).toBe("proposal_compatibility");
|
||||||
|
expect(result.errors.join(" ")).toContain(
|
||||||
|
"stronger reasoning category than the raw answer establishes",
|
||||||
|
);
|
||||||
|
expect(result.errors.join(" ")).toContain(
|
||||||
|
"unsupported constraint or preference/trade-off distinction",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("Regression B: rejects conditional trade-off proposals that flatten the qualification", () => {
|
it("Regression B: rejects conditional trade-off proposals that flatten the qualification", () => {
|
||||||
const { graph, riskUnknownId } = makeRiskClarificationFixture();
|
const { graph, riskUnknownId } = makeRiskClarificationFixture();
|
||||||
|
|
||||||
@@ -1306,6 +1354,127 @@ describe("applyValidatedProposal", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Regression B: preserves conditional trade-off when userSupportedMeaning stays within the raw answer", () => {
|
||||||
|
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: [
|
||||||
|
makeNode({
|
||||||
|
id: "n-opportunity-criteria",
|
||||||
|
label: "What counts as the right opportunity",
|
||||||
|
description:
|
||||||
|
"Need to know what counts as the right opportunity because that determines when some additional risk would be acceptable.",
|
||||||
|
kind: "unknown",
|
||||||
|
status: "unknown",
|
||||||
|
confidence: "medium",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
updatedNodes: [
|
||||||
|
{
|
||||||
|
nodeId: riskUnknownId,
|
||||||
|
previousStatus: "unknown",
|
||||||
|
newStatus: "resolved",
|
||||||
|
previousValue: null,
|
||||||
|
newValue:
|
||||||
|
"The user would normally avoid more risk, but for the right opportunity might accept some.",
|
||||||
|
reason:
|
||||||
|
"The answer establishes a conditional trade-off rather than a flat hard constraint.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addedEdges: [
|
||||||
|
makeEdge({
|
||||||
|
id: "e-risk-opportunity-criteria",
|
||||||
|
fromNodeId: riskUnknownId,
|
||||||
|
toNodeId: "n-opportunity-criteria",
|
||||||
|
relationship: "depends_on",
|
||||||
|
confidence: "medium",
|
||||||
|
description:
|
||||||
|
"The unresolved opportunity threshold matters because it determines when the trade-off changes.",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
removedEdgeIds: [],
|
||||||
|
resolvedUnknownNodeIds: [riskUnknownId],
|
||||||
|
affectedNodeIds: [],
|
||||||
|
selectedQuestion: {
|
||||||
|
nodeId: "n-opportunity-criteria",
|
||||||
|
question:
|
||||||
|
"What would count as the right opportunity for accepting some additional risk?",
|
||||||
|
reason:
|
||||||
|
"The conditional threshold remains unresolved and is the next consequential unknown.",
|
||||||
|
},
|
||||||
|
answerMeaning: {
|
||||||
|
userSupportedMeaning:
|
||||||
|
"The user would normally avoid more risk, but for the right opportunity might accept some.",
|
||||||
|
possibleInference:
|
||||||
|
"The exact threshold for the right opportunity remains undefined.",
|
||||||
|
supportCategory: "conditional_tradeoff",
|
||||||
|
resolutionGuidance: "may_resolve",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.updatedSituationGraph.resolvedNodeIds).toContain(
|
||||||
|
riskUnknownId,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
result.updatedSituationGraph.nodes.some(
|
||||||
|
(node) => node.id === "n-opportunity-criteria",
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Inference separation: possibleInference may remain plausible but cannot justify graph mutation when userSupportedMeaning overstates the raw answer", () => {
|
||||||
|
const { graph, riskUnknownId } = makeRiskClarificationFixture();
|
||||||
|
|
||||||
|
const result = applyValidatedProposal({
|
||||||
|
situationGraph: graph,
|
||||||
|
answer: "Risk matters more to me.",
|
||||||
|
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 interpretation was treated as sufficient to resolve the distinction.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addedEdges: [],
|
||||||
|
removedEdgeIds: [],
|
||||||
|
resolvedUnknownNodeIds: [riskUnknownId],
|
||||||
|
affectedNodeIds: [],
|
||||||
|
selectedQuestion: null,
|
||||||
|
answerMeaning: {
|
||||||
|
userSupportedMeaning:
|
||||||
|
"Avoiding additional risk is a preference rather than a hard constraint.",
|
||||||
|
possibleInference:
|
||||||
|
"The user may be signaling caution and a willingness to trade off growth for lower risk.",
|
||||||
|
supportCategory: "other",
|
||||||
|
resolutionGuidance: "may_resolve",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(result.stage).toBe("proposal_compatibility");
|
||||||
|
expect(result.errors.join(" ")).toContain(
|
||||||
|
"unsupported constraint or preference/trade-off distinction",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("Regression C: rejects unresolved uncertainty being treated as resolved", () => {
|
it("Regression C: rejects unresolved uncertainty being treated as resolved", () => {
|
||||||
const { graph, riskUnknownId } = makeRiskClarificationFixture();
|
const { graph, riskUnknownId } = makeRiskClarificationFixture();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user