diff --git a/lib/graph/apply-proposal.js b/lib/graph/apply-proposal.js index fcfe266..d34728d 100644 --- a/lib/graph/apply-proposal.js +++ b/lib/graph/apply-proposal.js @@ -2804,6 +2804,16 @@ function proposalResolutionSummary(proposal) { return { resolved, proposalText }; } +function resolvedProposalMeaningText(proposal) { + return normaliseSemanticText( + (proposal.updatedNodes || []) + .filter((update) => update.newStatus === "resolved") + .map((update) => update.newValue) + .filter((value) => typeof value === "string" && value.trim().length > 0) + .join(" "), + ); +} + function mentionsHardConstraint(text) { return ( text.includes("hard constraint") || @@ -3040,13 +3050,18 @@ function validateAnswerMeaningAlignment(proposal) { } if (supportCategory === "other") { + const resolvedMeaningText = resolvedProposalMeaningText(proposal); + if ( resolved && - containsConstraintBoundaryLanguage(proposalText) && - !containsConstraintBoundaryLanguage(meaningText) + resolvedMeaningText && + !rawAnswerSupportsUnclassifiedMeaning( + proposal.answerMeaning.userSupportedMeaning, + resolvedMeaningText, + ) ) { errors.push( - "Proposal cannot resolve beyond an unclassified answer by introducing an unsupported constraint or preference/trade-off distinction.", + "Proposal cannot resolve beyond an unclassified answer by introducing unsupported stronger meaning than answerMeaning.userSupportedMeaning establishes.", ); } } diff --git a/tests/graph/apply-proposal.test.js b/tests/graph/apply-proposal.test.js index 5aa0d2d..d1afaf0 100644 --- a/tests/graph/apply-proposal.test.js +++ b/tests/graph/apply-proposal.test.js @@ -1568,7 +1568,7 @@ describe("applyValidatedProposal", () => { ); }); - it("57A regression: a legitimate decision-advancing answer classified as other may resolve its intended unknown", () => { + it("57F legitimate-answer regression: a grounded unclassified answer may resolve its intended unknown", () => { const graph = makeCommercialUpdateFixture(); const parentId = graph.activeUnknownNodeId; @@ -1640,6 +1640,49 @@ describe("applyValidatedProposal", () => { expect(result.stage).toBeUndefined(); }); + it("57F deterministic reproduction: pre-fix grounded unclassified answer would have been rejected by proposal-text constraint language", () => { + const graph = makeCommercialUpdateFixture(); + const parentId = graph.activeUnknownNodeId; + + const result = applyValidatedProposal({ + situationGraph: graph, + answer: + "We're looking at this mainly for cost reduction — roughly £2M annual savings on office overhead.", + previousQuestion: + "What problem would this need to solve to justify continuing development?", + proposal: { + addedNodes: [], + updatedNodes: [ + { + nodeId: parentId, + previousStatus: "unknown", + newStatus: "resolved", + previousValue: null, + newValue: + "Cost reduction, including approximately £2M annual office-overhead savings, is a stated reason for considering the relocation.", + reason: + "The answer establishes that cost reduction is the relevant preference/trade-off consideration for this decision.", + }, + ], + addedEdges: [], + removedEdgeIds: [], + resolvedUnknownNodeIds: [parentId], + affectedNodeIds: [], + selectedQuestion: null, + answerMeaning: { + userSupportedMeaning: + "Cost reduction, including approximately £2M annual office-overhead savings, is a stated reason for considering the relocation.", + possibleInference: null, + supportCategory: "other", + resolutionGuidance: null, + }, + }, + }); + + expect(result.success).toBe(true); + expect(result.updatedSituationGraph.resolvedNodeIds).toContain(parentId); + }); + it("unsafe unclassified answer: other is not automatically trusted when the proposal adds stronger unsupported meaning", () => { const graph = makeCommercialUpdateFixture(); const parentId = graph.activeUnknownNodeId; @@ -1902,7 +1945,7 @@ describe("applyValidatedProposal", () => { expect(result.success).toBe(false); expect(result.stage).toBe("proposal_compatibility"); expect(result.errors.join(" ")).toContain( - "unsupported constraint or preference/trade-off distinction", + "unsupported stronger meaning than answerMeaning.userSupportedMeaning establishes", ); });