mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
refactor(analysis): remove caller-selected result caps
This commit is contained in:
+7
-33
@@ -255,18 +255,14 @@ def _containing_procedure(document, address):
|
||||
return (procedure, None) if procedure is not None else (None, "not_in_procedure")
|
||||
|
||||
|
||||
def _instruction_addresses(procedure, limit=None):
|
||||
def _instruction_addresses(procedure):
|
||||
result = []
|
||||
seen = set()
|
||||
segment = procedure.getSegment()
|
||||
truncated = False
|
||||
for block in procedure.basicBlockIterator():
|
||||
address = block.getStartingAddress()
|
||||
end = block.getEndingAddress()
|
||||
while address < end and address not in seen:
|
||||
if limit is not None and len(result) >= limit:
|
||||
truncated = True
|
||||
return result, truncated
|
||||
seen.add(address)
|
||||
instruction = segment.getInstructionAtAddress(address)
|
||||
if instruction is None:
|
||||
@@ -276,7 +272,7 @@ def _instruction_addresses(procedure, limit=None):
|
||||
if length <= 0:
|
||||
break
|
||||
address += length
|
||||
return result, truncated
|
||||
return result
|
||||
|
||||
|
||||
def _procedure_references(document, params):
|
||||
@@ -284,7 +280,7 @@ def _procedure_references(document, params):
|
||||
direction = params.get("direction", "outgoing")
|
||||
if direction not in ("incoming", "outgoing"):
|
||||
raise ValueError("direction must be incoming or outgoing")
|
||||
addresses, _ = _instruction_addresses(procedure)
|
||||
addresses = _instruction_addresses(procedure)
|
||||
edges = set()
|
||||
for address in addresses:
|
||||
segment = _segment(document, address)
|
||||
@@ -408,26 +404,6 @@ def _unavailable(reason):
|
||||
return {"available": False, "reason": reason}
|
||||
|
||||
|
||||
def _offset(params, name):
|
||||
value = params.get(name, 0)
|
||||
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
||||
raise ValueError("%s must be a non-negative integer" % name)
|
||||
return value
|
||||
|
||||
|
||||
def _bounded(items, offset, limit, total=None, scan_truncated=False):
|
||||
selected = items[offset:offset + limit]
|
||||
known_total = len(items) if total is None and not scan_truncated else total
|
||||
has_more = offset + len(selected) < len(items)
|
||||
return {
|
||||
"items": selected,
|
||||
"total": known_total,
|
||||
"returned": len(selected),
|
||||
"truncated": scan_truncated or has_more,
|
||||
"next_offset": offset + len(selected) if has_more else None,
|
||||
}
|
||||
|
||||
|
||||
def _name_map(document):
|
||||
result = {}
|
||||
for segment in document.getSegmentsList():
|
||||
@@ -454,8 +430,8 @@ def _render_instruction(segment, address):
|
||||
)
|
||||
|
||||
|
||||
def _assembly(procedure, limit=None):
|
||||
"""Render bounded assembly while guarding against malformed instruction cycles."""
|
||||
def _assembly(procedure):
|
||||
"""Render assembly while guarding against malformed instruction cycles."""
|
||||
lines = []
|
||||
segment = procedure.getSegment()
|
||||
seen = set()
|
||||
@@ -470,8 +446,6 @@ def _assembly(procedure, limit=None):
|
||||
line = _render_instruction(segment, address)
|
||||
if line is not None:
|
||||
lines.append(line)
|
||||
if limit is not None and len(lines) >= limit:
|
||||
return "\n".join(lines)
|
||||
length = instruction.getInstructionLength()
|
||||
if length <= 0:
|
||||
break
|
||||
@@ -482,7 +456,7 @@ def _assembly(procedure, limit=None):
|
||||
def _read_function_instructions(document, params):
|
||||
"""Read all raw instructions without invoking decompilation."""
|
||||
procedure = _procedure(document, params.get("procedure"))
|
||||
addresses, _ = _instruction_addresses(procedure)
|
||||
addresses = _instruction_addresses(procedure)
|
||||
segment = procedure.getSegment()
|
||||
items = []
|
||||
for address in addresses:
|
||||
@@ -502,7 +476,7 @@ def _read_function_instructions(document, params):
|
||||
def _analyze_function(document, params):
|
||||
"""Collect the complete function dossier for agent callers."""
|
||||
procedure = _procedure(document, params.get("procedure"))
|
||||
addresses, instruction_scan_truncated = _instruction_addresses(procedure)
|
||||
addresses = _instruction_addresses(procedure)
|
||||
blocks = []
|
||||
all_blocks = list(procedure.basicBlockIterator())
|
||||
for block in all_blocks:
|
||||
|
||||
@@ -165,9 +165,6 @@ export const resolveCompareApplicationVersionsRequestValidated = (
|
||||
...input.right_native_observations,
|
||||
...rightNative.value,
|
||||
],
|
||||
...(input.unknown_registry_approved === undefined
|
||||
? {}
|
||||
: { unknown_registry_approved: input.unknown_registry_approved }),
|
||||
};
|
||||
const parsed = compareApplicationVersionsInputSchema.safeParse(raw);
|
||||
return parsed.success
|
||||
@@ -196,10 +193,6 @@ export const resolveCompareSourceToBundleRequestValidated = (
|
||||
const raw = {
|
||||
reference: input.reference,
|
||||
application: application.value,
|
||||
limits: input.limits,
|
||||
...(input.unknown_registry_approved === undefined
|
||||
? {}
|
||||
: { unknown_registry_approved: input.unknown_registry_approved }),
|
||||
};
|
||||
const parsed = compareSourceToBundleInputSchema.safeParse(raw);
|
||||
return parsed.success
|
||||
@@ -244,9 +237,6 @@ export const resolveCompareJavaScriptExportShapesRequestValidated = (
|
||||
left_export_name: input.left_export_name,
|
||||
right_module_path: input.right_module_path,
|
||||
right_export_name: input.right_export_name,
|
||||
...(input.unknown_registry_approved === undefined
|
||||
? {}
|
||||
: { unknown_registry_approved: input.unknown_registry_approved }),
|
||||
};
|
||||
const parsed = compareJavaScriptExportShapesInputSchema.safeParse(raw);
|
||||
return parsed.success
|
||||
|
||||
@@ -32,8 +32,6 @@ const status = (
|
||||
availability_code: capability.available
|
||||
? null
|
||||
: (capability.availability_code ?? null),
|
||||
input_contract_version: 1,
|
||||
output_contract_version: 1,
|
||||
effects: {
|
||||
mutates_artifact: false,
|
||||
launches_process: false,
|
||||
|
||||
@@ -154,14 +154,12 @@ export const compareSourceToBundleEvidenceValidated = (
|
||||
rootArtifactSha256: application.rootArtifactSha256,
|
||||
graph: application.graph,
|
||||
},
|
||||
limits: input.limits,
|
||||
});
|
||||
return ok(
|
||||
createSourceToBundleComparisonEvidence(
|
||||
{
|
||||
reference_root_sha256: input.reference.root_sha256,
|
||||
application_evidence_id: application.evidence.evidence_id,
|
||||
limits: input.limits,
|
||||
},
|
||||
result,
|
||||
),
|
||||
|
||||
@@ -147,7 +147,7 @@ describe("managed decompiler reconstruction import", () => {
|
||||
).toThrow(/does not match/u);
|
||||
});
|
||||
|
||||
it("wraps imported reconstruction in Evidence v2", () => {
|
||||
it("wraps imported reconstruction in Evidence", () => {
|
||||
const evidence = importManagedReconstructionEvidence(exampleInput());
|
||||
|
||||
expect(evidence.ok).toBe(true);
|
||||
|
||||
@@ -8,7 +8,7 @@ export const firstProcedureAddress = (input: unknown): string => {
|
||||
.array(z.object({ address: addressSchema }))
|
||||
.min(1)
|
||||
.parse(input)[0];
|
||||
if (first === undefined) throw new TypeError("Procedure page was empty");
|
||||
if (first === undefined) throw new TypeError("Procedure inventory was empty");
|
||||
return first.address;
|
||||
};
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ describe("Hopper boundary values", () => {
|
||||
expect(parseNames({ names: [{ address: "0x2", name: "label" }] }).ok).toBe(
|
||||
true,
|
||||
);
|
||||
expect(parseNames({ items: [{ address: "0x2", value: "label" }] }).ok).toBe(
|
||||
false,
|
||||
);
|
||||
expect(parseRelatedAddresses({ callers: ["0x3"] }, "callers")).toEqual({
|
||||
ok: true,
|
||||
value: ["0x3"],
|
||||
|
||||
@@ -221,15 +221,6 @@ export const parseNames = (
|
||||
value: JsonValue,
|
||||
): Result<readonly AddressedName[], HopperProtocolError> => {
|
||||
const unwrapped = unwrapProperty(value, "names");
|
||||
const page = z
|
||||
.object({
|
||||
items: z.array(z.object({ address: z.string(), value: z.string() })),
|
||||
})
|
||||
.safeParse(unwrapped);
|
||||
if (page.success)
|
||||
return ok(
|
||||
page.data.items.map(({ address, value: name }) => ({ address, name })),
|
||||
);
|
||||
const records = addressedNamesSchema.safeParse(unwrapped);
|
||||
if (records.success) return ok(records.data);
|
||||
const map = addressedNameMapSchema.safeParse(unwrapped);
|
||||
|
||||
@@ -15,7 +15,6 @@ export const compareApplicationVersionsInputSchema = z
|
||||
right: evidenceSchema,
|
||||
left_native_observations: z.array(evidenceSchema).default([]),
|
||||
right_native_observations: z.array(evidenceSchema).default([]),
|
||||
unknown_registry_approved: z.literal(true).optional(),
|
||||
})
|
||||
.superRefine((input, context) => {
|
||||
if (input.left.evidence_id === input.right.evidence_id)
|
||||
|
||||
@@ -63,7 +63,6 @@ export const compareJavaScriptExportShapesInputSchema = z
|
||||
left_export_name: selectorTextSchema,
|
||||
right_module_path: selectorTextSchema,
|
||||
right_export_name: selectorTextSchema,
|
||||
unknown_registry_approved: z.literal(true).optional(),
|
||||
})
|
||||
.superRefine((input, context) => {
|
||||
if (input.left.evidence_id === input.right.evidence_id)
|
||||
|
||||
@@ -252,7 +252,7 @@ it("returns every relevant unknown inline", () => {
|
||||
direction: "forward-influence",
|
||||
});
|
||||
expect(result).toMatchObject({
|
||||
summary: { relevant_unknowns: 1, retained_unknowns: 1 },
|
||||
summary: { relevant_unknowns: 1 },
|
||||
});
|
||||
expect(result.unknowns).toHaveLength(1);
|
||||
});
|
||||
@@ -274,6 +274,51 @@ it("returns the complete deterministic forward influence result inline", () => {
|
||||
expect(first.relations).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("traverses paths beyond the former fixed depth ceiling", () => {
|
||||
const original = fixtureGraph();
|
||||
const request = original.nodes.find(({ kind }) => kind === "request");
|
||||
if (request === undefined) throw new TypeError("Missing request node");
|
||||
const chain = Array.from({ length: 70 }, (_, index) =>
|
||||
node("binding", `long-chain-${index}`),
|
||||
);
|
||||
const chainRelations = chain.map((target, index) =>
|
||||
createJavaScriptSemanticGraphRelation({
|
||||
source_node_id: chain[index - 1]?.node_id ?? request.node_id,
|
||||
target_node_id: target.node_id,
|
||||
relation: "reads",
|
||||
resolution: "resolved",
|
||||
properties: {},
|
||||
evidence: evidence("inferred"),
|
||||
}),
|
||||
);
|
||||
const { graph_id: _graphId, ...input } = original;
|
||||
const graph = createJavaScriptSemanticGraph({
|
||||
...input,
|
||||
nodes: [...original.nodes, ...chain],
|
||||
relations: [...original.relations, ...chainRelations],
|
||||
coverage: {
|
||||
...original.coverage,
|
||||
families: original.coverage.families.map((family) =>
|
||||
family.family === "data-flow"
|
||||
? {
|
||||
...family,
|
||||
retained_relations:
|
||||
family.retained_relations + chainRelations.length,
|
||||
}
|
||||
: family,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const result = queryJavaScriptSemanticGraph(graph, {
|
||||
seed: { kind: "literal", value: "TOKEN" },
|
||||
direction: "forward-influence",
|
||||
});
|
||||
|
||||
expect(result.summary.traversed_nodes).toBe(74);
|
||||
expect(result.nodes).toHaveLength(74);
|
||||
});
|
||||
|
||||
it("keeps relevant dynamic frontiers unknown", () => {
|
||||
const result = queryJavaScriptSemanticGraph(fixtureGraph(true), {
|
||||
seed: { kind: "literal", value: "TOKEN" },
|
||||
|
||||
@@ -12,44 +12,26 @@ import {
|
||||
type JavaScriptSemanticQueryInput,
|
||||
type JavaScriptSemanticQueryResult,
|
||||
} from "./javascriptSemanticQuerySchemas.js";
|
||||
import { assessJavaScriptSemanticQuery } from "./javascriptSemanticQueryAssessment.js";
|
||||
import {
|
||||
assessJavaScriptSemanticQuery,
|
||||
type JavaScriptSemanticQueryFrontier,
|
||||
} from "./javascriptSemanticQueryAssessment.js";
|
||||
import {
|
||||
canonicalJavaScriptSemanticQueryJson as canonicalJson,
|
||||
javaScriptSemanticQueryIdentifier as queryIdentifier,
|
||||
resolveJavaScriptSemanticQuerySeeds as resolveSeeds,
|
||||
} from "./javascriptSemanticQueryIdentity.js";
|
||||
|
||||
const TRAVERSAL_LIMITS = {
|
||||
max_seed_matches: 1_000,
|
||||
max_nodes: 50_000,
|
||||
max_edges: 100_000,
|
||||
max_depth: 64,
|
||||
max_functions: 10_000,
|
||||
max_modules: 10_000,
|
||||
max_unknowns: 10_000,
|
||||
} as const;
|
||||
|
||||
interface TraversalEntry {
|
||||
readonly relation: JavaScriptSemanticGraphRelation;
|
||||
readonly nextNodeId: string;
|
||||
}
|
||||
|
||||
type QueryFrontier = JavaScriptSemanticQueryFrontier;
|
||||
|
||||
interface Traversal {
|
||||
readonly nodeIds: Set<string>;
|
||||
readonly relationIds: Set<string>;
|
||||
readonly functionIds: Set<string>;
|
||||
readonly modules: Set<string>;
|
||||
readonly frontier: QueryFrontier[];
|
||||
}
|
||||
|
||||
interface SeedAdmission {
|
||||
readonly nodeIds: string[];
|
||||
readonly frontier: QueryFrontier[];
|
||||
}
|
||||
|
||||
interface QueryResult {
|
||||
@@ -57,7 +39,7 @@ interface QueryResult {
|
||||
readonly retainedRelations: JavaScriptSemanticGraphRelation[];
|
||||
}
|
||||
|
||||
/** Run one deterministic bounded traversal over a verified semantic graph. */
|
||||
/** Run one deterministic traversal over a verified semantic graph. */
|
||||
export const queryJavaScriptSemanticGraph = (
|
||||
graph: JavaScriptSemanticGraph,
|
||||
rawInput: unknown,
|
||||
@@ -65,24 +47,17 @@ export const queryJavaScriptSemanticGraph = (
|
||||
const input = javaScriptSemanticQueryInputSchema.parse(rawInput);
|
||||
const queryId = queryIdentifier(graph, input);
|
||||
const seeds = resolveSeeds(graph, input);
|
||||
const admission = admitSeeds(graph, seeds, input);
|
||||
const admission = admitSeeds(graph, seeds);
|
||||
const retainedSeeds = admission.nodeIds;
|
||||
const adjacency = buildAdjacency(graph.relations, input);
|
||||
const traversal = traverse(graph, retainedSeeds, adjacency, input);
|
||||
const frontier = uniqueFrontier([
|
||||
...traversal.frontier,
|
||||
...admission.frontier,
|
||||
]);
|
||||
const traversal = traverse(graph, retainedSeeds, adjacency);
|
||||
const result = createQueryResult(graph, traversal);
|
||||
const allRelevantUnknowns = relevantUnknownFrontiers(
|
||||
graph,
|
||||
traversal.nodeIds,
|
||||
input,
|
||||
);
|
||||
const relevantUnknowns = allRelevantUnknowns.slice(
|
||||
0,
|
||||
TRAVERSAL_LIMITS.max_unknowns,
|
||||
);
|
||||
const relevantUnknowns = allRelevantUnknowns;
|
||||
const candidateRelations = relevantCandidateRelationCount(
|
||||
graph,
|
||||
traversal.nodeIds,
|
||||
@@ -96,13 +71,10 @@ export const queryJavaScriptSemanticGraph = (
|
||||
const assessment = assessJavaScriptSemanticQuery({
|
||||
graph,
|
||||
totalSeeds: seeds.length,
|
||||
retainedSeeds: retainedSeeds.length,
|
||||
expectedMatches: expectedMatches.length,
|
||||
hasExpectation: input.expected !== null,
|
||||
frontier,
|
||||
unknowns: relevantUnknowns,
|
||||
candidateRelations,
|
||||
unknownsTruncated: allRelevantUnknowns.length > relevantUnknowns.length,
|
||||
});
|
||||
return javaScriptSemanticQueryResultSchema.parse({
|
||||
query_id: queryId,
|
||||
@@ -117,13 +89,11 @@ export const queryJavaScriptSemanticGraph = (
|
||||
expected_match_node_ids: expectedMatches.map(({ node_id }) => node_id),
|
||||
summary: {
|
||||
total_seed_matches: seeds.length,
|
||||
retained_seed_matches: retainedSeeds.length,
|
||||
traversed_nodes: traversal.nodeIds.size,
|
||||
traversed_relations: traversal.relationIds.size,
|
||||
traversed_functions: traversal.functionIds.size,
|
||||
traversed_modules: traversal.modules.size,
|
||||
relevant_unknowns: allRelevantUnknowns.length,
|
||||
retained_unknowns: relevantUnknowns.length,
|
||||
},
|
||||
coverage: assessment.coverage,
|
||||
limitations: assessment.limitations,
|
||||
@@ -146,68 +116,15 @@ const createQueryResult = (
|
||||
const admitSeeds = (
|
||||
graph: JavaScriptSemanticGraph,
|
||||
seeds: readonly string[],
|
||||
input: JavaScriptSemanticQueryInput,
|
||||
): SeedAdmission => {
|
||||
const nodes = new Map(graph.nodes.map((node) => [node.node_id, node]));
|
||||
const retained: string[] = [];
|
||||
const functions = new Set<string>();
|
||||
const modules = new Set<string>();
|
||||
const frontier: QueryFrontier[] = [];
|
||||
for (const nodeId of seeds) {
|
||||
const node = nodes.get(nodeId);
|
||||
if (node === undefined) continue;
|
||||
const reason = seedBlockedReason(node, {
|
||||
retained,
|
||||
functions,
|
||||
modules,
|
||||
input,
|
||||
});
|
||||
if (reason !== null) {
|
||||
frontier.push({ node_id: nodeId, depth: 0, reason });
|
||||
continue;
|
||||
}
|
||||
retained.push(nodeId);
|
||||
retainOwners(node, functions, modules);
|
||||
}
|
||||
return { nodeIds: retained, frontier: uniqueFrontier(frontier) };
|
||||
};
|
||||
|
||||
interface SeedBounds {
|
||||
readonly retained: readonly string[];
|
||||
readonly functions: ReadonlySet<string>;
|
||||
readonly modules: ReadonlySet<string>;
|
||||
readonly input: JavaScriptSemanticQueryInput;
|
||||
}
|
||||
|
||||
interface TraversalBounds {
|
||||
readonly nodes: ReadonlyMap<string, JavaScriptSemanticGraphNode>;
|
||||
readonly nodeIds: ReadonlySet<string>;
|
||||
readonly relationIds: ReadonlySet<string>;
|
||||
readonly functionIds: ReadonlySet<string>;
|
||||
readonly modules: ReadonlySet<string>;
|
||||
readonly input: JavaScriptSemanticQueryInput;
|
||||
}
|
||||
|
||||
const seedBlockedReason = (
|
||||
node: JavaScriptSemanticGraphNode,
|
||||
bounds: SeedBounds,
|
||||
): QueryFrontier["reason"] | null => {
|
||||
if (bounds.retained.length >= TRAVERSAL_LIMITS.max_seed_matches)
|
||||
return "max-seed-matches";
|
||||
if (bounds.retained.length >= TRAVERSAL_LIMITS.max_nodes) return "max-nodes";
|
||||
const functionId = functionOwner(node);
|
||||
if (
|
||||
functionId !== null &&
|
||||
!bounds.functions.has(functionId) &&
|
||||
bounds.functions.size >= TRAVERSAL_LIMITS.max_functions
|
||||
)
|
||||
return "max-functions";
|
||||
if (
|
||||
!bounds.modules.has(node.identity.module_path) &&
|
||||
bounds.modules.size >= TRAVERSAL_LIMITS.max_modules
|
||||
)
|
||||
return "max-modules";
|
||||
return null;
|
||||
return { nodeIds: retained };
|
||||
};
|
||||
|
||||
const buildAdjacency = (
|
||||
@@ -300,43 +217,25 @@ const traverse = (
|
||||
graph: JavaScriptSemanticGraph,
|
||||
seeds: readonly string[],
|
||||
adjacency: ReadonlyMap<string, TraversalEntry[]>,
|
||||
input: JavaScriptSemanticQueryInput,
|
||||
): Traversal => {
|
||||
const nodes = new Map(graph.nodes.map((node) => [node.node_id, node]));
|
||||
const nodeIds = new Set(seeds);
|
||||
const relationIds = new Set<string>();
|
||||
const functionIds = new Set<string>();
|
||||
const modules = new Set<string>();
|
||||
const frontier: QueryFrontier[] = [];
|
||||
const queue = [...nodeIds];
|
||||
for (const nodeId of nodeIds)
|
||||
retainOwners(nodes.get(nodeId), functionIds, modules);
|
||||
const depths = new Map([...nodeIds].map((nodeId) => [nodeId, 0]));
|
||||
const queue = [...nodeIds];
|
||||
const bounds: TraversalBounds = {
|
||||
nodes,
|
||||
nodeIds,
|
||||
relationIds,
|
||||
functionIds,
|
||||
modules,
|
||||
input,
|
||||
};
|
||||
for (let offset = 0; offset < queue.length; offset += 1) {
|
||||
const current = queue[offset];
|
||||
if (current === undefined) continue;
|
||||
const depth = depths.get(current) ?? 0;
|
||||
for (const entry of adjacency.get(current) ?? []) {
|
||||
const reason = blockedReason(entry, depth, bounds);
|
||||
if (reason !== null) {
|
||||
frontier.push({ node_id: current, depth, reason });
|
||||
continue;
|
||||
}
|
||||
relationIds.add(entry.relation.relation_id);
|
||||
if (nodeIds.has(entry.nextNodeId)) continue;
|
||||
const node = nodes.get(entry.nextNodeId);
|
||||
if (node === undefined) continue;
|
||||
nodeIds.add(entry.nextNodeId);
|
||||
retainOwners(node, functionIds, modules);
|
||||
depths.set(entry.nextNodeId, depth + 1);
|
||||
queue.push(entry.nextNodeId);
|
||||
}
|
||||
}
|
||||
@@ -345,66 +244,21 @@ const traverse = (
|
||||
relationIds,
|
||||
functionIds,
|
||||
modules,
|
||||
frontier: uniqueFrontier(frontier),
|
||||
};
|
||||
};
|
||||
|
||||
const blockedReason = (
|
||||
entry: TraversalEntry,
|
||||
depth: number,
|
||||
bounds: TraversalBounds,
|
||||
): QueryFrontier["reason"] | null => {
|
||||
if (depth >= TRAVERSAL_LIMITS.max_depth) return "max-depth";
|
||||
if (
|
||||
!bounds.relationIds.has(entry.relation.relation_id) &&
|
||||
bounds.relationIds.size >= TRAVERSAL_LIMITS.max_edges
|
||||
)
|
||||
return "max-edges";
|
||||
if (bounds.nodeIds.has(entry.nextNodeId)) return null;
|
||||
if (bounds.nodeIds.size >= TRAVERSAL_LIMITS.max_nodes) return "max-nodes";
|
||||
const node = bounds.nodes.get(entry.nextNodeId);
|
||||
if (node === undefined) return null;
|
||||
const functionId = functionOwner(node);
|
||||
if (
|
||||
functionId !== null &&
|
||||
!bounds.functionIds.has(functionId) &&
|
||||
bounds.functionIds.size >= TRAVERSAL_LIMITS.max_functions
|
||||
)
|
||||
return "max-functions";
|
||||
if (
|
||||
!bounds.modules.has(node.identity.module_path) &&
|
||||
bounds.modules.size >= TRAVERSAL_LIMITS.max_modules
|
||||
)
|
||||
return "max-modules";
|
||||
return null;
|
||||
};
|
||||
|
||||
const retainOwners = (
|
||||
node: JavaScriptSemanticGraphNode | undefined,
|
||||
functions: Set<string>,
|
||||
modules: Set<string>,
|
||||
): void => {
|
||||
if (node === undefined) return;
|
||||
const functionId = functionOwner(node);
|
||||
const functionId =
|
||||
node.kind === "function" ? node.node_id : node.function_node_id;
|
||||
if (functionId !== null) functions.add(functionId);
|
||||
modules.add(node.identity.module_path);
|
||||
};
|
||||
|
||||
const functionOwner = (node: JavaScriptSemanticGraphNode): string | null =>
|
||||
node.kind === "function" ? node.node_id : node.function_node_id;
|
||||
|
||||
const uniqueFrontier = (frontier: readonly QueryFrontier[]): QueryFrontier[] =>
|
||||
[
|
||||
...new Map(
|
||||
frontier.map((item) => [
|
||||
`${item.node_id}\0${String(item.depth)}\0${item.reason}`,
|
||||
item,
|
||||
]),
|
||||
).values(),
|
||||
].sort((left, right) =>
|
||||
compareCodePoints(canonicalJson(left), canonicalJson(right)),
|
||||
);
|
||||
|
||||
const relevantUnknownFrontiers = (
|
||||
graph: JavaScriptSemanticGraph,
|
||||
nodeIds: ReadonlySet<string>,
|
||||
|
||||
@@ -3,40 +3,24 @@ import type { JavaScriptSemanticGraph } from "./javascriptSemanticGraph.js";
|
||||
import type { JavaScriptSemanticGraphUnknown } from "./javascriptSemanticGraphSchemas.js";
|
||||
import type { JavaScriptSemanticQueryResult } from "./javascriptSemanticQuerySchemas.js";
|
||||
|
||||
/** One caller-limit frontier retained by deterministic query traversal. */
|
||||
export interface JavaScriptSemanticQueryFrontier {
|
||||
readonly node_id: string;
|
||||
readonly depth: number;
|
||||
readonly reason:
|
||||
| "max-depth"
|
||||
| "max-edges"
|
||||
| "max-functions"
|
||||
| "max-modules"
|
||||
| "max-nodes"
|
||||
| "max-seed-matches";
|
||||
}
|
||||
|
||||
/** Inputs needed to classify one completed semantic traversal. */
|
||||
export interface JavaScriptSemanticQueryAssessmentInput {
|
||||
readonly graph: JavaScriptSemanticGraph;
|
||||
readonly totalSeeds: number;
|
||||
readonly retainedSeeds: number;
|
||||
readonly expectedMatches: number;
|
||||
readonly hasExpectation: boolean;
|
||||
readonly frontier: readonly JavaScriptSemanticQueryFrontier[];
|
||||
readonly unknowns: readonly JavaScriptSemanticGraphUnknown[];
|
||||
readonly candidateRelations: number;
|
||||
readonly unknownsTruncated: boolean;
|
||||
}
|
||||
|
||||
/** Status, coverage, limits, and limitations derived from one traversal. */
|
||||
/** Status, coverage, and limitations derived from one traversal. */
|
||||
export interface JavaScriptSemanticQueryAssessment {
|
||||
readonly status: JavaScriptSemanticQueryResult["status"];
|
||||
readonly coverage: JavaScriptSemanticQueryResult["coverage"];
|
||||
readonly limitations: string[];
|
||||
}
|
||||
|
||||
/** Classify a bounded semantic traversal without promoting unknowns to absence. */
|
||||
/** Classify a complete semantic traversal without promoting unknowns to absence. */
|
||||
export const assessJavaScriptSemanticQuery = (
|
||||
input: JavaScriptSemanticQueryAssessmentInput,
|
||||
): JavaScriptSemanticQueryAssessment => ({
|
||||
@@ -51,28 +35,26 @@ const queryCoverage = (
|
||||
status:
|
||||
input.graph.coverage.status === "unavailable"
|
||||
? "unavailable"
|
||||
: input.frontier.length > 0 || input.unknownsTruncated
|
||||
? "truncated"
|
||||
: input.unknowns.length > 0 ||
|
||||
input.candidateRelations > 0 ||
|
||||
input.graph.coverage.status !== "complete"
|
||||
? "partial"
|
||||
: "complete",
|
||||
frontier: [...input.frontier],
|
||||
: input.unknowns.length > 0 ||
|
||||
input.candidateRelations > 0 ||
|
||||
input.graph.coverage.status !== "complete" ||
|
||||
input.graph.coverage.truncated
|
||||
? "partial"
|
||||
: "complete",
|
||||
});
|
||||
|
||||
const queryStatus = (
|
||||
input: JavaScriptSemanticQueryAssessmentInput,
|
||||
): JavaScriptSemanticQueryResult["status"] => {
|
||||
if (input.graph.coverage.status === "unavailable") return "unsupported";
|
||||
if (input.frontier.length > 0 || input.unknownsTruncated) return "truncated";
|
||||
if (input.candidateRelations > 0) return "ambiguous";
|
||||
if (
|
||||
input.totalSeeds === 0 ||
|
||||
(input.hasExpectation && input.expectedMatches === 0)
|
||||
)
|
||||
return input.unknowns.length > 0 ||
|
||||
input.graph.coverage.status !== "complete"
|
||||
input.graph.coverage.status !== "complete" ||
|
||||
input.graph.coverage.truncated
|
||||
? "partial"
|
||||
: "no-match";
|
||||
return input.totalSeeds > 1 ? "ambiguous" : "found";
|
||||
@@ -84,26 +66,11 @@ const queryLimitations = (
|
||||
uniqueSorted([
|
||||
"Semantic graph reachability is a static inference and does not prove runtime execution.",
|
||||
...input.graph.limitations,
|
||||
...(input.totalSeeds === input.retainedSeeds
|
||||
? []
|
||||
: [
|
||||
"Seed matches exceeded the internal traversal safety ceiling; omitted starts remain unexplored.",
|
||||
]),
|
||||
...(input.frontier.length === 0
|
||||
? []
|
||||
: [
|
||||
"Traversal stopped at an internal resource ceiling; frontier facts remain unknown.",
|
||||
]),
|
||||
...(input.unknowns.length === 0
|
||||
? []
|
||||
: [
|
||||
"Relevant dynamic, unsupported, incomplete, or ambiguous semantics remain unknown.",
|
||||
]),
|
||||
...(input.unknownsTruncated
|
||||
? [
|
||||
"Relevant unknown frontiers exceeded the internal result safety ceiling; omitted unknowns remain unresolved.",
|
||||
]
|
||||
: []),
|
||||
...(input.candidateRelations === 0
|
||||
? []
|
||||
: [
|
||||
|
||||
@@ -86,51 +86,28 @@ export const javaScriptSemanticQueryInputSchema = z.strictObject({
|
||||
source_map_authority: sourceMapAuthoritySchema.default({ authority: "none" }),
|
||||
});
|
||||
|
||||
const queryFrontierSchema = z.strictObject({
|
||||
node_id: semanticNodeIdSchema,
|
||||
depth: z.number().int().min(0).max(65),
|
||||
reason: z.enum([
|
||||
"max-depth",
|
||||
"max-edges",
|
||||
"max-functions",
|
||||
"max-modules",
|
||||
"max-nodes",
|
||||
"max-seed-matches",
|
||||
]),
|
||||
});
|
||||
|
||||
/** Deterministic semantic trace result. */
|
||||
export const javaScriptSemanticQueryResultSchema = z.strictObject({
|
||||
query_id: z.string().regex(/^jsrq_[a-f0-9]{64}$/u),
|
||||
source_graph_id: z.string().regex(/^jsrg_[a-f0-9]{64}$/u),
|
||||
seed: javaScriptSemanticQuerySeedSchema,
|
||||
direction: javaScriptSemanticQueryInputSchema.shape.direction,
|
||||
status: z.enum([
|
||||
"found",
|
||||
"no-match",
|
||||
"ambiguous",
|
||||
"partial",
|
||||
"truncated",
|
||||
"unsupported",
|
||||
]),
|
||||
seed_node_ids: z.array(semanticNodeIdSchema).max(1_000),
|
||||
nodes: z.array(javaScriptSemanticNodeSchema).max(3_000),
|
||||
relations: z.array(javaScriptSemanticRelationSchema).max(1_000),
|
||||
unknowns: z.array(javaScriptSemanticUnknownSchema).max(100_000),
|
||||
expected_match_node_ids: z.array(semanticNodeIdSchema).max(50_000),
|
||||
status: z.enum(["found", "no-match", "ambiguous", "partial", "unsupported"]),
|
||||
seed_node_ids: z.array(semanticNodeIdSchema),
|
||||
nodes: z.array(javaScriptSemanticNodeSchema),
|
||||
relations: z.array(javaScriptSemanticRelationSchema),
|
||||
unknowns: z.array(javaScriptSemanticUnknownSchema),
|
||||
expected_match_node_ids: z.array(semanticNodeIdSchema),
|
||||
summary: z.strictObject({
|
||||
total_seed_matches: z.number().int().min(0),
|
||||
retained_seed_matches: z.number().int().min(0),
|
||||
traversed_nodes: z.number().int().min(0),
|
||||
traversed_relations: z.number().int().min(0),
|
||||
traversed_functions: z.number().int().min(0),
|
||||
traversed_modules: z.number().int().min(0),
|
||||
relevant_unknowns: z.number().int().min(0),
|
||||
retained_unknowns: z.number().int().min(0).max(10_000),
|
||||
}),
|
||||
coverage: z.strictObject({
|
||||
status: z.enum(["complete", "partial", "truncated", "unavailable"]),
|
||||
frontier: z.array(queryFrontierSchema).max(100_000),
|
||||
status: z.enum(["complete", "partial", "unavailable"]),
|
||||
}),
|
||||
limitations: z.array(z.string().min(1).max(4_096)).max(1_000),
|
||||
});
|
||||
|
||||
@@ -28,7 +28,6 @@ export const compareManagedMembersInputSchema = z
|
||||
.strictObject({
|
||||
left: evidenceSchema,
|
||||
right: evidenceSchema,
|
||||
unknown_registry_approved: z.literal(true).optional(),
|
||||
})
|
||||
.superRefine((input, context) => {
|
||||
if (input.left.evidence_id === input.right.evidence_id)
|
||||
|
||||
@@ -91,7 +91,6 @@ export const managedNativeVerificationInputSchema = z
|
||||
.strictObject({
|
||||
managed_boundaries: evidenceSchema,
|
||||
native_observations: z.array(evidenceSchema).min(1),
|
||||
unknown_registry_approved: z.literal(true).optional(),
|
||||
})
|
||||
.superRefine((input, context) => {
|
||||
const ids = new Set<string>();
|
||||
|
||||
@@ -49,7 +49,6 @@ export const managedReconstructionImportInputSchema = z.strictObject({
|
||||
decompiler: decompilerSchema,
|
||||
methods: z.array(managedReconstructionMethodInputSchema).min(1).max(50),
|
||||
notes: z.array(z.string().min(1).max(4_096)).max(100).default([]),
|
||||
unknown_registry_approved: z.literal(true).optional(),
|
||||
});
|
||||
|
||||
const importedMethodSchema = z.strictObject({
|
||||
|
||||
@@ -53,7 +53,6 @@ export const managedRuntimeCorrelationInputSchema = z.strictObject({
|
||||
allow_network: false,
|
||||
allow_ui: false,
|
||||
}),
|
||||
unknown_registry_approved: z.literal(true).optional(),
|
||||
});
|
||||
|
||||
export const managedRuntimeCorrelationResultSchema = z.strictObject({
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
} from "./javascriptApplicationGraph.js";
|
||||
import { createHistoricalSourceGraph } from "./referenceSourceGraph.js";
|
||||
import { compareSourceToBundle } from "./sourceToBundleComparison.js";
|
||||
import { sourceToBundleComparisonResultSchema } from "./sourceToBundleComparisonSchemas.js";
|
||||
import {
|
||||
compareSourceToBundleInputSchema,
|
||||
sourceToBundleComparisonResultSchema,
|
||||
} from "./sourceToBundleComparisonSchemas.js";
|
||||
import { artifactEvidence } from "./javascriptApplicationGraph.fixture.js";
|
||||
|
||||
const HASH = {
|
||||
@@ -31,7 +34,6 @@ describe("historical source to bundle comparison", () => {
|
||||
rootArtifactSha256: HASH.artifact,
|
||||
graph,
|
||||
},
|
||||
limits: limits(),
|
||||
};
|
||||
|
||||
const first = compareSourceToBundle(input);
|
||||
@@ -104,7 +106,6 @@ describe("historical source to bundle comparison", () => {
|
||||
rootArtifactSha256: HASH.artifact,
|
||||
graph,
|
||||
},
|
||||
limits: limits(),
|
||||
});
|
||||
|
||||
expect(result.summary).toMatchObject({ removed: 0, unknown: 1 });
|
||||
@@ -116,7 +117,7 @@ describe("historical source to bundle comparison", () => {
|
||||
expect(result.coverage.status).toBe("partial");
|
||||
});
|
||||
|
||||
it("keeps exact mappings unknown when candidate output is truncated", () => {
|
||||
it("retains every candidate without caller-selected caps", () => {
|
||||
const result = compareSourceToBundle({
|
||||
reference: historicalGraph("complete"),
|
||||
application: {
|
||||
@@ -124,16 +125,15 @@ describe("historical source to bundle comparison", () => {
|
||||
rootArtifactSha256: HASH.artifact,
|
||||
graph: applicationGraph("complete"),
|
||||
},
|
||||
limits: { ...limits(), max_candidate_nodes: 1 },
|
||||
});
|
||||
|
||||
expect(item(result, "src/duplicated.ts").status).toBe("unknown");
|
||||
expect(
|
||||
item(result, "src/duplicated.ts").omitted_candidates,
|
||||
).toBeGreaterThan(0);
|
||||
expect(item(result, "src/duplicated.ts")).toMatchObject({
|
||||
status: "duplicated",
|
||||
candidates: [{}, {}],
|
||||
});
|
||||
});
|
||||
|
||||
it("reports the candidate frontier instead of deciding past a limit", () => {
|
||||
it("evaluates all indexed candidates for each source file", () => {
|
||||
const result = compareSourceToBundle({
|
||||
reference: historicalGraph("complete", ["src/split.ts"]),
|
||||
application: {
|
||||
@@ -141,20 +141,18 @@ describe("historical source to bundle comparison", () => {
|
||||
rootArtifactSha256: HASH.artifact,
|
||||
graph: applicationGraph("complete"),
|
||||
},
|
||||
limits: { ...limits(), max_candidate_evaluations: 1 },
|
||||
});
|
||||
|
||||
expect(result.items[0]).toMatchObject({
|
||||
status: "unknown",
|
||||
confidence: "unknown",
|
||||
omitted_candidates: 1,
|
||||
});
|
||||
expect(result.items[0]).toMatchObject({ status: "split" });
|
||||
expect(result.coverage).toMatchObject({
|
||||
status: "truncated",
|
||||
candidate_evaluations: 1,
|
||||
omitted_candidate_evaluations: 1,
|
||||
status: "complete-within-inputs",
|
||||
candidate_evaluations: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not expose caller-selected comparison limits", () => {
|
||||
expect(compareSourceToBundleInputSchema.shape).not.toHaveProperty("limits");
|
||||
});
|
||||
});
|
||||
|
||||
const historicalGraph = (
|
||||
@@ -300,13 +298,6 @@ const sourceDigest = (path: string): string => {
|
||||
return HASH.unchanged;
|
||||
};
|
||||
|
||||
const limits = () => ({
|
||||
max_source_files: 100,
|
||||
max_application_nodes: 100,
|
||||
max_candidate_nodes: 100,
|
||||
max_candidate_evaluations: 10_000,
|
||||
});
|
||||
|
||||
const item = (
|
||||
result: ReturnType<typeof compareSourceToBundle>,
|
||||
sourcePath: string,
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { HistoricalSourceGraph } from "./referenceSourceGraph.js";
|
||||
import {
|
||||
SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS,
|
||||
sourceToBundleComparisonResultSchema,
|
||||
type CompareSourceToBundleInput,
|
||||
type SourceToBundleCandidate,
|
||||
type SourceToBundleComparisonItem,
|
||||
type SourceToBundleComparisonResult,
|
||||
@@ -28,7 +27,6 @@ interface SourceToBundleComparisonInput {
|
||||
readonly rootArtifactSha256: string;
|
||||
readonly graph: JavaScriptApplicationGraph;
|
||||
};
|
||||
readonly limits: CompareSourceToBundleInput["limits"];
|
||||
}
|
||||
|
||||
interface ClassifiedMapping {
|
||||
@@ -49,8 +47,6 @@ interface ComparisonProjection {
|
||||
interface MappingProjection {
|
||||
readonly items: SourceToBundleComparisonItem[];
|
||||
readonly candidateEvaluations: number;
|
||||
readonly omittedCandidateEvaluations: number;
|
||||
readonly omittedCandidateReferences: number;
|
||||
}
|
||||
|
||||
/** Compare historical source files to static source-bearing application nodes. */
|
||||
@@ -61,8 +57,6 @@ export const compareSourceToBundle = (
|
||||
const mappings = compareMappings(input, projection);
|
||||
const factors = {
|
||||
...projection.factors,
|
||||
omittedCandidateEvaluations: mappings.omittedCandidateEvaluations,
|
||||
omittedCandidateReferences: mappings.omittedCandidateReferences,
|
||||
};
|
||||
const mappedCurrentIds = new Set(
|
||||
mappings.items.flatMap(({ current_node_ids: nodeIds }) => nodeIds),
|
||||
@@ -99,13 +93,8 @@ const projectComparison = (
|
||||
): ComparisonProjection => {
|
||||
const allSources = historicalSourceFiles(input.reference);
|
||||
const allCurrentNodes = sourceBearingNodes(input.application.graph.nodes);
|
||||
const sources = allSources.slice(0, input.limits.max_source_files);
|
||||
const currentNodes = allCurrentNodes.slice(
|
||||
0,
|
||||
input.limits.max_application_nodes,
|
||||
);
|
||||
const omittedSourceFiles = allSources.length - sources.length;
|
||||
const omittedApplicationNodes = allCurrentNodes.length - currentNodes.length;
|
||||
const sources = allSources;
|
||||
const currentNodes = allCurrentNodes;
|
||||
const index = buildSourceToBundleCandidateIndex(currentNodes);
|
||||
return {
|
||||
sources,
|
||||
@@ -114,17 +103,9 @@ const projectComparison = (
|
||||
absenceComplete:
|
||||
input.reference.inventory_state === "complete" &&
|
||||
input.application.graph.coverage.status === "complete" &&
|
||||
!input.application.graph.coverage.truncated &&
|
||||
omittedSourceFiles === 0 &&
|
||||
omittedApplicationNodes === 0 &&
|
||||
!index.pathIndexTruncated,
|
||||
!input.application.graph.coverage.truncated,
|
||||
factors: {
|
||||
input,
|
||||
omittedSourceFiles,
|
||||
omittedApplicationNodes,
|
||||
omittedCandidateEvaluations: 0,
|
||||
omittedCandidateReferences: 0,
|
||||
pathIndexTruncated: index.pathIndexTruncated,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -133,19 +114,12 @@ const compareMappings = (
|
||||
input: SourceToBundleComparisonInput,
|
||||
projection: ComparisonProjection,
|
||||
): MappingProjection => {
|
||||
let evaluationsRemaining = input.limits.max_candidate_evaluations;
|
||||
let candidateEvaluations = 0;
|
||||
let omittedCandidateEvaluations = 0;
|
||||
let omittedCandidateReferences = 0;
|
||||
const provisionalItems: SourceToBundleComparisonItem[] = [];
|
||||
for (const source of projection.sources) {
|
||||
const candidateIds = candidateIdsForSource(source, projection.index);
|
||||
const retainedIds = candidateIds.slice(0, evaluationsRemaining);
|
||||
const omittedEvaluations = candidateIds.length - retainedIds.length;
|
||||
evaluationsRemaining -= retainedIds.length;
|
||||
candidateEvaluations += retainedIds.length;
|
||||
omittedCandidateEvaluations += omittedEvaluations;
|
||||
const scored = retainedIds
|
||||
candidateEvaluations += candidateIds.length;
|
||||
const scored = candidateIds
|
||||
.map((nodeId) =>
|
||||
scoreSourceToBundleCandidate(source, nodeId, projection.index),
|
||||
)
|
||||
@@ -153,18 +127,7 @@ const compareMappings = (
|
||||
(candidate): candidate is SourceToBundleCandidate => candidate !== null,
|
||||
)
|
||||
.sort(compareCandidates);
|
||||
const retainedCandidates = scored.slice(
|
||||
0,
|
||||
input.limits.max_candidate_nodes,
|
||||
);
|
||||
const omittedCandidates =
|
||||
omittedEvaluations + scored.length - retainedCandidates.length;
|
||||
omittedCandidateReferences += omittedCandidates;
|
||||
const classification = classifyMapping(
|
||||
retainedCandidates,
|
||||
omittedCandidates === 0,
|
||||
projection.absenceComplete,
|
||||
);
|
||||
const classification = classifyMapping(scored, projection.absenceComplete);
|
||||
const semantic = {
|
||||
source_path: source.path,
|
||||
source_sha256: source.sha256,
|
||||
@@ -172,16 +135,10 @@ const compareMappings = (
|
||||
status: classification.status,
|
||||
confidence: classification.confidence,
|
||||
current_node_ids: classification.currentNodeIds,
|
||||
candidates: retainedCandidates,
|
||||
omitted_candidates: omittedCandidates,
|
||||
candidates: scored,
|
||||
limitations: uniqueSorted([
|
||||
...source.limitations,
|
||||
...classification.limitations,
|
||||
...(omittedCandidates > 0
|
||||
? [
|
||||
`${String(omittedCandidates)} candidate references were omitted by comparison limits.`,
|
||||
]
|
||||
: []),
|
||||
]),
|
||||
};
|
||||
provisionalItems.push({
|
||||
@@ -192,8 +149,6 @@ const compareMappings = (
|
||||
return {
|
||||
items: markMergedMappings(input, provisionalItems),
|
||||
candidateEvaluations,
|
||||
omittedCandidateEvaluations,
|
||||
omittedCandidateReferences,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -206,13 +161,8 @@ const comparisonCoverage = (
|
||||
reference_inventory_state: factors.input.reference.inventory_state,
|
||||
application_graph_status: factors.input.application.graph.coverage.status,
|
||||
retained_source_files: projection.sources.length,
|
||||
omitted_source_files: factors.omittedSourceFiles,
|
||||
retained_application_nodes: projection.currentNodes.length,
|
||||
omitted_application_nodes: factors.omittedApplicationNodes,
|
||||
candidate_evaluations: mappings.candidateEvaluations,
|
||||
omitted_candidate_evaluations: mappings.omittedCandidateEvaluations,
|
||||
omitted_candidate_references: mappings.omittedCandidateReferences,
|
||||
omitted_unmapped_current_nodes: 0,
|
||||
});
|
||||
|
||||
const scoringModel = (): SourceToBundleComparisonResult["scoring"] => ({
|
||||
@@ -226,18 +176,8 @@ const scoringModel = (): SourceToBundleComparisonResult["scoring"] => ({
|
||||
|
||||
const classifyMapping = (
|
||||
candidates: readonly SourceToBundleCandidate[],
|
||||
evaluationComplete: boolean,
|
||||
absenceComplete: boolean,
|
||||
): ClassifiedMapping => {
|
||||
if (!evaluationComplete)
|
||||
return {
|
||||
status: "unknown",
|
||||
confidence: "unknown",
|
||||
currentNodeIds: [],
|
||||
limitations: [
|
||||
"Candidate evaluation stopped at the declared limit; a stronger match may be omitted.",
|
||||
],
|
||||
};
|
||||
const exact = candidates.filter((candidate) =>
|
||||
hasSignal(candidate, "exact-source-digest"),
|
||||
);
|
||||
@@ -387,24 +327,11 @@ const countStatus = (
|
||||
|
||||
interface CoverageFactors {
|
||||
readonly input: SourceToBundleComparisonInput;
|
||||
readonly omittedSourceFiles: number;
|
||||
readonly omittedApplicationNodes: number;
|
||||
readonly omittedCandidateEvaluations: number;
|
||||
readonly omittedCandidateReferences: number;
|
||||
readonly pathIndexTruncated: boolean;
|
||||
}
|
||||
|
||||
const coverageStatus = (
|
||||
factors: CoverageFactors,
|
||||
): SourceToBundleComparisonResult["coverage"]["status"] => {
|
||||
if (
|
||||
factors.omittedSourceFiles > 0 ||
|
||||
factors.omittedApplicationNodes > 0 ||
|
||||
factors.omittedCandidateEvaluations > 0 ||
|
||||
factors.omittedCandidateReferences > 0 ||
|
||||
factors.pathIndexTruncated
|
||||
)
|
||||
return "truncated";
|
||||
return factors.input.reference.inventory_state === "complete" &&
|
||||
factors.input.application.graph.coverage.status === "complete"
|
||||
? "complete-within-inputs"
|
||||
@@ -426,31 +353,6 @@ const comparisonLimitations = (factors: CoverageFactors): string[] =>
|
||||
: [
|
||||
`Application graph coverage is ${factors.input.application.graph.coverage.status}; unmatched files remain unknown.`,
|
||||
]),
|
||||
...(factors.omittedSourceFiles > 0
|
||||
? [
|
||||
`${String(factors.omittedSourceFiles)} historical source files were omitted by max_source_files.`,
|
||||
]
|
||||
: []),
|
||||
...(factors.omittedApplicationNodes > 0
|
||||
? [
|
||||
`${String(factors.omittedApplicationNodes)} source-bearing application nodes were omitted by max_application_nodes.`,
|
||||
]
|
||||
: []),
|
||||
...(factors.omittedCandidateEvaluations > 0
|
||||
? [
|
||||
`${String(factors.omittedCandidateEvaluations)} candidate evaluations were omitted by max_candidate_evaluations.`,
|
||||
]
|
||||
: []),
|
||||
...(factors.omittedCandidateReferences > 0
|
||||
? [
|
||||
`${String(factors.omittedCandidateReferences)} candidate references were omitted from item output.`,
|
||||
]
|
||||
: []),
|
||||
...(factors.pathIndexTruncated
|
||||
? [
|
||||
"At least one current path exceeded the 128-segment suffix index bound.",
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
|
||||
const hasSignal = (
|
||||
|
||||
@@ -27,36 +27,17 @@ const sourceToBundleSignalKindSchema = z.enum([
|
||||
"language-extension",
|
||||
]);
|
||||
|
||||
const sourceToBundleLimitsSchema = z.strictObject({
|
||||
max_source_files: z.number().int().min(1).max(100_000).default(10_000),
|
||||
max_application_nodes: z.number().int().min(1).max(100_000).default(20_000),
|
||||
max_candidate_nodes: z.number().int().min(1).max(1_000).default(100),
|
||||
max_candidate_evaluations: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(10_000_000)
|
||||
.default(1_000_000),
|
||||
});
|
||||
|
||||
/** Historical source inventory, authenticated application Evidence, and bounds. */
|
||||
/** Historical source inventory and authenticated application Evidence. */
|
||||
export const compareSourceToBundleInputSchema = z.strictObject({
|
||||
reference: historicalSourceGraphSchema,
|
||||
application: evidenceSchema,
|
||||
limits: sourceToBundleLimitsSchema.default({
|
||||
max_source_files: 10_000,
|
||||
max_application_nodes: 20_000,
|
||||
max_candidate_nodes: 100,
|
||||
max_candidate_evaluations: 1_000_000,
|
||||
}),
|
||||
unknown_registry_approved: z.literal(true).optional(),
|
||||
});
|
||||
|
||||
const sourceToBundleSignalSchema = z.strictObject({
|
||||
kind: sourceToBundleSignalKindSchema,
|
||||
weight: z.number().int().min(1).max(100),
|
||||
source_value: boundedTextSchema,
|
||||
current_values: z.array(boundedTextSchema).min(1).max(64),
|
||||
current_values: z.array(boundedTextSchema).min(1),
|
||||
});
|
||||
|
||||
const sourceToBundleCandidateSchema = z.strictObject({
|
||||
@@ -82,9 +63,8 @@ const sourceToBundleItemSchema = z.strictObject({
|
||||
"unknown",
|
||||
]),
|
||||
confidence: z.enum(["exact", "high", "medium", "unknown"]),
|
||||
current_node_ids: z.array(nodeIdSchema).max(1_000),
|
||||
candidates: z.array(sourceToBundleCandidateSchema).max(1_000),
|
||||
omitted_candidates: z.number().int().min(0),
|
||||
current_node_ids: z.array(nodeIdSchema),
|
||||
candidates: z.array(sourceToBundleCandidateSchema),
|
||||
limitations: z.array(boundedTextSchema).max(100),
|
||||
});
|
||||
|
||||
@@ -119,10 +99,10 @@ export const sourceToBundleComparisonResultSchema = z.strictObject({
|
||||
duplicated: z.number().int().min(0),
|
||||
unknown: z.number().int().min(0),
|
||||
}),
|
||||
items: z.array(sourceToBundleItemSchema).max(100_000),
|
||||
unmapped_current_node_ids: z.array(nodeIdSchema).max(100_000),
|
||||
items: z.array(sourceToBundleItemSchema),
|
||||
unmapped_current_node_ids: z.array(nodeIdSchema),
|
||||
coverage: z.strictObject({
|
||||
status: z.enum(["complete-within-inputs", "partial", "truncated"]),
|
||||
status: z.enum(["complete-within-inputs", "partial"]),
|
||||
reference_inventory_state: z.enum(["complete", "partial", "unknown"]),
|
||||
application_graph_status: z.enum([
|
||||
"complete",
|
||||
@@ -131,13 +111,8 @@ export const sourceToBundleComparisonResultSchema = z.strictObject({
|
||||
"unavailable",
|
||||
]),
|
||||
retained_source_files: z.number().int().min(0),
|
||||
omitted_source_files: z.number().int().min(0),
|
||||
retained_application_nodes: z.number().int().min(0),
|
||||
omitted_application_nodes: z.number().int().min(0),
|
||||
candidate_evaluations: z.number().int().min(0),
|
||||
omitted_candidate_evaluations: z.number().int().min(0),
|
||||
omitted_candidate_references: z.number().int().min(0),
|
||||
omitted_unmapped_current_nodes: z.number().int().min(0),
|
||||
}),
|
||||
evidence_links: z.array(evidenceIdSchema).min(1).max(1),
|
||||
limitations: z.array(boundedTextSchema).max(1_000),
|
||||
|
||||
@@ -35,7 +35,6 @@ export interface SourceToBundleCandidateIndex {
|
||||
readonly byDigest: ReadonlyMap<string, ReadonlySet<string>>;
|
||||
readonly byPathSuffix: ReadonlyMap<string, ReadonlySet<string>>;
|
||||
readonly byBasename: ReadonlyMap<string, ReadonlySet<string>>;
|
||||
readonly pathIndexTruncated: boolean;
|
||||
}
|
||||
|
||||
const RELEVANT_NODE_KINDS = new Set([
|
||||
@@ -51,7 +50,6 @@ const PATH_PROPERTIES = [
|
||||
"resolved_path",
|
||||
"declared_path",
|
||||
] as const;
|
||||
const MAX_PATH_SUFFIXES = 128;
|
||||
|
||||
const signalWeight = (kind: SourceToBundleSignal["kind"]): number => {
|
||||
const entry = SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS.find(
|
||||
@@ -81,7 +79,7 @@ export const historicalSourceFiles = (
|
||||
)
|
||||
.sort((left, right) => compareText(left.path, right.path));
|
||||
|
||||
/** Build bounded-key indices without assigning fuzzy matches. */
|
||||
/** Build deterministic candidate indices without assigning fuzzy matches. */
|
||||
export const buildSourceToBundleCandidateIndex = (
|
||||
nodes: readonly ApplicationNode[],
|
||||
): SourceToBundleCandidateIndex => {
|
||||
@@ -89,14 +87,11 @@ export const buildSourceToBundleCandidateIndex = (
|
||||
const byDigest = new Map<string, Set<string>>();
|
||||
const byPathSuffix = new Map<string, Set<string>>();
|
||||
const byBasename = new Map<string, Set<string>>();
|
||||
let pathIndexTruncated = false;
|
||||
for (const projection of projections) {
|
||||
for (const digest of projection.digests)
|
||||
addIndexValue(byDigest, digest, projection.node.node_id);
|
||||
for (const path of projection.paths) {
|
||||
const suffixes = pathSuffixes(path.value);
|
||||
pathIndexTruncated ||= suffixes.truncated;
|
||||
for (const suffix of suffixes.values)
|
||||
for (const suffix of pathSuffixes(path.value))
|
||||
addIndexValue(byPathSuffix, suffix, projection.node.node_id);
|
||||
addIndexValue(
|
||||
byBasename,
|
||||
@@ -112,7 +107,6 @@ export const buildSourceToBundleCandidateIndex = (
|
||||
byDigest,
|
||||
byPathSuffix,
|
||||
byBasename,
|
||||
pathIndexTruncated,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -238,7 +232,7 @@ const signal = (
|
||||
kind,
|
||||
weight: signalWeight(kind),
|
||||
source_value: sourceValue,
|
||||
current_values: [...new Set(currentValues)].sort(compareText).slice(0, 64),
|
||||
current_values: [...new Set(currentValues)].sort(compareText),
|
||||
});
|
||||
|
||||
const candidateConfidence = (
|
||||
@@ -294,17 +288,9 @@ const uniquePaths = (paths: readonly CurrentPath[]): CurrentPath[] =>
|
||||
compareText(`${left.kind}\0${left.value}`, `${right.kind}\0${right.value}`),
|
||||
);
|
||||
|
||||
const pathSuffixes = (
|
||||
path: string,
|
||||
): { readonly values: readonly string[]; readonly truncated: boolean } => {
|
||||
const pathSuffixes = (path: string): string[] => {
|
||||
const parts = path.split("/");
|
||||
const start = Math.max(0, parts.length - MAX_PATH_SUFFIXES);
|
||||
return {
|
||||
values: parts
|
||||
.slice(start)
|
||||
.map((_, index) => parts.slice(start + index).join("/")),
|
||||
truncated: start > 0,
|
||||
};
|
||||
return parts.map((_, index) => parts.slice(index).join("/"));
|
||||
};
|
||||
|
||||
const addIndexValue = (
|
||||
|
||||
@@ -103,7 +103,6 @@ const sourceMapSchema = z.union([
|
||||
]);
|
||||
|
||||
const webTextArtifactSummarySchema = z.object({
|
||||
uri: z.string().regex(/^rea:\/\/web-content\/sha256\/[a-f0-9]{64}$/u),
|
||||
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
||||
bytes: z.number().int().min(0),
|
||||
media_type: z.string().min(1).max(256),
|
||||
|
||||
Reference in New Issue
Block a user