reasoning: use structured semantic fidelity contract

This commit is contained in:
2026-08-11 16:45:00 +01:00
parent b6a232ff6f
commit 7d06cd3c47
7 changed files with 630 additions and 97 deletions
+70 -7
View File
@@ -10,6 +10,8 @@ import {
selectReasoningPattern,
} from "./question-formulator.js";
import {
answerResolutionGuidance,
answerSupportCategory,
graphUpdateSchema,
makeNodeId,
situationGraphSchema,
@@ -92,7 +94,10 @@ function validateAddedUnknowns(graph, proposal, answerMeaning) {
.join(" "),
);
return rawAnswerSupportsUnclassifiedMeaning(userSupportedMeaningText, unknownText);
return rawAnswerSupportsUnclassifiedMeaning(
userSupportedMeaningText,
unknownText,
);
}
if (addedUnknowns.length > 3) {
@@ -2929,13 +2934,43 @@ function deriveAnswerMeaningProfile(userSupportedMeaning) {
};
}
function getAnswerMeaningProfile(answerMeaning) {
if (!answerMeaning?.userSupportedMeaning) {
return {
category: null,
resolutionGuidance: null,
usedStructuredSupportCategory: false,
usedStructuredResolutionGuidance: false,
};
}
const derivedProfile = deriveAnswerMeaningProfile(
answerMeaning.userSupportedMeaning,
);
return {
category: answerMeaning.supportCategory ?? derivedProfile.category,
resolutionGuidance:
answerMeaning.resolutionGuidance ?? derivedProfile.resolutionGuidance,
usedStructuredSupportCategory: answerMeaning.supportCategory != null,
usedStructuredResolutionGuidance: answerMeaning.resolutionGuidance != null,
};
}
function validateAnswerMeaningCompatibilityWithRawAnswer({ answer, proposal }) {
if (!answer || !proposal.answerMeaning) return [];
if (
proposal.answerMeaning.supportCategory != null ||
proposal.answerMeaning.resolutionGuidance != null
) {
return [];
}
const errors = [];
const rawAnswerProfile = deriveAnswerMeaningProfile(answer);
const supportedMeaningProfile = deriveAnswerMeaningProfile(
proposal.answerMeaning.userSupportedMeaning,
const supportedMeaningProfile = getAnswerMeaningProfile(
proposal.answerMeaning,
);
const supportedMeaningText = normaliseSemanticText(
proposal.answerMeaning.userSupportedMeaning,
@@ -3006,9 +3041,33 @@ function validateAnswerMeaningAlignment(proposal) {
const { userSupportedMeaning } = proposal.answerMeaning;
const meaningText = normaliseSemanticText(userSupportedMeaning);
const { resolved, proposalText } = proposalResolutionSummary(proposal);
const derivedProfile = deriveAnswerMeaningProfile(userSupportedMeaning);
const supportCategory = derivedProfile.category;
const resolutionGuidance = derivedProfile.resolutionGuidance;
const profile = getAnswerMeaningProfile(proposal.answerMeaning);
const supportCategory = profile.category;
const resolutionGuidance = profile.resolutionGuidance;
const usesStructuredPath =
profile.usedStructuredSupportCategory ||
profile.usedStructuredResolutionGuidance;
if (usesStructuredPath) {
if (
resolutionGuidance === answerResolutionGuidance.must_remain_unresolved &&
resolved
) {
errors.push(
"Proposal resolves an unknown even though answerMeaning.resolutionGuidance is must_remain_unresolved.",
);
}
if (
supportCategory === answerSupportCategory.explicit_hard_constraint &&
resolutionGuidance === answerResolutionGuidance.must_resolve
) {
// Deferred: the current proposal structure does not safely identify which
// specific answered/targeted unknown must resolve in every case.
}
return errors;
}
if (resolutionGuidance === "must_remain_unresolved" && resolved) {
errors.push(
@@ -3249,7 +3308,11 @@ export function applyValidatedProposal({
...validateSemanticDuplicateUnknowns(situationGraph, validatedProposal),
);
proposalCompatibilityErrors.push(
...validateAddedUnknowns(situationGraph, validatedProposal, validatedProposal.answerMeaning),
...validateAddedUnknowns(
situationGraph,
validatedProposal,
validatedProposal.answerMeaning,
),
);
const selectedQuestionValidation = validateSelectedQuestion(