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
+58 -18
View File
@@ -122,12 +122,36 @@ describe("buildGraphUpdatePrompt", () => {
"Put any stronger interpretation in answerMeaning.possibleInference",
);
expect(prompt).toContain(
"supportCategory and resolutionGuidance are optional descriptive hints only",
"populate supportCategory with one of the allowed values whenever the user's meaning fits an existing category",
);
expect(prompt).toContain(
"leave them null rather than inventing rigid category labels",
"Do not leave supportCategory null merely because the wording is uncertain",
);
});
it("lists allowed structured semantic enum values", () => {
const prompt = buildGraphUpdatePrompt(makeContext());
expect(prompt).toContain("## Allowed answerMeaning.supportCategory Values");
expect(prompt).toContain(
"relative_priority_only | conditional_tradeoff | uncertain | explicit_hard_constraint | other",
);
expect(prompt).toContain(
"must_remain_unresolved | may_resolve | must_resolve",
);
});
it("strengthens resolutionGuidance population guidance without provider-specific wording", () => {
const prompt = buildGraphUpdatePrompt(makeContext());
expect(prompt).toContain(
"Populate resolutionGuidance when the user's meaning genuinely implies must_remain_unresolved, may_resolve, or must_resolve",
);
expect(prompt).toContain(
"Keep it null only when no existing resolution state actually applies",
);
expect(prompt).not.toContain("qwen");
});
});
// ── Semantic-to-mutation contract (57J.39) ──────────────
@@ -135,7 +159,9 @@ describe("buildGraphUpdatePrompt", () => {
describe("buildGraphUpdatePrompt — semantic-to-mutation MUST rule", () => {
it("contains the explicit structural-materialization MUST rule", () => {
const prompt = buildGraphUpdatePrompt(makeContext());
expect(prompt).toContain("MUST express its effect through structural mutation");
expect(prompt).toContain(
"MUST express its effect through structural mutation",
);
});
it("rule permits update/refine of existing structure", () => {
@@ -162,9 +188,7 @@ describe("buildGraphUpdatePrompt — semantic-to-mutation MUST rule", () => {
const prompt = buildGraphUpdatePrompt(makeContext());
// The rule should be silent about forcing new nodes — this is preserved by existing rule #7.
// Verify the MUST rule exists but doesn't contain "must add a new node" or similar.
const mustRuleMatch = prompt.match(
/6\..*?(?=\n7\.)/s,
);
const mustRuleMatch = prompt.match(/6\..*?(?=\n7\.)/s);
expect(mustRuleMatch).not.toBe(null);
expect(mustRuleMatch[0]).not.toContain("must add a new node");
});
@@ -172,9 +196,7 @@ describe("buildGraphUpdatePrompt — semantic-to-mutation MUST rule", () => {
it("does not imply possibleInference alone triggers mutation", () => {
const prompt = buildGraphUpdatePrompt(makeContext());
// The rule must reference userSupportedMeaning specifically, not possibleInference as a trigger.
const mustRuleMatch = prompt.match(
/6\..*?(?=\n7\.)/s,
);
const mustRuleMatch = prompt.match(/6\..*?(?=\n7\.)/s);
expect(mustRuleMatch[0]).toContain("userSupportedMeaning");
});
@@ -234,7 +256,9 @@ describe("buildGraphUpdatePrompt — semantic-to-mutation MUST rule", () => {
expect(prompt).toContain("duplicate unknowns");
// Additional Guidance preference for update over add:
const additionalGuidance = prompt.split("## Additional Guidance")[1];
expect(additionalGuidance).toContain("prefer updatedNodes and resolvedUnknownNodeIds over creating duplicate nodes");
expect(additionalGuidance).toContain(
"prefer updatedNodes and resolvedUnknownNodeIds over creating duplicate nodes",
);
});
it("PASS: update/refine route preserved — no new mandatory-add requirement", () => {
@@ -244,8 +268,12 @@ describe("buildGraphUpdatePrompt — semantic-to-mutation MUST rule", () => {
expect(prompt).toContain("update/refinement of existing structure");
// Additional guidance still encourages preferring updates:
const additionalGuidance = prompt.split("## Additional Guidance")[1];
expect(additionalGuidance).toContain("prefer updatedNodes and resolvedUnknownNodeIds over creating duplicate nodes");
expect(additionalGuidance).toContain("update that node rather than creating only a parallel observation");
expect(additionalGuidance).toContain(
"prefer updatedNodes and resolvedUnknownNodeIds over creating duplicate nodes",
);
expect(additionalGuidance).toContain(
"update that node rather than creating only a parallel observation",
);
});
it("PASS: possibleInference separation preserved — not converted to mandatory mutation", () => {
@@ -263,7 +291,9 @@ describe("buildGraphUpdatePrompt — semantic-to-mutation MUST rule", () => {
expect(additionalGuidance).not.toContain("possibleInference");
// Rule #27 exists in the prompt (separation preserved):
expect(fullPrompt).toContain("answerMeaning.possibleInference, not in userSupportedMeaning");
expect(fullPrompt).toContain(
"answerMeaning.possibleInference, not in userSupportedMeaning",
);
});
it("PASS: no action-selection machinery added — no keyword routing or node-kind decision table", () => {
@@ -293,7 +323,9 @@ describe("buildGraphUpdatePrompt — 57J.46 existing-first uncertainty fallback"
it("test 1: assembled prompt explicitly says to check for an equivalent unresolved unknown first", () => {
const prompt = buildGraphUpdatePrompt(makeContext());
const guidance = getAdditionalGuidance(prompt);
expect(guidance).toContain("first check whether an existing unresolved node");
expect(guidance).toContain(
"first check whether an existing unresolved node",
);
expect(guidance).toContain("represents the same uncertainty");
});
@@ -310,7 +342,9 @@ describe("buildGraphUpdatePrompt — 57J.46 existing-first uncertainty fallback"
const prompt = buildGraphUpdatePrompt(makeContext());
const guidance = getAdditionalGuidance(prompt);
expect(guidance).toContain("if no such node exists");
expect(guidance).toContain("add a new unknown that directly represents the unresolved uncertainty");
expect(guidance).toContain(
"add a new unknown that directly represents the unresolved uncertainty",
);
});
// Test 4 — ordered fallback means: existing first, otherwise add
@@ -361,9 +395,13 @@ describe("buildGraphUpdatePrompt — 57J.46 existing-first uncertainty fallback"
expect(prompt).toContain(
"include that existing node ID in resolvedUnknownNodeIds",
);
expect(prompt).toContain("update that node rather than creating only a parallel observation");
expect(prompt).toContain(
"update that node rather than creating only a parallel observation",
);
// Rule #5 (resolve answered unknown first) must still exist:
expect(prompt).toContain("Resolve the answered unknown first when the answer supports it");
expect(prompt).toContain(
"Resolve the answered unknown first when the answer supports it",
);
});
// Test 9 — duplicate validator/contract preserved
@@ -372,7 +410,9 @@ describe("buildGraphUpdatePrompt — 57J.46 existing-first uncertainty fallback"
expect(prompt).toContain("Do not add duplicate unknowns");
expect(prompt).toContain("genuinely new concepts");
const guidance = getAdditionalGuidance(prompt);
expect(guidance).toContain("prefer updatedNodes and resolvedUnknownNodeIds over creating duplicate nodes");
expect(guidance).toContain(
"prefer updatedNodes and resolvedUnknownNodeIds over creating duplicate nodes",
);
});
// Test 10 — scope remains uncertainty-only (not universal to all categories)