reasoning: add proposal-level answer meaning guard

- Add answerMeaning schema with supportCategory and resolutionGuidance enums
- Add pre-mutation guard that validates proposal alignment with answerMeaning
- Update prompt builder to instruct the model on answerMeaning contract
- Add tests for schema, guard logic, parsing defaults, and regression cases A-D
This commit is contained in:
2026-08-09 07:07:21 +01:00
parent 8c1036ecd0
commit 0d7ad5775c
7 changed files with 470 additions and 0 deletions
+24
View File
@@ -144,6 +144,29 @@ const graphUpdateNodeChangeSchema = z.object({
reason: z.string().min(1),
});
export const answerSupportCategory = /** @type {const} */ ({
relative_priority_only: "relative_priority_only",
conditional_tradeoff: "conditional_tradeoff",
uncertain: "uncertain",
explicit_hard_constraint: "explicit_hard_constraint",
other: "other",
});
export const answerResolutionGuidance = /** @type {const} */ ({
must_remain_unresolved: "must_remain_unresolved",
may_resolve: "may_resolve",
must_resolve: "must_resolve",
});
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)),
})
.strict();
export const selectedQuestionSchema = z
.object({
nodeId: z.string().min(1),
@@ -160,6 +183,7 @@ export const graphUpdateSchema = z.object({
resolvedUnknownNodeIds: z.array(z.string()).default([]),
affectedNodeIds: z.array(z.string()).default([]),
selectedQuestion: selectedQuestionSchema.nullable().default(null),
answerMeaning: answerMeaningSchema.nullable().default(null),
});
/** @typedef {z.infer<typeof graphUpdateSchema>} GraphUpdate */