diff --git a/lib/graph/prompt-builder.js b/lib/graph/prompt-builder.js index 7d7d53e..92dd294 100644 --- a/lib/graph/prompt-builder.js +++ b/lib/graph/prompt-builder.js @@ -123,6 +123,7 @@ The JSON object must contain exactly these top-level fields: ## Additional Guidance - If the answer only clarifies an existing unknown, prefer updatedNodes and resolvedUnknownNodeIds over creating duplicate nodes. +- When rule #6 applies to explicitly unresolved uncertainty: first check whether an existing unresolved node already represents the same uncertainty; if so, update/refine that existing structure rather than adding a duplicate; if no such node exists, add a new unknown that directly represents the unresolved uncertainty; do not use an edge alone to represent a previously unrepresented uncertainty. - When an answer resolves an existing unknown, include that existing node ID in resolvedUnknownNodeIds and update that node rather than creating only a parallel observation. - If the answer creates a more specific decision situation, add the smallest set of new nodes and edges needed to represent that situation and only its most consequential unknowns. - If you add a new unknown, do not leave it floating: connect it with an added edge to the relevant decision/context node created or updated from the answer. diff --git a/tests/graph/prompt-builder.test.js b/tests/graph/prompt-builder.test.js index fcd9064..a102562 100644 --- a/tests/graph/prompt-builder.test.js +++ b/tests/graph/prompt-builder.test.js @@ -281,3 +281,155 @@ describe("buildGraphUpdatePrompt — semantic-to-mutation MUST rule", () => { expect(fullPrompt).not.toContain("must add a new node"); }); }); + +// ── Experiment 57J.46 — existing-first uncertainty fallback ─── + +describe("buildGraphUpdatePrompt — 57J.46 existing-first uncertainty fallback", () => { + function getAdditionalGuidance(prompt) { + return prompt.split("## Additional Guidance")[1]; + } + + // Test 1 — existing-first ordering exists + 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("represents the same uncertainty"); + }); + + // Test 2 — reuse path explicit + it("test 2: prompt says update/refine existing structure rather than add a duplicate when equivalent exists", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + const guidance = getAdditionalGuidance(prompt); + expect(guidance).toContain("if so, update/refine that existing structure"); + expect(guidance).toContain("rather than adding a duplicate"); + }); + + // Test 3 — fallback-to-add explicit + it("test 3: prompt explicitly requires adding a new unknown when no equivalent unresolved unknown exists", () => { + 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"); + }); + + // Test 4 — ordered fallback means: existing first, otherwise add + it("test 4: full ordered fallback is present in the assembled prompt", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + const guidance = getAdditionalGuidance(prompt); + // The entire rule must be present as a single coherent instruction: + expect(guidance).toContain( + "When rule #6 applies to explicitly unresolved uncertainty: first check whether an existing unresolved node already represents the same uncertainty; if so, update/refine that existing structure rather than adding a duplicate; if no such node exists, add a new unknown that directly represents the unresolved uncertainty; do not use an edge alone to represent a previously unrepresented uncertainty.", + ); + }); + + // Test 5 — merely related node is insufficient + it("test 5: prompt does not imply a general related state/cost node counts as representing the same uncertainty", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + const guidance = getAdditionalGuidance(prompt); + // The rule requires "same uncertainty" — not "related" or "similar": + expect(guidance).toContain("same uncertainty"); + // Must not use weaker criteria: + expect(guidance).not.toContain("similar"); + expect(guidance).not.toContain("related to"); + }); + + // Test 6 — edge-only insufficient + it("test 6: prompt explicitly prevents using an edge alone to represent previously unrepresented uncertainty", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + const guidance = getAdditionalGuidance(prompt); + expect(guidance).toContain( + "do not use an edge alone to represent a previously unrepresented uncertainty", + ); + }); + + // Test 7 — possibleInference does not create unknowns + it("test 7: existing possibleInference separation remains intact — rule #27 still separates interpretation from userSupportedMeaning", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + expect(prompt).toContain( + "Put any stronger interpretation in answerMeaning.possibleInference, not in userSupportedMeaning", + ); + // The 57J.46 rule must NOT reference possibleInference as a trigger: + const guidance = getAdditionalGuidance(prompt); + expect(guidance).not.toContain("possibleInference"); + }); + + // Test 8 — actual resolution path preserved + it("test 8: prompt still allows resolution when the user's answer genuinely resolves an existing unknown", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + // The "resolve existing" guidance must remain intact: + expect(prompt).toContain( + "include that existing node ID in resolvedUnknownNodeIds", + ); + 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"); + }); + + // Test 9 — duplicate validator/contract preserved + it("test 9: existing duplicate-avoidance wording remains unchanged", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + 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"); + }); + + // Test 10 — scope remains uncertainty-only (not universal to all categories) + it("test 10: the new action-order rule does not apply universally to facts, constraints, decisions, or other meaning categories", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + const guidance = getAdditionalGuidance(prompt); + // The rule must be scoped to "unresolved uncertainty" specifically: + expect(guidance).toContain("explicitly unresolved uncertainty"); + // It must not say "any answer" or "all categories": + expect(guidance).not.toContain("any answer"); + expect(guidance).not.toContain("every change"); + expect(guidance).not.toContain("all categories"); + // Rule #7 (new unknown conditions) and other category rules must be untouched: + expect(prompt).toContain( + "Add new unknown nodes only when the answer introduces a new decision, claim, object, measure, dependency, or unresolved term directly relevant to the case", + ); + }); + + // Test 11 — fidelity / possibleInference separation preserved + it("test 11: fidelity rule separating userSupportedMeaning from possibleInference is untouched", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + expect(prompt).toContain( + "answerMeaning.userSupportedMeaning must state only what the user's answer directly supports", + ); + }); + + // Test 12 — traceability preserved + it("test 12: traceability requirement for new unknowns remains intact", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + expect(prompt).toContain( + "Every new unknown must be directly traceable to the user's answer and its description must state why that uncertainty matters", + ); + }); + + // Test 13 — no noop validator changed + it("test 13: the 'rule #6 does not apply → empty arrays' permission is preserved unchanged", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + const guidance = getAdditionalGuidance(prompt); + expect(guidance).toContain( + "If rule #6 does not apply (the answer contains no user-supported meaning that requires graph progress) and there is no other justification for change, return empty arrays for every category", + ); + }); + + // Test 14 — no semantic classifier or keyword logic added + it("test 14: no new deterministic semantic matcher or keyword matching was added", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + expect(prompt).not.toContain("threshold"); + expect(prompt).not.toContain("synonym"); + expect(prompt).not.toContain("keyword match"); + }); + + // Test 15 — provider-agnostic preserved + it("test 15: no provider-specific wording added", () => { + const prompt = buildGraphUpdatePrompt(makeContext()); + expect(prompt).not.toContain("qwen"); + expect(prompt).not.toContain("claude"); + expect(prompt).not.toContain("gpt"); + expect(prompt).not.toContain("ollama"); + }); +});