refine raw-answer boundary for answer meaning

This commit is contained in:
2026-08-09 11:31:27 +01:00
parent c06aecc3f7
commit 4aa1492c8d
2 changed files with 228 additions and 0 deletions
+59
View File
@@ -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),
); );
+169
View File
@@ -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();