fix(javascript): bound semantic unknown retention at projection

Admit unknowns before canonical construction and provenance interning. Offer independent per-file and application capacities through CLI, Evidence and the owned analysis worker, and reject over-allocation during streamed decoding.

Report examined omitted emission occurrences by family and reason without retaining discarded identities. Preserve omission-free graph identities and distinguish node truncation from overall partial coverage.

Fixes #4797
This commit is contained in:
morluto
2026-10-17 21:57:31 +08:00
parent 926e18b3fc
commit f4322b15ed
34 changed files with 892 additions and 293 deletions
@@ -1,8 +1,10 @@
import type { z } from "zod";
import type { JavaScriptApplicationGraph } from "../../domain/javascript/javascriptApplicationGraph.js";
import type { JsonValue } from "../../domain/jsonValue.js";
import { javascriptDisplayText } from "../../domain/javascript/javascriptAstValues.js";
import {
createJavaScriptSemanticGraphNode,
createJavaScriptSemanticGraphUnknown,
createJavaScriptSemanticGraphRelation,
JavaScriptSemanticEvidenceContextRegistry,
javaScriptSemanticNodeId,
@@ -12,6 +14,8 @@ import type { ApplicationGraphEvidence } from "../../domain/javascript/javascrip
import type {
JavaScriptSemanticGraphRelation,
JavaScriptSemanticGraphUnknown,
JavaScriptSemanticUnknownOmission,
javaScriptSemanticUnknownInputSchema,
} from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
import type { JavaScriptSourceRange } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
@@ -26,6 +30,9 @@ export interface SemanticGraphProjectionState {
readonly nodes: Map<string, JavaScriptSemanticGraphNode>;
readonly relations: Map<string, JavaScriptSemanticGraphRelation>;
readonly unknowns: Map<string, JavaScriptSemanticGraphUnknown>;
/** Absolute retained unknown count allowed during this file; null outside admission. */
unknownCapacity: number | null;
readonly unknownOmissions: Map<string, JavaScriptSemanticUnknownOmission>;
readonly evidenceContexts: JavaScriptSemanticEvidenceContextRegistry;
readonly roots: Set<string>;
/**
@@ -74,6 +81,8 @@ export const createSemanticGraphProjectionState =
nodes: new Map(),
relations: new Map(),
unknowns: new Map(),
unknownCapacity: null,
unknownOmissions: new Map(),
evidenceContexts: new JavaScriptSemanticEvidenceContextRegistry(),
roots: new Set(),
});
@@ -204,11 +213,45 @@ export const addSemanticGraphRelation = (
state.relations.set(value.relation_id, value);
};
/** Retain one unresolved frontier. */
/** Aggregate examined omissions without retaining unbounded discarded identities. */
export const recordSemanticUnknownOmission = (
state: SemanticGraphProjectionState,
omission: JavaScriptSemanticUnknownOmission,
): void => {
const key = `${omission.family}:${omission.reason}`;
const occurrences =
(state.unknownOmissions.get(key)?.occurrences ?? 0) + omission.occurrences;
if (!Number.isSafeInteger(occurrences))
throw new RangeError("Semantic unknown omission count overflow");
state.unknownOmissions.set(key, { ...omission, occurrences });
};
/** Admit unknown provenance only while its independent retained capacity allows it. */
export const addSemanticGraphUnknown = (
state: SemanticGraphProjectionState,
unknown: JavaScriptSemanticGraphUnknown,
input: Omit<
z.input<typeof javaScriptSemanticUnknownInputSchema>,
"relation_kinds" | "candidate_node_ids"
> & {
readonly relation_kinds: readonly JavaScriptSemanticGraphRelation["relation"][];
readonly candidate_node_ids: readonly string[];
},
): void => {
if (
state.unknownCapacity !== null &&
state.unknowns.size >= state.unknownCapacity
) {
recordSemanticUnknownOmission(state, {
family: input.family,
reason: input.reason,
occurrences: 1,
});
return;
}
const unknown = createJavaScriptSemanticGraphUnknown(
input,
state.evidenceContexts,
);
if (state.unknowns.has(unknown.unknown_id)) return;
state.unknowns.set(unknown.unknown_id, unknown);
};