@saptools/service-flow 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +11 -2
- package/TECHNICAL-NOTE.md +13 -0
- package/dist/{chunk-SSSBQIMK.js → chunk-DUGM7FRA.js} +129 -88
- package/dist/chunk-DUGM7FRA.js.map +1 -0
- package/dist/cli.js +53 -23
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-SSSBQIMK.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
containsSupportedOutboundCall,
|
|
3
4
|
discoverRepositories,
|
|
4
5
|
linkWorkspace,
|
|
5
6
|
normalizePath,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
parsePackageJson,
|
|
11
12
|
parseServiceBindings,
|
|
12
13
|
trace
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-DUGM7FRA.js";
|
|
14
15
|
|
|
15
16
|
// src/cli.ts
|
|
16
17
|
import { Command } from "commander";
|
|
@@ -100,7 +101,7 @@ CREATE TABLE IF NOT EXISTS handler_classes (id INTEGER PRIMARY KEY, repo_id INTE
|
|
|
100
101
|
CREATE TABLE IF NOT EXISTS handler_methods (id INTEGER PRIMARY KEY, handler_class_id INTEGER NOT NULL, method_name TEXT NOT NULL, decorator_kind TEXT NOT NULL, decorator_value TEXT, decorator_raw_expression TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE CASCADE);
|
|
101
102
|
CREATE TABLE IF NOT EXISTS handler_registrations (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, handler_class_id INTEGER, class_name TEXT, import_source TEXT, registration_file TEXT NOT NULL, registration_line INTEGER NOT NULL, registration_kind TEXT NOT NULL, confidence REAL NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE SET NULL);
|
|
102
103
|
CREATE TABLE IF NOT EXISTS service_bindings (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, variable_name TEXT NOT NULL, alias TEXT, alias_expr TEXT, destination_expr TEXT, service_path_expr TEXT, is_dynamic INTEGER NOT NULL, placeholders_json TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, helper_chain_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
103
|
-
CREATE TABLE IF NOT EXISTS outbound_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_symbol_id INTEGER, call_type TEXT NOT NULL, service_binding_id INTEGER, method TEXT, operation_path_expr TEXT, query_entity TEXT, event_name_expr TEXT, payload_summary TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, confidence REAL NOT NULL, unresolved_reason TEXT, local_service_name TEXT, local_service_lookup TEXT, alias_chain_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(source_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL, FOREIGN KEY(service_binding_id) REFERENCES service_bindings(id) ON DELETE SET NULL);
|
|
104
|
+
CREATE TABLE IF NOT EXISTS outbound_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_symbol_id INTEGER, call_type TEXT NOT NULL, service_binding_id INTEGER, method TEXT, operation_path_expr TEXT, query_entity TEXT, event_name_expr TEXT, payload_summary TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, confidence REAL NOT NULL, unresolved_reason TEXT, local_service_name TEXT, local_service_lookup TEXT, alias_chain_json TEXT, evidence_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(source_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL, FOREIGN KEY(service_binding_id) REFERENCES service_bindings(id) ON DELETE SET NULL);
|
|
104
105
|
CREATE TABLE IF NOT EXISTS symbol_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, caller_symbol_id INTEGER NOT NULL, callee_symbol_id INTEGER, callee_expression TEXT NOT NULL, import_source TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, status TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, unresolved_reason TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(caller_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE, FOREIGN KEY(callee_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
105
106
|
CREATE TABLE IF NOT EXISTS graph_edges (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, edge_type TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'unresolved', from_kind TEXT NOT NULL, from_id TEXT NOT NULL, to_kind TEXT NOT NULL, to_id TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, is_dynamic INTEGER NOT NULL, unresolved_reason TEXT, generation INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
106
107
|
CREATE TABLE IF NOT EXISTS index_runs (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, started_at TEXT NOT NULL, finished_at TEXT, status TEXT NOT NULL, repo_count INTEGER NOT NULL, file_count INTEGER NOT NULL, diagnostic_count INTEGER NOT NULL, error_message TEXT, FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
@@ -114,7 +115,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(kind, name, path, rep
|
|
|
114
115
|
`;
|
|
115
116
|
|
|
116
117
|
// src/db/migrations.ts
|
|
117
|
-
var CURRENT_SCHEMA_VERSION =
|
|
118
|
+
var CURRENT_SCHEMA_VERSION = 5;
|
|
118
119
|
var columns = {
|
|
119
120
|
service_bindings: [
|
|
120
121
|
{ name: "helper_chain_json", ddl: "ALTER TABLE service_bindings ADD COLUMN helper_chain_json TEXT" },
|
|
@@ -145,7 +146,8 @@ var columns = {
|
|
|
145
146
|
outbound_calls: [
|
|
146
147
|
{ name: "local_service_name", ddl: "ALTER TABLE outbound_calls ADD COLUMN local_service_name TEXT" },
|
|
147
148
|
{ name: "local_service_lookup", ddl: "ALTER TABLE outbound_calls ADD COLUMN local_service_lookup TEXT" },
|
|
148
|
-
{ name: "alias_chain_json", ddl: "ALTER TABLE outbound_calls ADD COLUMN alias_chain_json TEXT" }
|
|
149
|
+
{ name: "alias_chain_json", ddl: "ALTER TABLE outbound_calls ADD COLUMN alias_chain_json TEXT" },
|
|
150
|
+
{ name: "evidence_json", ddl: "ALTER TABLE outbound_calls ADD COLUMN evidence_json TEXT" }
|
|
149
151
|
],
|
|
150
152
|
index_runs: [
|
|
151
153
|
{ name: "error_message", ddl: "ALTER TABLE index_runs ADD COLUMN error_message TEXT" }
|
|
@@ -185,7 +187,7 @@ function migrate(db) {
|
|
|
185
187
|
// package.json
|
|
186
188
|
var package_default = {
|
|
187
189
|
name: "@saptools/service-flow",
|
|
188
|
-
version: "0.1.
|
|
190
|
+
version: "0.1.18",
|
|
189
191
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
190
192
|
type: "module",
|
|
191
193
|
publishConfig: {
|
|
@@ -571,7 +573,7 @@ function resolveSymbolCallTarget(db, repoId, r) {
|
|
|
571
573
|
}
|
|
572
574
|
function insertCalls(db, repoId, rows) {
|
|
573
575
|
const stmt = db.prepare(
|
|
574
|
-
"INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,service_binding_id) VALUES(?,COALESCE((SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1))
|
|
576
|
+
"INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,evidence_json,service_binding_id) VALUES(?,COALESCE((SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1)),?,?,?,?,?,?,?,?,?,?,?,?,?,?,(SELECT id FROM service_bindings WHERE repo_id=? AND variable_name=? AND source_file=? ORDER BY CASE WHEN source_line<=? THEN 0 ELSE 1 END, ABS(source_line-?) ASC, id DESC LIMIT 1))"
|
|
575
577
|
);
|
|
576
578
|
for (const r of rows)
|
|
577
579
|
stmt.run(
|
|
@@ -596,6 +598,7 @@ function insertCalls(db, repoId, rows) {
|
|
|
596
598
|
r.localServiceName,
|
|
597
599
|
r.localServiceLookup,
|
|
598
600
|
r.aliasChain ? JSON.stringify(r.aliasChain) : null,
|
|
601
|
+
r.evidence ? JSON.stringify(r.evidence) : null,
|
|
599
602
|
repoId,
|
|
600
603
|
r.serviceVariableName,
|
|
601
604
|
r.sourceFile,
|
|
@@ -802,20 +805,6 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
802
805
|
} else ts.forEachChild(node, (child) => visitSymbols(child, parentClass));
|
|
803
806
|
};
|
|
804
807
|
visitSymbols(source);
|
|
805
|
-
const outboundCallNames = /* @__PURE__ */ new Set(["cds.run", "emit", "publish", "on", "send", "axios", "executeHttpRequest", "useOrFetchDestination"]);
|
|
806
|
-
const containsSupportedOutbound = (node) => {
|
|
807
|
-
let found = false;
|
|
808
|
-
const visit = (child) => {
|
|
809
|
-
if (found) return;
|
|
810
|
-
if (ts.isCallExpression(child)) {
|
|
811
|
-
const callee = callName(child.expression);
|
|
812
|
-
if (outboundCallNames.has(callee.expression) || (callee.member ? outboundCallNames.has(callee.member) : false)) found = true;
|
|
813
|
-
}
|
|
814
|
-
ts.forEachChild(child, visit);
|
|
815
|
-
};
|
|
816
|
-
visit(node);
|
|
817
|
-
return found;
|
|
818
|
-
};
|
|
819
808
|
const isTopLevelCallback = (node) => {
|
|
820
809
|
if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return false;
|
|
821
810
|
if (!ts.isCallExpression(node.parent)) return false;
|
|
@@ -824,7 +813,7 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
824
813
|
return Boolean(member && ["bootstrap", "served", "connect", "on", "once", "use", "get", "post", "put", "patch", "delete", "subscribe"].includes(member));
|
|
825
814
|
};
|
|
826
815
|
const visitCallbackSymbols = (node) => {
|
|
827
|
-
if (isTopLevelCallback(node) &&
|
|
816
|
+
if (isTopLevelCallback(node) && containsSupportedOutboundCall(node)) {
|
|
828
817
|
const startLine = lineOf(source, node.getStart(source));
|
|
829
818
|
const name = `callback:${startLine}`;
|
|
830
819
|
symbols.push({ kind: "callback", localName: name, qualifiedName: `module:${sourceFile}#${name}`, sourceFile, startLine, endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: false, importExportEvidence: { source: "synthetic_outbound_callback", callbackLine: startLine } });
|
|
@@ -832,6 +821,20 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
832
821
|
ts.forEachChild(node, visitCallbackSymbols);
|
|
833
822
|
};
|
|
834
823
|
visitCallbackSymbols(source);
|
|
824
|
+
const visitEventRegistrationSymbols = (node) => {
|
|
825
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "on") {
|
|
826
|
+
const receiver = node.expression.expression.getText(source);
|
|
827
|
+
const eventArg = node.arguments[0];
|
|
828
|
+
if ((receiver === "cds" || /^(srv|service|serviceClient|messaging|messageClient|eventClient|.*Client)$/.test(receiver)) && eventArg && (ts.isStringLiteral(eventArg) || ts.isNoSubstitutionTemplateLiteral(eventArg))) {
|
|
829
|
+
const startLine = lineOf(source, node.getStart(source));
|
|
830
|
+
const eventName = eventArg.text.replace(/[^A-Za-z0-9_$-]/g, "_");
|
|
831
|
+
const name = `event:${eventName}:${startLine}`;
|
|
832
|
+
symbols.push({ kind: "event_registration", localName: name, qualifiedName: `module:${sourceFile}#${name}`, sourceFile, startLine, endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: false, importExportEvidence: { source: "synthetic_event_registration", eventName: eventArg.text, registrationLine: startLine, receiver } });
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
ts.forEachChild(node, visitEventRegistrationSymbols);
|
|
836
|
+
};
|
|
837
|
+
visitEventRegistrationSymbols(source);
|
|
835
838
|
const visitProxyVariables = (node) => {
|
|
836
839
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isPropertyAccessExpression(node.initializer.expression)) {
|
|
837
840
|
const callee = callName(node.initializer.expression);
|
|
@@ -1139,8 +1142,31 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1139
1142
|
const evidence = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN json_valid(evidence_json)=0 OR json_type(evidence_json) <> 'object' THEN 1 ELSE 0 END) nonObject FROM symbol_calls").get();
|
|
1140
1143
|
const dbq = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN query_entity IS NOT NULL THEN 1 ELSE 0 END) known, SUM(CASE WHEN query_entity IS NULL THEN 1 ELSE 0 END) unknown FROM outbound_calls WHERE call_type='local_db_query'").get();
|
|
1141
1144
|
const outbound = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN source_symbol_id IS NULL THEN 1 ELSE 0 END) withoutOwnership FROM outbound_calls").get();
|
|
1145
|
+
const outboundEvidence = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN evidence_json IS NULL THEN 1 ELSE 0 END) missing, SUM(CASE WHEN evidence_json IS NOT NULL AND json_valid(evidence_json)=0 THEN 1 ELSE 0 END) invalid, SUM(CASE WHEN evidence_json IS NOT NULL AND json_valid(evidence_json)=1 AND json_type(evidence_json) <> 'object' THEN 1 ELSE 0 END) nonObject FROM outbound_calls").get();
|
|
1146
|
+
const outboundEvidenceExamples = db.prepare("SELECT call_type callType, source_file sourceFile, source_line sourceLine FROM outbound_calls WHERE evidence_json IS NULL OR json_valid(evidence_json)=0 OR json_type(evidence_json) <> 'object' ORDER BY source_file, source_line LIMIT 10").all();
|
|
1147
|
+
const graphEvidence = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN e.evidence_json IS NULL OR json_valid(e.evidence_json)=0 OR json_type(e.evidence_json) <> 'object' THEN 1 ELSE 0 END) nonObject, SUM(CASE WHEN e.evidence_json IS NOT NULL AND json_valid(e.evidence_json)=1 AND json_extract(e.evidence_json,'$.outboundEvidence.parser') IS NOT NULL THEN 1 ELSE 0 END) withOutboundEvidence FROM graph_edges e WHERE e.from_kind='call'").get();
|
|
1148
|
+
const graphEvidenceExamples = db.prepare("SELECT c.call_type callType,c.source_file sourceFile,c.source_line sourceLine,e.edge_type edgeType FROM graph_edges e JOIN outbound_calls c ON e.from_kind='call' AND c.id=CAST(e.from_id AS INTEGER) WHERE e.evidence_json IS NULL OR json_valid(e.evidence_json)=0 OR json_type(e.evidence_json) <> 'object' OR json_extract(e.evidence_json,'$.outboundEvidence.parser') IS NULL ORDER BY c.source_file,c.source_line LIMIT 10").all();
|
|
1149
|
+
const eventReceiver = db.prepare("SELECT COUNT(*) total, SUM(CASE WHEN call_type IN ('async_emit','async_subscribe') THEN 1 ELSE 0 END) eventTotal, SUM(CASE WHEN call_type IN ('async_emit','async_subscribe') AND (json_extract(evidence_json,'$.receiverClassification') IS NULL OR json_extract(evidence_json,'$.receiverClassification') <> 'cap_evidence') THEN 1 ELSE 0 END) questionable FROM outbound_calls").get();
|
|
1150
|
+
const dynamicTerminal = db.prepare("SELECT COUNT(*) count FROM graph_edges WHERE status='terminal' AND is_dynamic=1").get();
|
|
1142
1151
|
const ownerlessByType = db.prepare("SELECT call_type callType, COUNT(*) count FROM outbound_calls WHERE source_symbol_id IS NULL GROUP BY call_type ORDER BY count DESC, call_type").all();
|
|
1143
|
-
const
|
|
1152
|
+
const ownerlessByCategory = db.prepare(`SELECT CASE
|
|
1153
|
+
WHEN COALESCE(evidence_json,'') LIKE '%comment_or_non_executable_source%' THEN 'comment_or_non_executable_source'
|
|
1154
|
+
WHEN call_type='async_subscribe' AND COALESCE(evidence_json,'') LIKE '%cap_service_event_subscription%' THEN 'top_level_event_registration'
|
|
1155
|
+
WHEN call_type='async_subscribe' THEN 'generic_event_listener_ignored_or_unowned'
|
|
1156
|
+
WHEN EXISTS (SELECT 1 FROM symbols s WHERE s.repo_id=outbound_calls.repo_id AND s.source_file=outbound_calls.source_file) THEN 'line_range_mismatch'
|
|
1157
|
+
WHEN source_line <= 1 THEN 'unsupported_function_shape'
|
|
1158
|
+
WHEN source_line > 1 THEN 'unsupported_callback_shape'
|
|
1159
|
+
ELSE 'unknown' END category, COUNT(*) count
|
|
1160
|
+
FROM outbound_calls WHERE source_symbol_id IS NULL GROUP BY category ORDER BY count DESC, category`).all();
|
|
1161
|
+
const ownerlessExamples = db.prepare(`SELECT CASE
|
|
1162
|
+
WHEN COALESCE(evidence_json,'') LIKE '%comment_or_non_executable_source%' THEN 'comment_or_non_executable_source'
|
|
1163
|
+
WHEN call_type='async_subscribe' AND COALESCE(evidence_json,'') LIKE '%cap_service_event_subscription%' THEN 'top_level_event_registration'
|
|
1164
|
+
WHEN call_type='async_subscribe' THEN 'generic_event_listener_ignored_or_unowned'
|
|
1165
|
+
WHEN EXISTS (SELECT 1 FROM symbols s WHERE s.repo_id=outbound_calls.repo_id AND s.source_file=outbound_calls.source_file) THEN 'line_range_mismatch'
|
|
1166
|
+
WHEN source_line <= 1 THEN 'unsupported_function_shape'
|
|
1167
|
+
WHEN source_line > 1 THEN 'unsupported_callback_shape'
|
|
1168
|
+
ELSE 'unknown' END category, call_type callType, source_file sourceFile, source_line sourceLine, unresolved_reason unresolvedReason
|
|
1169
|
+
FROM outbound_calls WHERE source_symbol_id IS NULL ORDER BY category, source_file, source_line LIMIT 10`).all();
|
|
1144
1170
|
const symbolTotal = Number(symbol.total ?? 0);
|
|
1145
1171
|
const symbolUnresolved = Number(symbol.unresolved ?? 0);
|
|
1146
1172
|
const symbolUnresolvedRatio = symbolTotal === 0 ? 0 : Number((symbolUnresolved / symbolTotal).toFixed(4));
|
|
@@ -1152,9 +1178,13 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1152
1178
|
const outboundWithoutOwnershipRatio = outboundTotal === 0 ? 0 : Number((outboundWithoutOwnership / outboundTotal).toFixed(4));
|
|
1153
1179
|
return [
|
|
1154
1180
|
{ severity: Number(evidence.nonObject ?? 0) > 0 ? "warning" : "info", code: "strict_symbol_call_evidence_quality", message: "Symbol-call evidence JSON object aggregate", total: Number(evidence.total ?? 0), nonObject: Number(evidence.nonObject ?? 0) },
|
|
1181
|
+
{ severity: Number(outboundEvidence.missing ?? 0) + Number(outboundEvidence.invalid ?? 0) + Number(outboundEvidence.nonObject ?? 0) > 0 ? "warning" : "info", code: "strict_outbound_evidence_quality", message: "Outbound parser evidence JSON object aggregate", total: Number(outboundEvidence.total ?? 0), missing: Number(outboundEvidence.missing ?? 0), invalid: Number(outboundEvidence.invalid ?? 0), nonObject: Number(outboundEvidence.nonObject ?? 0), examples: outboundEvidenceExamples },
|
|
1182
|
+
{ severity: Number(graphEvidence.nonObject ?? 0) > 0 || Number(graphEvidence.withOutboundEvidence ?? 0) < Number(graphEvidence.total ?? 0) ? "warning" : "info", code: "strict_graph_evidence_quality", message: "Call-derived graph evidence and parser-evidence propagation aggregate", total: Number(graphEvidence.total ?? 0), nonObject: Number(graphEvidence.nonObject ?? 0), withOutboundEvidence: Number(graphEvidence.withOutboundEvidence ?? 0), examples: graphEvidenceExamples },
|
|
1183
|
+
{ severity: Number(eventReceiver.questionable ?? 0) > 0 ? "warning" : "info", code: "strict_event_receiver_classification_quality", message: "CAP event receiver classification aggregate", eventTotal: Number(eventReceiver.eventTotal ?? 0), questionable: Number(eventReceiver.questionable ?? 0) },
|
|
1184
|
+
{ severity: Number(dynamicTerminal.count ?? 0) > 0 ? "warning" : "info", code: "strict_graph_dynamic_flag_consistency", message: "Graph dynamic flag consistency aggregate", dynamicTerminalEdges: Number(dynamicTerminal.count ?? 0) },
|
|
1155
1185
|
{ severity: symbolUnresolvedRatio > symbolUnresolvedThreshold ? "warning" : "info", code: "strict_symbol_call_quality", message: "Symbol-call quality aggregate", total: symbolTotal, resolved: Number(symbol.resolved ?? 0), unresolved: symbolUnresolved, unresolvedRatio: symbolUnresolvedRatio, unresolvedRatioThreshold: symbolUnresolvedThreshold, topUnresolvedCallees: top },
|
|
1156
1186
|
{ severity: queryUnknownRatio > dbUnknownThreshold ? "warning" : "info", code: "strict_db_query_quality", message: "Local DB query quality aggregate", total: queryTotal, known: Number(dbq.known ?? 0), unknown: queryUnknown, unknownRatio: queryUnknownRatio, unknownRatioThreshold: dbUnknownThreshold },
|
|
1157
|
-
{ severity: outboundWithoutOwnershipRatio > outboundUnownedThreshold ? "warning" : "info", code: "strict_outbound_source_ownership_quality", message: "Outbound call source-symbol ownership aggregate", total: outboundTotal, withoutOwnership: outboundWithoutOwnership, withoutOwnershipRatio: outboundWithoutOwnershipRatio, withoutOwnershipRatioThreshold: outboundUnownedThreshold, ownerlessByType,
|
|
1187
|
+
{ severity: outboundWithoutOwnershipRatio > outboundUnownedThreshold ? "warning" : "info", code: "strict_outbound_source_ownership_quality", message: "Outbound call source-symbol ownership aggregate", total: outboundTotal, withoutOwnership: outboundWithoutOwnership, withoutOwnershipRatio: outboundWithoutOwnershipRatio, withoutOwnershipRatioThreshold: outboundUnownedThreshold, ownerlessByType, ownerlessByCategory, ownerlessExamples }
|
|
1158
1188
|
];
|
|
1159
1189
|
}
|
|
1160
1190
|
function createProgram() {
|