refine answerMeaning contract around user-supported meaning

This commit is contained in:
2026-08-09 08:50:37 +01:00
parent 869afee1ab
commit 7965375aff
8 changed files with 208 additions and 57 deletions
+66 -2
View File
@@ -2745,14 +2745,70 @@ function proposalResolutionSummary(proposal) {
return { resolved, proposalText };
}
function deriveAnswerMeaningProfile(userSupportedMeaning) {
const meaningText = normaliseSemanticText(userSupportedMeaning);
if (
meaningText.includes("not really sure") ||
meaningText.includes("not sure") ||
meaningText.includes("unsure") ||
meaningText.includes("do not know") ||
meaningText.includes("don't know")
) {
return {
category: "uncertain",
resolutionGuidance: "must_remain_unresolved",
};
}
if (
meaningText.includes("hard constraint") ||
meaningText.includes("non-negotiable") ||
meaningText.includes("dont want any increase in risk") ||
meaningText.includes("do not want any increase in risk")
) {
return {
category: "explicit_hard_constraint",
resolutionGuidance: "must_resolve",
};
}
if (hasConditionalQualification(meaningText)) {
return {
category: "conditional_tradeoff",
resolutionGuidance: "may_resolve",
};
}
if (
meaningText.includes("matters more") ||
meaningText.includes("more important") ||
meaningText.includes("higher priority") ||
meaningText.includes("greater relative importance") ||
meaningText.includes("relative importance")
) {
return {
category: "relative_priority_only",
resolutionGuidance: "must_remain_unresolved",
};
}
return {
category: "other",
resolutionGuidance: null,
};
}
function validateAnswerMeaningAlignment(proposal) {
if (!proposal.answerMeaning) return [];
const errors = [];
const { userSupportedMeaning, supportCategory, resolutionGuidance } =
proposal.answerMeaning;
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;
if (resolutionGuidance === "must_remain_unresolved" && resolved) {
errors.push(
@@ -2810,6 +2866,14 @@ function validateAnswerMeaningAlignment(proposal) {
}
}
if (supportCategory === "other") {
if (resolved || containsConstraintBoundaryLanguage(proposalText)) {
errors.push(
"Proposal cannot resolve or strengthen answerMeaning that does not clearly establish one of the protected reasoning categories.",
);
}
}
return errors;
}
+3 -3
View File
@@ -116,10 +116,10 @@ The JSON object must contain exactly these top-level fields:
25. Do not replace the whole graph, and do not restate unchanged graph content inside the proposal.
26. answerMeaning.userSupportedMeaning must state only what the user's answer directly supports.
27. Put any stronger interpretation in answerMeaning.possibleInference, not in userSupportedMeaning.
28. If the answer is only a relative priority statement, use supportCategory=relative_priority_only and resolutionGuidance=must_remain_unresolved.
28. supportCategory and resolutionGuidance are optional descriptive hints only; if you are unsure of the exact wording, leave them null rather than inventing rigid category labels.
29. If the answer is conditional or qualified, preserve that qualification explicitly in userSupportedMeaning.
30. If the answer says the user is unsure or does not resolve the distinction, use supportCategory=uncertain and resolutionGuidance=must_remain_unresolved.
31. If the answer explicitly states a hard constraint, use supportCategory=explicit_hard_constraint and resolutionGuidance=must_resolve.
30. If the answer says the user is unsure or does not resolve the distinction, state that uncertainty directly in userSupportedMeaning.
31. If the answer explicitly states a hard constraint, state that directly in userSupportedMeaning.
## Additional Guidance
- If the answer only clarifies an existing unknown, prefer updatedNodes and resolvedUnknownNodeIds over creating duplicate nodes.
+2 -2
View File
@@ -162,8 +162,8 @@ export const answerMeaningSchema = z
.object({
userSupportedMeaning: z.string().min(1),
possibleInference: z.string().nullable().optional(),
supportCategory: z.enum(Object.values(answerSupportCategory)),
resolutionGuidance: z.enum(Object.values(answerResolutionGuidance)),
supportCategory: z.string().min(1).nullable().optional(),
resolutionGuidance: z.string().min(1).nullable().optional(),
})
.strict();
-18
View File
@@ -11,10 +11,6 @@ const TOP_LEVEL_ARRAY_FIELDS = [
const TOP_LEVEL_NULLABLE_FIELDS = ["selectedQuestion"];
const ANSWER_SUPPORT_CATEGORY_ALIASES = {
conditional_qualification: "conditional_tradeoff",
};
function cloneJsonSafe(value) {
if (value == null) return value;
return JSON.parse(JSON.stringify(value));
@@ -66,20 +62,6 @@ function applyKnownEnumAliases(proposal, normalisationsApplied) {
});
}
const supportCategory = proposal.answerMeaning?.supportCategory;
const canonicalSupportCategory =
ANSWER_SUPPORT_CATEGORY_ALIASES[supportCategory];
if (canonicalSupportCategory) {
normalisationsApplied.push({
path: ["answerMeaning", "supportCategory"],
change: `Converted ${supportCategory} to ${canonicalSupportCategory}`,
});
proposal.answerMeaning = {
...proposal.answerMeaning,
supportCategory: canonicalSupportCategory,
};
}
return proposal;
}