From 8678b57b5e03b0fb103803eb0a9e9ebda3124647 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:44:24 +0000 Subject: [PATCH] fix(javascript): preserve candidate trace ambiguity --- src/domain/javascriptSemanticQuery.ts | 24 +++++++++++++++++++ .../javascriptSemanticQueryAssessment.ts | 8 +++++++ tests/javascriptSemanticGraphQuery.test.ts | 3 +++ 3 files changed, 35 insertions(+) diff --git a/src/domain/javascriptSemanticQuery.ts b/src/domain/javascriptSemanticQuery.ts index 7dfe450948..ca97d51f27 100644 --- a/src/domain/javascriptSemanticQuery.ts +++ b/src/domain/javascriptSemanticQuery.ts @@ -102,6 +102,11 @@ export const queryJavaScriptSemanticGraph = ( traversal.nodeIds, input, ); + const candidateRelations = relevantCandidateRelationCount( + graph, + traversal.nodeIds, + input, + ); const expectedMatches = expectedMatchesFor( retainedNodes, retainedRelations, @@ -115,6 +120,7 @@ export const queryJavaScriptSemanticGraph = ( hasExpectation: input.expected !== null, frontier, unknowns: relevantUnknowns, + candidateRelations, }); const nextOffset = offset + pageRelations.length; const nextCursor = @@ -543,6 +549,24 @@ const relevantUnknownFrontiers = ( ); }; +const relevantCandidateRelationCount = ( + graph: JavaScriptSemanticGraph, + nodeIds: ReadonlySet, + input: JavaScriptSemanticQueryInput, +): number => { + const allowed = + input.allowed_relations === undefined + ? null + : new Set(input.allowed_relations); + return graph.relations.filter( + (relation) => + relation.resolution === "candidate" && + (allowed === null || allowed.has(relation.relation)) && + (nodeIds.has(relation.source_node_id) || + nodeIds.has(relation.target_node_id)), + ).length; +}; + const expectedMatchesFor = ( nodes: readonly JavaScriptSemanticGraphNode[], relations: readonly JavaScriptSemanticGraphRelation[], diff --git a/src/domain/javascriptSemanticQueryAssessment.ts b/src/domain/javascriptSemanticQueryAssessment.ts index b735903542..89b4a3d448 100644 --- a/src/domain/javascriptSemanticQueryAssessment.ts +++ b/src/domain/javascriptSemanticQueryAssessment.ts @@ -25,6 +25,7 @@ export interface JavaScriptSemanticQueryAssessmentInput { readonly hasExpectation: boolean; readonly frontier: readonly JavaScriptSemanticQueryFrontier[]; readonly unknowns: readonly JavaScriptSemanticGraphUnknown[]; + readonly candidateRelations: number; } /** Status, coverage, limits, and limitations derived from one traversal. */ @@ -62,6 +63,7 @@ const queryCoverage = ( : input.frontier.length > 0 ? "truncated" : input.unknowns.length > 0 || + input.candidateRelations > 0 || input.graph.coverage.status !== "complete" ? "partial" : "complete", @@ -75,6 +77,7 @@ const queryStatus = ( if (input.frontier.length > 0) return "truncated"; if (input.unknowns.length > 0 || input.graph.coverage.status !== "complete") return "partial"; + if (input.candidateRelations > 0) return "ambiguous"; if ( input.totalSeeds === 0 || (input.hasExpectation && input.expectedMatches === 0) @@ -104,6 +107,11 @@ const queryLimitations = ( : [ "Relevant dynamic, unsupported, incomplete, or ambiguous semantics remain unknown.", ]), + ...(input.candidateRelations === 0 + ? [] + : [ + "Candidate relations remain ambiguous and do not establish a resolved semantic path.", + ]), ]); const uniqueSorted = (values: readonly string[]): string[] => diff --git a/tests/javascriptSemanticGraphQuery.test.ts b/tests/javascriptSemanticGraphQuery.test.ts index 771e92ac46..5468e306bb 100644 --- a/tests/javascriptSemanticGraphQuery.test.ts +++ b/tests/javascriptSemanticGraphQuery.test.ts @@ -370,6 +370,9 @@ describe("JavaScript semantic query", () => { }); expect(conservative.summary.traversed_relations).toBe(0); expect(optedIn.summary.traversed_relations).toBe(1); + expect(conservative.status).toBe("ambiguous"); + expect(conservative.coverage.status).toBe("partial"); + expect(optedIn.status).toBe("ambiguous"); expect(optedIn.relations).toMatchObject([ { relation: "supplies-request-field", resolution: "candidate" }, ]);