@neat.is/core 0.9.2-dev.20260822 → 0.9.3-dev.20260823
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/dist/{chunk-RQQUI3NQ.js → chunk-LRYPKC3B.js} +745 -50
- package/dist/chunk-LRYPKC3B.js.map +1 -0
- package/dist/{chunk-L4SZIIER.js → chunk-WJGZYEUG.js} +2 -2
- package/dist/cli.cjs +1203 -283
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +333 -110
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +788 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +788 -92
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +787 -91
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-RQQUI3NQ.js.map +0 -1
- /package/dist/{chunk-L4SZIIER.js.map → chunk-WJGZYEUG.js.map} +0 -0
package/dist/neatd.cjs
CHANGED
|
@@ -1355,7 +1355,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
|
|
|
1355
1355
|
for (const id of walk10.path) {
|
|
1356
1356
|
const owner = resolveOwningService(graph, id);
|
|
1357
1357
|
if (!owner) continue;
|
|
1358
|
-
const { id:
|
|
1358
|
+
const { id: serviceId16, svc } = owner;
|
|
1359
1359
|
const deps = svc.dependencies ?? {};
|
|
1360
1360
|
for (const pair of candidatePairs) {
|
|
1361
1361
|
const declared = deps[pair.driver];
|
|
@@ -1368,7 +1368,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
|
|
|
1368
1368
|
);
|
|
1369
1369
|
if (!result.compatible) {
|
|
1370
1370
|
return {
|
|
1371
|
-
rootCauseNode:
|
|
1371
|
+
rootCauseNode: serviceId16,
|
|
1372
1372
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1373
1373
|
...result.minDriverVersion ? {
|
|
1374
1374
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1383,7 +1383,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
1383
1383
|
for (const id of walk10.path) {
|
|
1384
1384
|
const owner = resolveOwningService(graph, id);
|
|
1385
1385
|
if (!owner) continue;
|
|
1386
|
-
const { id:
|
|
1386
|
+
const { id: serviceId16, svc } = owner;
|
|
1387
1387
|
const deps = svc.dependencies ?? {};
|
|
1388
1388
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1389
1389
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1392,7 +1392,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
1392
1392
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1393
1393
|
if (!result.compatible && result.reason) {
|
|
1394
1394
|
return {
|
|
1395
|
-
rootCauseNode:
|
|
1395
|
+
rootCauseNode: serviceId16,
|
|
1396
1396
|
rootCauseReason: result.reason,
|
|
1397
1397
|
...result.requiredNodeVersion ? {
|
|
1398
1398
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1407,7 +1407,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
1407
1407
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1408
1408
|
if (!result.compatible && result.reason) {
|
|
1409
1409
|
return {
|
|
1410
|
-
rootCauseNode:
|
|
1410
|
+
rootCauseNode: serviceId16,
|
|
1411
1411
|
rootCauseReason: result.reason,
|
|
1412
1412
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1413
1413
|
};
|
|
@@ -1512,9 +1512,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1512
1512
|
function isFailingCallEdge(e) {
|
|
1513
1513
|
return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
1514
1514
|
}
|
|
1515
|
-
function callSourcesForService(graph,
|
|
1516
|
-
const ids = [
|
|
1517
|
-
for (const edgeId of graph.outboundEdges(
|
|
1515
|
+
function callSourcesForService(graph, serviceId16) {
|
|
1516
|
+
const ids = [serviceId16];
|
|
1517
|
+
for (const edgeId of graph.outboundEdges(serviceId16)) {
|
|
1518
1518
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1519
1519
|
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1520
1520
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -1538,9 +1538,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
1538
1538
|
}
|
|
1539
1539
|
return id < curId;
|
|
1540
1540
|
}
|
|
1541
|
-
function dominantFailingCall(graph,
|
|
1541
|
+
function dominantFailingCall(graph, serviceId16, visited) {
|
|
1542
1542
|
let best = null;
|
|
1543
|
-
for (const src of callSourcesForService(graph,
|
|
1543
|
+
for (const src of callSourcesForService(graph, serviceId16)) {
|
|
1544
1544
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1545
1545
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1546
1546
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -1579,9 +1579,9 @@ function staleCallDominates(e, id, curEdge, curId) {
|
|
|
1579
1579
|
if (ev !== cv) return ev > cv;
|
|
1580
1580
|
return id < curId;
|
|
1581
1581
|
}
|
|
1582
|
-
function dominantStaleCall(graph,
|
|
1582
|
+
function dominantStaleCall(graph, serviceId16, visited) {
|
|
1583
1583
|
const bestByCallee = /* @__PURE__ */ new Map();
|
|
1584
|
-
for (const src of callSourcesForService(graph,
|
|
1584
|
+
for (const src of callSourcesForService(graph, serviceId16)) {
|
|
1585
1585
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1586
1586
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1587
1587
|
if (e.type !== import_types.EdgeType.CALLS) continue;
|
|
@@ -4780,11 +4780,11 @@ function actixAttributeRoute(attrItem, out) {
|
|
|
4780
4780
|
if (!nameNode) return;
|
|
4781
4781
|
const macro = nameNode.type === "identifier" ? nameNode.text : nameNode.type === "scoped_identifier" ? nameNode.childForFieldName("name")?.text ?? null : null;
|
|
4782
4782
|
if (!macro) return;
|
|
4783
|
-
const
|
|
4784
|
-
if (!
|
|
4783
|
+
const tokens2 = attr.childForFieldName("arguments");
|
|
4784
|
+
if (!tokens2 || tokens2.type !== "token_tree") return;
|
|
4785
4785
|
const strings = [];
|
|
4786
|
-
for (let i = 0; i <
|
|
4787
|
-
const s = rustStringContent(
|
|
4786
|
+
for (let i = 0; i < tokens2.namedChildCount; i++) {
|
|
4787
|
+
const s = rustStringContent(tokens2.namedChild(i));
|
|
4788
4788
|
if (s !== null) strings.push(s);
|
|
4789
4789
|
}
|
|
4790
4790
|
const pathStr = strings[0];
|
|
@@ -6678,29 +6678,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
6678
6678
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
6679
6679
|
});
|
|
6680
6680
|
let promoted = 0;
|
|
6681
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
6681
|
+
for (const { frontierId: frontierId2, serviceId: serviceId16 } of toPromote) {
|
|
6682
6682
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
6683
6683
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
6684
6684
|
if (!gate.allowed) {
|
|
6685
6685
|
continue;
|
|
6686
6686
|
}
|
|
6687
6687
|
}
|
|
6688
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
6688
|
+
rewireFrontierEdges(graph, frontierId2, serviceId16);
|
|
6689
6689
|
graph.dropNode(frontierId2);
|
|
6690
6690
|
promoted++;
|
|
6691
6691
|
}
|
|
6692
6692
|
return promoted;
|
|
6693
6693
|
}
|
|
6694
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
6694
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId16) {
|
|
6695
6695
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
6696
6696
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
6697
6697
|
for (const edgeId of inbound) {
|
|
6698
6698
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6699
|
-
rebuildEdge(graph, edge, edge.source,
|
|
6699
|
+
rebuildEdge(graph, edge, edge.source, serviceId16, edgeId);
|
|
6700
6700
|
}
|
|
6701
6701
|
for (const edgeId of outbound) {
|
|
6702
6702
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6703
|
-
rebuildEdge(graph, edge,
|
|
6703
|
+
rebuildEdge(graph, edge, serviceId16, edge.target, edgeId);
|
|
6704
6704
|
}
|
|
6705
6705
|
}
|
|
6706
6706
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -7812,9 +7812,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
7812
7812
|
"StatefulSet",
|
|
7813
7813
|
"DaemonSet"
|
|
7814
7814
|
]);
|
|
7815
|
-
function addAliases(graph,
|
|
7816
|
-
if (!graph.hasNode(
|
|
7817
|
-
const node = graph.getNodeAttributes(
|
|
7815
|
+
function addAliases(graph, serviceId16, candidates) {
|
|
7816
|
+
if (!graph.hasNode(serviceId16)) return;
|
|
7817
|
+
const node = graph.getNodeAttributes(serviceId16);
|
|
7818
7818
|
if (node.type !== import_types19.NodeType.ServiceNode) return;
|
|
7819
7819
|
const set = new Set(node.aliases ?? []);
|
|
7820
7820
|
for (const c of candidates) {
|
|
@@ -7824,7 +7824,7 @@ function addAliases(graph, serviceId15, candidates) {
|
|
|
7824
7824
|
}
|
|
7825
7825
|
if (set.size === 0) return;
|
|
7826
7826
|
const updated = { ...node, aliases: [...set].sort() };
|
|
7827
|
-
graph.replaceNodeAttributes(
|
|
7827
|
+
graph.replaceNodeAttributes(serviceId16, updated);
|
|
7828
7828
|
}
|
|
7829
7829
|
function indexServicesByName(services) {
|
|
7830
7830
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -7857,12 +7857,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
7857
7857
|
}
|
|
7858
7858
|
if (!compose?.services) return;
|
|
7859
7859
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
7860
|
-
const
|
|
7861
|
-
if (!
|
|
7860
|
+
const serviceId16 = serviceIndex.get(composeName);
|
|
7861
|
+
if (!serviceId16) continue;
|
|
7862
7862
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
7863
7863
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
7864
7864
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
7865
|
-
addAliases(graph,
|
|
7865
|
+
addAliases(graph, serviceId16, aliases);
|
|
7866
7866
|
}
|
|
7867
7867
|
}
|
|
7868
7868
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -9852,12 +9852,12 @@ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
|
|
|
9852
9852
|
const value = await interpolateEnvRefs(raw, serviceDir);
|
|
9853
9853
|
if (!looksLike(value)) continue;
|
|
9854
9854
|
const parsed = parse11(value);
|
|
9855
|
-
if (parsed) out.push(parsed);
|
|
9855
|
+
if (parsed) out.push({ ...parsed, hostSource: "config" });
|
|
9856
9856
|
}
|
|
9857
9857
|
for (const lit of literals) {
|
|
9858
9858
|
if (!looksLike(lit)) continue;
|
|
9859
9859
|
const parsed = parse11(lit);
|
|
9860
|
-
if (parsed) out.push(parsed);
|
|
9860
|
+
if (parsed) out.push({ ...parsed, hostSource: "literal" });
|
|
9861
9861
|
}
|
|
9862
9862
|
return out;
|
|
9863
9863
|
}
|
|
@@ -10126,7 +10126,10 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
10126
10126
|
type: import_types23.EdgeType.CONNECTS_TO,
|
|
10127
10127
|
provenance: import_types23.Provenance.EXTRACTED,
|
|
10128
10128
|
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
10129
|
-
|
|
10129
|
+
// Carry how the host was recovered (ADR-213) so the divergence ranker
|
|
10130
|
+
// can tell a real declared store from a hardcoded fault-injection /
|
|
10131
|
+
// flag-gated probe. Only set when the parser distinguished the two.
|
|
10132
|
+
evidence: config.hostSource ? { file: evidenceFile, hostSource: config.hostSource } : { file: evidenceFile }
|
|
10130
10133
|
};
|
|
10131
10134
|
if (!graph.hasEdge(edge.id)) {
|
|
10132
10135
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -15851,7 +15854,7 @@ var Projects = class {
|
|
|
15851
15854
|
init_cjs_shims();
|
|
15852
15855
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
15853
15856
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
15854
|
-
var
|
|
15857
|
+
var import_types93 = require("@neat.is/types");
|
|
15855
15858
|
|
|
15856
15859
|
// src/extend/index.ts
|
|
15857
15860
|
init_cjs_shims();
|
|
@@ -16455,6 +16458,53 @@ function detectColumnDrift(node) {
|
|
|
16455
16458
|
function involvesNode(d, nodeId) {
|
|
16456
16459
|
return d.source === nodeId || d.target === nodeId;
|
|
16457
16460
|
}
|
|
16461
|
+
function datastoreEverObserved(graph, nodeId) {
|
|
16462
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
16463
|
+
const n = graph.getNodeAttributes(nodeId);
|
|
16464
|
+
if (n.type === import_types58.NodeType.DatabaseNode) {
|
|
16465
|
+
const via = n.discoveredVia;
|
|
16466
|
+
if (via === "otel" || via === "merged") return true;
|
|
16467
|
+
}
|
|
16468
|
+
for (const edgeId of graph.inboundEdges(nodeId)) {
|
|
16469
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
16470
|
+
if (e.provenance === import_types58.Provenance.OBSERVED || e.provenance === import_types58.Provenance.STALE) return true;
|
|
16471
|
+
}
|
|
16472
|
+
return false;
|
|
16473
|
+
}
|
|
16474
|
+
function serviceHasObservedSameEngineStore(graph, buckets2, serviceId16, engine, excludeTarget) {
|
|
16475
|
+
for (const bucket of buckets2.values()) {
|
|
16476
|
+
if (bucket.type !== import_types58.EdgeType.CONNECTS_TO) continue;
|
|
16477
|
+
if (bucket.source !== serviceId16) continue;
|
|
16478
|
+
if (bucket.target === excludeTarget) continue;
|
|
16479
|
+
if (!graph.hasNode(bucket.target)) continue;
|
|
16480
|
+
const target = graph.getNodeAttributes(bucket.target);
|
|
16481
|
+
if (target.type !== import_types58.NodeType.DatabaseNode) continue;
|
|
16482
|
+
if (target.engine !== engine) continue;
|
|
16483
|
+
if (bucket.observed || datastoreEverObserved(graph, bucket.target)) return true;
|
|
16484
|
+
}
|
|
16485
|
+
return false;
|
|
16486
|
+
}
|
|
16487
|
+
var DEAD_CODE_PROBE_CONFIDENCE = 0.1;
|
|
16488
|
+
function dampenDeadCodeProbes(graph, buckets2, all) {
|
|
16489
|
+
return all.map((d) => {
|
|
16490
|
+
if (d.type !== "missing-observed") return d;
|
|
16491
|
+
if (!d.extracted || d.edgeType !== import_types58.EdgeType.CONNECTS_TO) return d;
|
|
16492
|
+
if (!graph.hasNode(d.target)) return d;
|
|
16493
|
+
const target = graph.getNodeAttributes(d.target);
|
|
16494
|
+
if (target.type !== import_types58.NodeType.DatabaseNode) return d;
|
|
16495
|
+
if (d.extracted.evidence?.hostSource !== "literal") return d;
|
|
16496
|
+
if (datastoreEverObserved(graph, d.target)) return d;
|
|
16497
|
+
const engine = target.engine;
|
|
16498
|
+
if (!serviceHasObservedSameEngineStore(graph, buckets2, d.source, engine, d.target)) return d;
|
|
16499
|
+
const host = target.host ?? target.name;
|
|
16500
|
+
return {
|
|
16501
|
+
...d,
|
|
16502
|
+
confidence: Math.min(d.confidence, DEAD_CODE_PROBE_CONFIDENCE),
|
|
16503
|
+
reason: `${d.source} declares a ${engine} connection to a hardcoded-literal host (${host}) that production has never observed, while it does observe another ${engine} store \u2014 this reads as a flag-gated or dead-code declaration (e.g. a fault-injection probe), not a real declared-vs-observed gap.`,
|
|
16504
|
+
recommendation: "Confirm this is an intentional dead alternate \u2014 a fault-injection probe or a flag-gated branch. If it is meant to run in production, check the feature flag or conditional that gates it; if not, it can be ignored or removed."
|
|
16505
|
+
};
|
|
16506
|
+
});
|
|
16507
|
+
}
|
|
16458
16508
|
function suppressHostMismatchHalves(all) {
|
|
16459
16509
|
const observedHalf = /* @__PURE__ */ new Set();
|
|
16460
16510
|
const declaredHalf = /* @__PURE__ */ new Set();
|
|
@@ -16491,7 +16541,8 @@ function computeDivergences(graph, opts = {}) {
|
|
|
16491
16541
|
}
|
|
16492
16542
|
});
|
|
16493
16543
|
const reconciled = suppressHostMismatchHalves(all);
|
|
16494
|
-
|
|
16544
|
+
const dampened = dampenDeadCodeProbes(graph, buckets2, reconciled);
|
|
16545
|
+
let filtered = dampened;
|
|
16495
16546
|
if (opts.type) {
|
|
16496
16547
|
const allowed = opts.type;
|
|
16497
16548
|
filtered = filtered.filter((d) => allowed.has(d.type));
|
|
@@ -16574,6 +16625,639 @@ function queryLogEntries(opts) {
|
|
|
16574
16625
|
return merged;
|
|
16575
16626
|
}
|
|
16576
16627
|
|
|
16628
|
+
// src/ask.ts
|
|
16629
|
+
init_cjs_shims();
|
|
16630
|
+
var import_types59 = require("@neat.is/types");
|
|
16631
|
+
var DEFAULT_MAX_NODES = 3;
|
|
16632
|
+
var MAX_FACTS_PER_SECTION = 6;
|
|
16633
|
+
var INTENT_RULES = [
|
|
16634
|
+
{
|
|
16635
|
+
intent: "root-cause",
|
|
16636
|
+
test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
|
|
16637
|
+
},
|
|
16638
|
+
{
|
|
16639
|
+
intent: "blast-radius",
|
|
16640
|
+
test: /\b(blast|break[\s-]?if|breaks[\s-]?if|impact|downstream|dependents?|redeploy|who\s+(?:uses|calls|depends)|what\s+depends\s+on|affect(?:s|ed)?)\b/
|
|
16641
|
+
},
|
|
16642
|
+
{
|
|
16643
|
+
intent: "divergence",
|
|
16644
|
+
test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
|
|
16645
|
+
},
|
|
16646
|
+
{
|
|
16647
|
+
intent: "incidents",
|
|
16648
|
+
test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
|
|
16649
|
+
},
|
|
16650
|
+
{
|
|
16651
|
+
intent: "observed",
|
|
16652
|
+
test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
|
|
16653
|
+
},
|
|
16654
|
+
{
|
|
16655
|
+
intent: "dependencies",
|
|
16656
|
+
test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
|
|
16657
|
+
}
|
|
16658
|
+
];
|
|
16659
|
+
function classifyIntent(question) {
|
|
16660
|
+
const q = question.toLowerCase();
|
|
16661
|
+
for (const rule of INTENT_RULES) {
|
|
16662
|
+
if (rule.test.test(q)) return rule.intent;
|
|
16663
|
+
}
|
|
16664
|
+
return "overview";
|
|
16665
|
+
}
|
|
16666
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
16667
|
+
"the",
|
|
16668
|
+
"a",
|
|
16669
|
+
"an",
|
|
16670
|
+
"is",
|
|
16671
|
+
"are",
|
|
16672
|
+
"was",
|
|
16673
|
+
"were",
|
|
16674
|
+
"be",
|
|
16675
|
+
"been",
|
|
16676
|
+
"being",
|
|
16677
|
+
"of",
|
|
16678
|
+
"to",
|
|
16679
|
+
"in",
|
|
16680
|
+
"on",
|
|
16681
|
+
"at",
|
|
16682
|
+
"by",
|
|
16683
|
+
"for",
|
|
16684
|
+
"with",
|
|
16685
|
+
"and",
|
|
16686
|
+
"or",
|
|
16687
|
+
"but",
|
|
16688
|
+
"if",
|
|
16689
|
+
"as",
|
|
16690
|
+
"this",
|
|
16691
|
+
"that",
|
|
16692
|
+
"these",
|
|
16693
|
+
"those",
|
|
16694
|
+
"it",
|
|
16695
|
+
"its",
|
|
16696
|
+
"do",
|
|
16697
|
+
"does",
|
|
16698
|
+
"did",
|
|
16699
|
+
"has",
|
|
16700
|
+
"have",
|
|
16701
|
+
"had",
|
|
16702
|
+
"what",
|
|
16703
|
+
"which",
|
|
16704
|
+
"who",
|
|
16705
|
+
"whom",
|
|
16706
|
+
"how",
|
|
16707
|
+
"when",
|
|
16708
|
+
"where",
|
|
16709
|
+
"my",
|
|
16710
|
+
"me",
|
|
16711
|
+
"i",
|
|
16712
|
+
"we",
|
|
16713
|
+
"you",
|
|
16714
|
+
"they",
|
|
16715
|
+
"them",
|
|
16716
|
+
"from",
|
|
16717
|
+
"into",
|
|
16718
|
+
"about",
|
|
16719
|
+
"would",
|
|
16720
|
+
"will",
|
|
16721
|
+
"can",
|
|
16722
|
+
"could",
|
|
16723
|
+
"should",
|
|
16724
|
+
"anything",
|
|
16725
|
+
"everything",
|
|
16726
|
+
"something",
|
|
16727
|
+
"all",
|
|
16728
|
+
"any",
|
|
16729
|
+
"some",
|
|
16730
|
+
"no",
|
|
16731
|
+
"not",
|
|
16732
|
+
"so",
|
|
16733
|
+
"up",
|
|
16734
|
+
"out",
|
|
16735
|
+
"over",
|
|
16736
|
+
"get",
|
|
16737
|
+
"got",
|
|
16738
|
+
"show",
|
|
16739
|
+
"tell",
|
|
16740
|
+
"explain",
|
|
16741
|
+
"find",
|
|
16742
|
+
"give",
|
|
16743
|
+
"happen",
|
|
16744
|
+
"happens",
|
|
16745
|
+
"happening",
|
|
16746
|
+
"here",
|
|
16747
|
+
"there",
|
|
16748
|
+
"system",
|
|
16749
|
+
"code",
|
|
16750
|
+
"service",
|
|
16751
|
+
"node",
|
|
16752
|
+
"graph"
|
|
16753
|
+
]);
|
|
16754
|
+
var INTENT_WORDS = /* @__PURE__ */ new Set([
|
|
16755
|
+
"why",
|
|
16756
|
+
"root",
|
|
16757
|
+
"cause",
|
|
16758
|
+
"failing",
|
|
16759
|
+
"fail",
|
|
16760
|
+
"fails",
|
|
16761
|
+
"failed",
|
|
16762
|
+
"breaking",
|
|
16763
|
+
"broke",
|
|
16764
|
+
"broken",
|
|
16765
|
+
"crash",
|
|
16766
|
+
"crashing",
|
|
16767
|
+
"crashed",
|
|
16768
|
+
"throw",
|
|
16769
|
+
"throwing",
|
|
16770
|
+
"throws",
|
|
16771
|
+
"culprit",
|
|
16772
|
+
"blast",
|
|
16773
|
+
"radius",
|
|
16774
|
+
"break",
|
|
16775
|
+
"breaks",
|
|
16776
|
+
"impact",
|
|
16777
|
+
"downstream",
|
|
16778
|
+
"dependent",
|
|
16779
|
+
"dependents",
|
|
16780
|
+
"redeploy",
|
|
16781
|
+
"affect",
|
|
16782
|
+
"affects",
|
|
16783
|
+
"affected",
|
|
16784
|
+
"diverge",
|
|
16785
|
+
"divergence",
|
|
16786
|
+
"divergences",
|
|
16787
|
+
"weird",
|
|
16788
|
+
"mismatch",
|
|
16789
|
+
"drift",
|
|
16790
|
+
"inconsistent",
|
|
16791
|
+
"disagree",
|
|
16792
|
+
"declared",
|
|
16793
|
+
"observed",
|
|
16794
|
+
"incident",
|
|
16795
|
+
"incidents",
|
|
16796
|
+
"recent",
|
|
16797
|
+
"errors",
|
|
16798
|
+
"error",
|
|
16799
|
+
"failures",
|
|
16800
|
+
"failure",
|
|
16801
|
+
"history",
|
|
16802
|
+
"runtime",
|
|
16803
|
+
"production",
|
|
16804
|
+
"prod",
|
|
16805
|
+
"traffic",
|
|
16806
|
+
"actually",
|
|
16807
|
+
"really",
|
|
16808
|
+
"depend",
|
|
16809
|
+
"depends",
|
|
16810
|
+
"dependency",
|
|
16811
|
+
"dependencies",
|
|
16812
|
+
"call",
|
|
16813
|
+
"calls",
|
|
16814
|
+
"use",
|
|
16815
|
+
"uses",
|
|
16816
|
+
"using",
|
|
16817
|
+
"import",
|
|
16818
|
+
"imports",
|
|
16819
|
+
"relies",
|
|
16820
|
+
"rely",
|
|
16821
|
+
"needs",
|
|
16822
|
+
"need",
|
|
16823
|
+
"sync"
|
|
16824
|
+
]);
|
|
16825
|
+
function tokens(input) {
|
|
16826
|
+
const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
16827
|
+
return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
|
|
16828
|
+
}
|
|
16829
|
+
function queryTokens(question) {
|
|
16830
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16831
|
+
const out = [];
|
|
16832
|
+
for (const t of tokens(question)) {
|
|
16833
|
+
if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
|
|
16834
|
+
if (seen.has(t)) continue;
|
|
16835
|
+
seen.add(t);
|
|
16836
|
+
out.push(t);
|
|
16837
|
+
}
|
|
16838
|
+
return out;
|
|
16839
|
+
}
|
|
16840
|
+
function nodeName(node) {
|
|
16841
|
+
return node.name ?? node.id;
|
|
16842
|
+
}
|
|
16843
|
+
function idBody(id) {
|
|
16844
|
+
const colon = id.indexOf(":");
|
|
16845
|
+
return colon >= 0 ? id.slice(colon + 1) : id;
|
|
16846
|
+
}
|
|
16847
|
+
var EMBED_MIN_SCORE = 0.35;
|
|
16848
|
+
async function resolveEntities(graph, question, searchIndex, maxNodes) {
|
|
16849
|
+
const qTokens = queryTokens(question);
|
|
16850
|
+
const normalized = question.toLowerCase();
|
|
16851
|
+
const best = /* @__PURE__ */ new Map();
|
|
16852
|
+
const consider = (cand) => {
|
|
16853
|
+
const cur = best.get(cand.nodeId);
|
|
16854
|
+
if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
|
|
16855
|
+
};
|
|
16856
|
+
graph.forEachNode((id, attrs) => {
|
|
16857
|
+
const node = attrs;
|
|
16858
|
+
if (node.type === import_types59.NodeType.FrontierNode) return;
|
|
16859
|
+
const name = nodeName(node);
|
|
16860
|
+
const body = idBody(id);
|
|
16861
|
+
const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
|
|
16862
|
+
if (labelTokens.size === 0) return;
|
|
16863
|
+
let matched = 0;
|
|
16864
|
+
for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
|
|
16865
|
+
const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
|
|
16866
|
+
if (matched === 0 && !named) return;
|
|
16867
|
+
const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
|
|
16868
|
+
const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
|
|
16869
|
+
const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
|
|
16870
|
+
consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
|
|
16871
|
+
});
|
|
16872
|
+
if (searchIndex) {
|
|
16873
|
+
try {
|
|
16874
|
+
const res = await searchIndex.search(question, 10);
|
|
16875
|
+
if (res.provider !== "substring") {
|
|
16876
|
+
for (const m of res.matches) {
|
|
16877
|
+
if (m.node.type === import_types59.NodeType.FrontierNode) continue;
|
|
16878
|
+
const already = best.get(m.node.id);
|
|
16879
|
+
if (!already && m.score < EMBED_MIN_SCORE) continue;
|
|
16880
|
+
consider({
|
|
16881
|
+
nodeId: m.node.id,
|
|
16882
|
+
label: nodeName(m.node),
|
|
16883
|
+
via: "embedding",
|
|
16884
|
+
score: Math.max(0, Math.min(1, m.score))
|
|
16885
|
+
});
|
|
16886
|
+
}
|
|
16887
|
+
}
|
|
16888
|
+
} catch {
|
|
16889
|
+
}
|
|
16890
|
+
}
|
|
16891
|
+
const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
|
|
16892
|
+
return [...best.values()].sort(
|
|
16893
|
+
(a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
|
|
16894
|
+
).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
|
|
16895
|
+
}
|
|
16896
|
+
function incidentsForNode(nodeId, incidents) {
|
|
16897
|
+
if (!incidents || incidents.length === 0) return [];
|
|
16898
|
+
const svc = nodeId.replace(/^service:/, "");
|
|
16899
|
+
return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
|
|
16900
|
+
}
|
|
16901
|
+
function edgeSignalNote(e) {
|
|
16902
|
+
const bits = [];
|
|
16903
|
+
if (e.signal) {
|
|
16904
|
+
bits.push(`${e.signal.spanCount} spans`);
|
|
16905
|
+
if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
|
|
16906
|
+
if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
|
|
16907
|
+
} else if (e.callCount !== void 0) {
|
|
16908
|
+
bits.push(`${e.callCount} calls`);
|
|
16909
|
+
}
|
|
16910
|
+
return bits.length ? ` (${bits.join(", ")})` : "";
|
|
16911
|
+
}
|
|
16912
|
+
function buildRootCauseSection(graph, node, incidents, now) {
|
|
16913
|
+
const result = getRootCause(graph, node, void 0, incidents, { now });
|
|
16914
|
+
if (!result) return null;
|
|
16915
|
+
const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types59.Provenance.OBSERVED;
|
|
16916
|
+
const facts = [
|
|
16917
|
+
{
|
|
16918
|
+
text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
|
|
16919
|
+
provenance: lastProv,
|
|
16920
|
+
confidence: result.confidence
|
|
16921
|
+
}
|
|
16922
|
+
];
|
|
16923
|
+
const candidates = result.candidates ?? [];
|
|
16924
|
+
for (const c of candidates.slice(0, 3)) {
|
|
16925
|
+
facts.push({
|
|
16926
|
+
text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
|
|
16927
|
+
...c.provenance ? { provenance: c.provenance } : {},
|
|
16928
|
+
confidence: c.confidence
|
|
16929
|
+
});
|
|
16930
|
+
}
|
|
16931
|
+
if (result.traversalPath.length > 1) {
|
|
16932
|
+
facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
|
|
16933
|
+
}
|
|
16934
|
+
if (result.fixRecommendation) {
|
|
16935
|
+
facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
|
|
16936
|
+
}
|
|
16937
|
+
return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
|
|
16938
|
+
}
|
|
16939
|
+
function buildDependenciesSection(graph, node) {
|
|
16940
|
+
const result = getTransitiveDependencies(graph, node, 2);
|
|
16941
|
+
if (result.total === 0) return null;
|
|
16942
|
+
const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16943
|
+
text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
|
|
16944
|
+
// Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
|
|
16945
|
+
// edges, so the tag is on the fact, not asserted for the whole section.
|
|
16946
|
+
provenance: d.provenance
|
|
16947
|
+
}));
|
|
16948
|
+
return { heading: "Dependencies", facts };
|
|
16949
|
+
}
|
|
16950
|
+
function buildObservedSection(graph, node) {
|
|
16951
|
+
const result = getObservedDependencies(graph, node);
|
|
16952
|
+
const facts = [];
|
|
16953
|
+
for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
|
|
16954
|
+
const via = e.source !== node ? ` via ${e.source}` : "";
|
|
16955
|
+
facts.push({
|
|
16956
|
+
text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
|
|
16957
|
+
provenance: e.provenance,
|
|
16958
|
+
confidence: confidenceForEdge(e)
|
|
16959
|
+
});
|
|
16960
|
+
}
|
|
16961
|
+
if (facts.length === 0) {
|
|
16962
|
+
if (result.observed && result.inboundObservedCount > 0) {
|
|
16963
|
+
facts.push({
|
|
16964
|
+
text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
|
|
16965
|
+
provenance: import_types59.Provenance.OBSERVED
|
|
16966
|
+
});
|
|
16967
|
+
} else {
|
|
16968
|
+
return null;
|
|
16969
|
+
}
|
|
16970
|
+
}
|
|
16971
|
+
return { heading: "Runtime dependencies (OBSERVED)", facts };
|
|
16972
|
+
}
|
|
16973
|
+
function buildBlastSection(graph, node) {
|
|
16974
|
+
const result = getBlastRadius(graph, node);
|
|
16975
|
+
if (result.totalAffected === 0) return null;
|
|
16976
|
+
const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
|
|
16977
|
+
text: `${n.nodeId} (distance ${n.distance})`,
|
|
16978
|
+
provenance: n.edgeProvenance,
|
|
16979
|
+
confidence: n.confidence
|
|
16980
|
+
}));
|
|
16981
|
+
return {
|
|
16982
|
+
heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
|
|
16983
|
+
facts
|
|
16984
|
+
};
|
|
16985
|
+
}
|
|
16986
|
+
function buildIncidentsSection(node, incidents) {
|
|
16987
|
+
const relevant = incidentsForNode(node, incidents);
|
|
16988
|
+
if (relevant.length === 0) return null;
|
|
16989
|
+
const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
|
|
16990
|
+
const facts = ordered.map((ev) => ({
|
|
16991
|
+
text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
|
|
16992
|
+
// ErrorEvents are observation records — OBSERVED by definition.
|
|
16993
|
+
provenance: import_types59.Provenance.OBSERVED
|
|
16994
|
+
}));
|
|
16995
|
+
return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
|
|
16996
|
+
}
|
|
16997
|
+
function divergenceLine(d) {
|
|
16998
|
+
if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
|
|
16999
|
+
return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
|
|
17000
|
+
}
|
|
17001
|
+
return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
|
|
17002
|
+
}
|
|
17003
|
+
function buildDivergenceSection(graph, node) {
|
|
17004
|
+
const result = computeDivergences(graph, { node });
|
|
17005
|
+
if (result.totalAffected === 0) return null;
|
|
17006
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
17007
|
+
text: divergenceLine(d),
|
|
17008
|
+
confidence: d.confidence
|
|
17009
|
+
// A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
|
|
17010
|
+
// carries no single provenance — the fact leaves it unset.
|
|
17011
|
+
}));
|
|
17012
|
+
return {
|
|
17013
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
17014
|
+
facts
|
|
17015
|
+
};
|
|
17016
|
+
}
|
|
17017
|
+
function buildGlobalDivergenceSection(graph) {
|
|
17018
|
+
const result = computeDivergences(graph);
|
|
17019
|
+
if (result.totalAffected === 0) {
|
|
17020
|
+
return {
|
|
17021
|
+
heading: "Divergences (EXTRACTED vs OBSERVED)",
|
|
17022
|
+
facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
|
|
17023
|
+
};
|
|
17024
|
+
}
|
|
17025
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
17026
|
+
text: divergenceLine(d),
|
|
17027
|
+
confidence: d.confidence
|
|
17028
|
+
// Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
|
|
17029
|
+
}));
|
|
17030
|
+
return {
|
|
17031
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
17032
|
+
facts
|
|
17033
|
+
};
|
|
17034
|
+
}
|
|
17035
|
+
function buildGlobalIncidentsSection(incidents) {
|
|
17036
|
+
if (!incidents || incidents.length === 0) {
|
|
17037
|
+
return {
|
|
17038
|
+
heading: "Incidents across the system",
|
|
17039
|
+
facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
|
|
17040
|
+
};
|
|
17041
|
+
}
|
|
17042
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
17043
|
+
for (const ev of incidents) {
|
|
17044
|
+
const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
|
|
17045
|
+
const cur = byKey.get(key);
|
|
17046
|
+
if (!cur) {
|
|
17047
|
+
byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
|
|
17048
|
+
} else {
|
|
17049
|
+
cur.count += 1;
|
|
17050
|
+
if (ev.timestamp > cur.latest) {
|
|
17051
|
+
cur.latest = ev.timestamp;
|
|
17052
|
+
cur.sampleMsg = ev.errorMessage;
|
|
17053
|
+
}
|
|
17054
|
+
}
|
|
17055
|
+
}
|
|
17056
|
+
const rows = [...byKey.values()].sort((a, b) => b.count - a.count || b.latest.localeCompare(a.latest) || a.key.localeCompare(b.key)).slice(0, MAX_FACTS_PER_SECTION);
|
|
17057
|
+
const facts = rows.map((r) => ({
|
|
17058
|
+
text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
|
|
17059
|
+
provenance: import_types59.Provenance.OBSERVED
|
|
17060
|
+
}));
|
|
17061
|
+
return {
|
|
17062
|
+
heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
|
|
17063
|
+
facts
|
|
17064
|
+
};
|
|
17065
|
+
}
|
|
17066
|
+
function buildOverviewSections(graph, incidents) {
|
|
17067
|
+
const nodeByType = /* @__PURE__ */ new Map();
|
|
17068
|
+
const services = [];
|
|
17069
|
+
graph.forEachNode((_id, attrs) => {
|
|
17070
|
+
const node = attrs;
|
|
17071
|
+
nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
|
|
17072
|
+
if (node.type === import_types59.NodeType.ServiceNode) services.push(node.id);
|
|
17073
|
+
});
|
|
17074
|
+
const edgeByProv = /* @__PURE__ */ new Map();
|
|
17075
|
+
graph.forEachEdge((_id, attrs) => {
|
|
17076
|
+
const p = attrs.provenance;
|
|
17077
|
+
edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
|
|
17078
|
+
});
|
|
17079
|
+
const sections = [];
|
|
17080
|
+
const count = (t) => nodeByType.get(t) ?? 0;
|
|
17081
|
+
const shapeFacts = [
|
|
17082
|
+
{ text: `${graph.order} nodes, ${graph.size} edges` },
|
|
17083
|
+
{
|
|
17084
|
+
text: `${count(import_types59.NodeType.ServiceNode)} services, ${count(import_types59.NodeType.FileNode)} files, ${count(import_types59.NodeType.SymbolNode)} symbols, ${count(import_types59.NodeType.DatabaseNode)} databases`
|
|
17085
|
+
}
|
|
17086
|
+
];
|
|
17087
|
+
for (const p of [import_types59.Provenance.EXTRACTED, import_types59.Provenance.OBSERVED, import_types59.Provenance.INFERRED, import_types59.Provenance.STALE]) {
|
|
17088
|
+
const n = edgeByProv.get(p) ?? 0;
|
|
17089
|
+
if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
|
|
17090
|
+
}
|
|
17091
|
+
sections.push({ heading: "System shape", facts: shapeFacts });
|
|
17092
|
+
const depCounts = services.map((s) => ({ s, n: getTransitiveDependencies(graph, s, 1).total })).filter((r) => r.n > 0).sort((a, b) => b.n - a.n || a.s.localeCompare(b.s)).slice(0, MAX_FACTS_PER_SECTION);
|
|
17093
|
+
if (depCounts.length > 0) {
|
|
17094
|
+
sections.push({
|
|
17095
|
+
heading: "Busiest services (by direct dependencies)",
|
|
17096
|
+
facts: depCounts.map((r) => ({
|
|
17097
|
+
text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
|
|
17098
|
+
}))
|
|
17099
|
+
});
|
|
17100
|
+
}
|
|
17101
|
+
if (incidents && incidents.length > 0) {
|
|
17102
|
+
const incCount = /* @__PURE__ */ new Map();
|
|
17103
|
+
for (const ev of incidents) {
|
|
17104
|
+
const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
|
|
17105
|
+
incCount.set(key, (incCount.get(key) ?? 0) + 1);
|
|
17106
|
+
}
|
|
17107
|
+
const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
|
|
17108
|
+
sections.push({
|
|
17109
|
+
heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
|
|
17110
|
+
facts: top.map(([k, n]) => ({
|
|
17111
|
+
text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
|
|
17112
|
+
provenance: import_types59.Provenance.OBSERVED
|
|
17113
|
+
}))
|
|
17114
|
+
});
|
|
17115
|
+
}
|
|
17116
|
+
const div = computeDivergences(graph);
|
|
17117
|
+
sections.push({
|
|
17118
|
+
heading: "Divergences",
|
|
17119
|
+
facts: [
|
|
17120
|
+
{
|
|
17121
|
+
text: div.totalAffected === 0 ? "None \u2014 declared code and observed runtime agree across the graph." : `${div.totalAffected} divergence${div.totalAffected === 1 ? "" : "s"} between declared code and observed runtime \u2014 ask "are there any divergences?" for the list.`
|
|
17122
|
+
}
|
|
17123
|
+
]
|
|
17124
|
+
});
|
|
17125
|
+
return sections;
|
|
17126
|
+
}
|
|
17127
|
+
function buildGlobalSections(intent, graph, incidents) {
|
|
17128
|
+
switch (intent) {
|
|
17129
|
+
case "divergence":
|
|
17130
|
+
return [buildGlobalDivergenceSection(graph)];
|
|
17131
|
+
case "incidents":
|
|
17132
|
+
return [buildGlobalIncidentsSection(incidents)];
|
|
17133
|
+
case "overview":
|
|
17134
|
+
return buildOverviewSections(graph, incidents);
|
|
17135
|
+
default:
|
|
17136
|
+
return null;
|
|
17137
|
+
}
|
|
17138
|
+
}
|
|
17139
|
+
var SECTION_ORDER = {
|
|
17140
|
+
"root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
|
|
17141
|
+
"blast-radius": ["blast", "dependencies", "observed", "incidents"],
|
|
17142
|
+
dependencies: ["dependencies", "observed", "blast"],
|
|
17143
|
+
observed: ["observed", "dependencies", "incidents"],
|
|
17144
|
+
incidents: ["incidents", "root-cause", "observed"],
|
|
17145
|
+
divergence: ["divergence", "observed", "dependencies", "incidents"],
|
|
17146
|
+
overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
|
|
17147
|
+
};
|
|
17148
|
+
function buildSection(kind, graph, node, incidents, now) {
|
|
17149
|
+
switch (kind) {
|
|
17150
|
+
case "root-cause":
|
|
17151
|
+
return buildRootCauseSection(graph, node, incidents, now);
|
|
17152
|
+
case "dependencies":
|
|
17153
|
+
return buildDependenciesSection(graph, node);
|
|
17154
|
+
case "observed":
|
|
17155
|
+
return buildObservedSection(graph, node);
|
|
17156
|
+
case "blast":
|
|
17157
|
+
return buildBlastSection(graph, node);
|
|
17158
|
+
case "incidents":
|
|
17159
|
+
return buildIncidentsSection(node, incidents);
|
|
17160
|
+
case "divergence":
|
|
17161
|
+
return buildDivergenceSection(graph, node);
|
|
17162
|
+
}
|
|
17163
|
+
}
|
|
17164
|
+
function summarizeGlobal(intent, sections) {
|
|
17165
|
+
const lead = sections[0];
|
|
17166
|
+
switch (intent) {
|
|
17167
|
+
case "divergence": {
|
|
17168
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
17169
|
+
return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
|
|
17170
|
+
}
|
|
17171
|
+
case "incidents": {
|
|
17172
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
17173
|
+
return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
|
|
17174
|
+
}
|
|
17175
|
+
case "overview":
|
|
17176
|
+
default:
|
|
17177
|
+
return `System overview across the whole graph: ${sections.length} view${sections.length === 1 ? "" : "s"} below \u2014 shape (nodes/edges by provenance), busiest services, incidents, and divergences.`;
|
|
17178
|
+
}
|
|
17179
|
+
}
|
|
17180
|
+
function summarize(question, intent, matched, primary, sections, scope) {
|
|
17181
|
+
if (scope === "global") {
|
|
17182
|
+
return summarizeGlobal(intent, sections);
|
|
17183
|
+
}
|
|
17184
|
+
if (!primary) {
|
|
17185
|
+
return `Nothing in "${question}" resolved to a node in the graph. Name a service, file, route, or table \u2014 e.g. \`ask "what does <service> depend on?"\`. For a graph-wide look, ask for an overview, divergences, or incidents.`;
|
|
17186
|
+
}
|
|
17187
|
+
const lead = sections[0];
|
|
17188
|
+
const others = matched.slice(1);
|
|
17189
|
+
const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
|
|
17190
|
+
let core;
|
|
17191
|
+
switch (intent) {
|
|
17192
|
+
case "root-cause": {
|
|
17193
|
+
const rc = sections.find((s) => s.heading === "Root cause (navigation)");
|
|
17194
|
+
if (rc) {
|
|
17195
|
+
core = rc.facts[0]?.text ?? "";
|
|
17196
|
+
} else if (lead) {
|
|
17197
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
|
|
17198
|
+
} else {
|
|
17199
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
|
|
17200
|
+
}
|
|
17201
|
+
break;
|
|
17202
|
+
}
|
|
17203
|
+
case "blast-radius":
|
|
17204
|
+
core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
|
|
17205
|
+
break;
|
|
17206
|
+
case "dependencies":
|
|
17207
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
|
|
17208
|
+
break;
|
|
17209
|
+
case "observed":
|
|
17210
|
+
core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
|
|
17211
|
+
break;
|
|
17212
|
+
case "incidents":
|
|
17213
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
|
|
17214
|
+
break;
|
|
17215
|
+
case "divergence":
|
|
17216
|
+
core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
|
|
17217
|
+
break;
|
|
17218
|
+
default:
|
|
17219
|
+
core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
|
|
17220
|
+
}
|
|
17221
|
+
return `${core}${alsoNote}`;
|
|
17222
|
+
}
|
|
17223
|
+
async function askGraph(graph, question, opts = {}) {
|
|
17224
|
+
const now = opts.now ?? Date.now();
|
|
17225
|
+
const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
|
|
17226
|
+
const intent = classifyIntent(question);
|
|
17227
|
+
const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
|
|
17228
|
+
const primary = matched[0]?.nodeId;
|
|
17229
|
+
const sections = [];
|
|
17230
|
+
let scope;
|
|
17231
|
+
if (primary) {
|
|
17232
|
+
scope = "node";
|
|
17233
|
+
for (const kind of SECTION_ORDER[intent]) {
|
|
17234
|
+
const s = buildSection(kind, graph, primary, opts.incidents, now);
|
|
17235
|
+
if (s && s.facts.length > 0) sections.push(s);
|
|
17236
|
+
}
|
|
17237
|
+
} else {
|
|
17238
|
+
const global = buildGlobalSections(intent, graph, opts.incidents);
|
|
17239
|
+
if (global) {
|
|
17240
|
+
scope = "global";
|
|
17241
|
+
for (const s of global) if (s.facts.length > 0) sections.push(s);
|
|
17242
|
+
}
|
|
17243
|
+
}
|
|
17244
|
+
const answer = summarize(question, intent, matched, primary, sections, scope);
|
|
17245
|
+
const provSet = /* @__PURE__ */ new Set();
|
|
17246
|
+
for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
|
|
17247
|
+
const confidence = sections[0]?.facts[0]?.confidence;
|
|
17248
|
+
return import_types59.AskResultSchema.parse({
|
|
17249
|
+
question,
|
|
17250
|
+
intent,
|
|
17251
|
+
matched,
|
|
17252
|
+
...primary ? { primaryNode: primary } : {},
|
|
17253
|
+
...scope ? { scope } : {},
|
|
17254
|
+
sections,
|
|
17255
|
+
answer,
|
|
17256
|
+
...confidence !== void 0 ? { confidence } : {},
|
|
17257
|
+
provenance: [...provSet]
|
|
17258
|
+
});
|
|
17259
|
+
}
|
|
17260
|
+
|
|
16577
17261
|
// src/diff.ts
|
|
16578
17262
|
init_cjs_shims();
|
|
16579
17263
|
var import_node_fs36 = require("fs");
|
|
@@ -16656,7 +17340,7 @@ init_cjs_shims();
|
|
|
16656
17340
|
var import_node_fs37 = require("fs");
|
|
16657
17341
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
16658
17342
|
var import_node_path72 = __toESM(require("path"), 1);
|
|
16659
|
-
var
|
|
17343
|
+
var import_types60 = require("@neat.is/types");
|
|
16660
17344
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
16661
17345
|
var LOCK_RETRY_MS = 50;
|
|
16662
17346
|
function neatHome() {
|
|
@@ -16858,10 +17542,10 @@ async function readRegistry() {
|
|
|
16858
17542
|
throw err;
|
|
16859
17543
|
}
|
|
16860
17544
|
const parsed = JSON.parse(raw);
|
|
16861
|
-
return
|
|
17545
|
+
return import_types60.RegistryFileSchema.parse(parsed);
|
|
16862
17546
|
}
|
|
16863
17547
|
async function writeRegistry(reg) {
|
|
16864
|
-
const validated =
|
|
17548
|
+
const validated = import_types60.RegistryFileSchema.parse(reg);
|
|
16865
17549
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
16866
17550
|
}
|
|
16867
17551
|
async function getProject(name) {
|
|
@@ -17212,15 +17896,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
17212
17896
|
|
|
17213
17897
|
// src/connectors/index.ts
|
|
17214
17898
|
init_cjs_shims();
|
|
17215
|
-
var
|
|
17899
|
+
var import_types61 = require("@neat.is/types");
|
|
17216
17900
|
var NO_ENV = "unknown";
|
|
17217
17901
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
17218
17902
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
17219
17903
|
const sites = [];
|
|
17220
17904
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
17221
17905
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
17222
|
-
if (edge.provenance !==
|
|
17223
|
-
const parsed = (0,
|
|
17906
|
+
if (edge.provenance !== import_types61.Provenance.EXTRACTED) continue;
|
|
17907
|
+
const parsed = (0, import_types61.parseFileId)(edge.source);
|
|
17224
17908
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
17225
17909
|
const site = { relPath: edge.evidence.file };
|
|
17226
17910
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -17231,7 +17915,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
17231
17915
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
17232
17916
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
17233
17917
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
17234
|
-
if (attrs.type !==
|
|
17918
|
+
if (attrs.type !== import_types61.NodeType.RouteNode || !attrs.path) return void 0;
|
|
17235
17919
|
const site = { relPath: attrs.path };
|
|
17236
17920
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
17237
17921
|
return site;
|
|
@@ -17843,23 +18527,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
17843
18527
|
|
|
17844
18528
|
// src/connectors/supabase/resolve.ts
|
|
17845
18529
|
init_cjs_shims();
|
|
17846
|
-
var
|
|
18530
|
+
var import_types63 = require("@neat.is/types");
|
|
17847
18531
|
function createSupabaseResolveTarget(graph, config) {
|
|
17848
18532
|
return (signal, _ctx) => {
|
|
17849
18533
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
17850
18534
|
return null;
|
|
17851
18535
|
}
|
|
17852
|
-
const subResourceId = (0,
|
|
18536
|
+
const subResourceId = (0, import_types63.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
17853
18537
|
if (graph.hasNode(subResourceId)) {
|
|
17854
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
18538
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17855
18539
|
}
|
|
17856
|
-
const bareResourceId = (0,
|
|
18540
|
+
const bareResourceId = (0, import_types63.infraId)(signal.targetKind, signal.targetName);
|
|
17857
18541
|
if (graph.hasNode(bareResourceId)) {
|
|
17858
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
18542
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17859
18543
|
}
|
|
17860
|
-
const projectLevelId = (0,
|
|
18544
|
+
const projectLevelId = (0, import_types63.infraId)("supabase", config.nodeRef);
|
|
17861
18545
|
if (graph.hasNode(projectLevelId)) {
|
|
17862
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
18546
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17863
18547
|
}
|
|
17864
18548
|
return null;
|
|
17865
18549
|
};
|
|
@@ -17952,7 +18636,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
17952
18636
|
|
|
17953
18637
|
// src/connectors/railway/index.ts
|
|
17954
18638
|
init_cjs_shims();
|
|
17955
|
-
var
|
|
18639
|
+
var import_types67 = require("@neat.is/types");
|
|
17956
18640
|
|
|
17957
18641
|
// src/connectors/railway/client.ts
|
|
17958
18642
|
init_cjs_shims();
|
|
@@ -18103,7 +18787,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
18103
18787
|
const out = [];
|
|
18104
18788
|
graph.forEachNode((_id, attrs) => {
|
|
18105
18789
|
const node = attrs;
|
|
18106
|
-
if (node.type !==
|
|
18790
|
+
if (node.type !== import_types67.NodeType.RouteNode) return;
|
|
18107
18791
|
const route = attrs;
|
|
18108
18792
|
if (route.service !== serviceName) return;
|
|
18109
18793
|
out.push({
|
|
@@ -18207,12 +18891,12 @@ function createRailwayResolveTarget(config) {
|
|
|
18207
18891
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
18208
18892
|
if (!serviceName) return null;
|
|
18209
18893
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
18210
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
18894
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types67.EdgeType.CALLS };
|
|
18211
18895
|
}
|
|
18212
18896
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
18213
18897
|
const peerName = config.serviceNameById[signal.targetName];
|
|
18214
18898
|
if (!peerName) return null;
|
|
18215
|
-
return { targetNodeId: (0,
|
|
18899
|
+
return { targetNodeId: (0, import_types67.serviceId)(peerName), serviceName, edgeType: import_types67.EdgeType.CONNECTS_TO };
|
|
18216
18900
|
}
|
|
18217
18901
|
return null;
|
|
18218
18902
|
};
|
|
@@ -18400,7 +19084,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
18400
19084
|
|
|
18401
19085
|
// src/connectors/firebase/resolve.ts
|
|
18402
19086
|
init_cjs_shims();
|
|
18403
|
-
var
|
|
19087
|
+
var import_types68 = require("@neat.is/types");
|
|
18404
19088
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
18405
19089
|
switch (resourceType) {
|
|
18406
19090
|
case "cloud_function":
|
|
@@ -18415,7 +19099,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
18415
19099
|
const entries = [];
|
|
18416
19100
|
graph.forEachNode((_id, attrs) => {
|
|
18417
19101
|
const node = attrs;
|
|
18418
|
-
if (node.type !==
|
|
19102
|
+
if (node.type !== import_types68.NodeType.RouteNode) return;
|
|
18419
19103
|
const route = attrs;
|
|
18420
19104
|
if (route.service !== serviceName) return;
|
|
18421
19105
|
entries.push({
|
|
@@ -18447,7 +19131,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
18447
19131
|
return {
|
|
18448
19132
|
targetNodeId: match.routeNodeId,
|
|
18449
19133
|
serviceName,
|
|
18450
|
-
edgeType:
|
|
19134
|
+
edgeType: import_types68.EdgeType.CALLS
|
|
18451
19135
|
};
|
|
18452
19136
|
};
|
|
18453
19137
|
}
|
|
@@ -18474,7 +19158,7 @@ init_cjs_shims();
|
|
|
18474
19158
|
|
|
18475
19159
|
// src/connectors/cloudflare/connector.ts
|
|
18476
19160
|
init_cjs_shims();
|
|
18477
|
-
var
|
|
19161
|
+
var import_types70 = require("@neat.is/types");
|
|
18478
19162
|
|
|
18479
19163
|
// src/connectors/cloudflare/client.ts
|
|
18480
19164
|
init_cjs_shims();
|
|
@@ -18638,7 +19322,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
18638
19322
|
graph.forEachNode((id, attrs) => {
|
|
18639
19323
|
if (found) return;
|
|
18640
19324
|
const a = attrs;
|
|
18641
|
-
if (a.type ===
|
|
19325
|
+
if (a.type === import_types70.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
18642
19326
|
found = id;
|
|
18643
19327
|
}
|
|
18644
19328
|
});
|
|
@@ -18650,7 +19334,7 @@ function findMatchingRouteNode(graph, serviceName, method, path78) {
|
|
|
18650
19334
|
graph.forEachNode((id, attrs) => {
|
|
18651
19335
|
if (found) return;
|
|
18652
19336
|
const a = attrs;
|
|
18653
|
-
if (a.type !==
|
|
19337
|
+
if (a.type !== import_types70.NodeType.RouteNode || a.service !== serviceName) return;
|
|
18654
19338
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
18655
19339
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
18656
19340
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -18669,11 +19353,11 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
18669
19353
|
};
|
|
18670
19354
|
const mapping = config.workers?.[scriptName];
|
|
18671
19355
|
if (mapping) {
|
|
18672
|
-
const wholeFileId = (0,
|
|
19356
|
+
const wholeFileId = (0, import_types70.fileId)(mapping.service, mapping.entryFile);
|
|
18673
19357
|
return {
|
|
18674
19358
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
18675
19359
|
serviceName: mapping.service,
|
|
18676
|
-
edgeType:
|
|
19360
|
+
edgeType: import_types70.EdgeType.CALLS
|
|
18677
19361
|
};
|
|
18678
19362
|
}
|
|
18679
19363
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -18682,13 +19366,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
18682
19366
|
return {
|
|
18683
19367
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
18684
19368
|
serviceName: fileNode.service,
|
|
18685
|
-
edgeType:
|
|
19369
|
+
edgeType: import_types70.EdgeType.CALLS
|
|
18686
19370
|
};
|
|
18687
19371
|
}
|
|
18688
19372
|
return {
|
|
18689
|
-
targetNodeId: (0,
|
|
19373
|
+
targetNodeId: (0, import_types70.infraId)("cloudflare-worker", scriptName),
|
|
18690
19374
|
serviceName: scriptName,
|
|
18691
|
-
edgeType:
|
|
19375
|
+
edgeType: import_types70.EdgeType.CALLS,
|
|
18692
19376
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
18693
19377
|
};
|
|
18694
19378
|
};
|
|
@@ -18884,14 +19568,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
18884
19568
|
|
|
18885
19569
|
// src/connectors/neon/resolve.ts
|
|
18886
19570
|
init_cjs_shims();
|
|
18887
|
-
var
|
|
19571
|
+
var import_types74 = require("@neat.is/types");
|
|
18888
19572
|
function createNeonResolveTarget(config) {
|
|
18889
19573
|
return (signal) => {
|
|
18890
19574
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
18891
19575
|
return {
|
|
18892
|
-
targetNodeId: (0,
|
|
19576
|
+
targetNodeId: (0, import_types74.infraId)("sql-table", signal.targetName),
|
|
18893
19577
|
serviceName: config.serviceName,
|
|
18894
|
-
edgeType:
|
|
19578
|
+
edgeType: import_types74.EdgeType.CALLS,
|
|
18895
19579
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
18896
19580
|
};
|
|
18897
19581
|
};
|
|
@@ -19072,14 +19756,14 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
19072
19756
|
|
|
19073
19757
|
// src/connectors/cloud-run/resolve.ts
|
|
19074
19758
|
init_cjs_shims();
|
|
19075
|
-
var
|
|
19759
|
+
var import_types78 = require("@neat.is/types");
|
|
19076
19760
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
19077
19761
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
19078
19762
|
let found = null;
|
|
19079
19763
|
graph.forEachNode((_id, attrs) => {
|
|
19080
19764
|
if (found) return;
|
|
19081
19765
|
const node = attrs;
|
|
19082
|
-
if (node.type !==
|
|
19766
|
+
if (node.type !== import_types78.NodeType.RouteNode) return;
|
|
19083
19767
|
const route = attrs;
|
|
19084
19768
|
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
19085
19769
|
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
@@ -19104,13 +19788,13 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
19104
19788
|
normalizePathTemplate(path78)
|
|
19105
19789
|
);
|
|
19106
19790
|
if (routeNodeId) {
|
|
19107
|
-
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType:
|
|
19791
|
+
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types78.EdgeType.CALLS };
|
|
19108
19792
|
}
|
|
19109
19793
|
}
|
|
19110
19794
|
return {
|
|
19111
|
-
targetNodeId: (0,
|
|
19795
|
+
targetNodeId: (0, import_types78.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
19112
19796
|
serviceName: mappedService ?? gcpServiceName,
|
|
19113
|
-
edgeType:
|
|
19797
|
+
edgeType: import_types78.EdgeType.CALLS,
|
|
19114
19798
|
ensureInfraNode: {
|
|
19115
19799
|
kind: CLOUD_RUN_SERVICE_INFRA_KIND,
|
|
19116
19800
|
name: gcpServiceName,
|
|
@@ -19151,7 +19835,7 @@ function createCloudRunConnector(graph, config = {}) {
|
|
|
19151
19835
|
|
|
19152
19836
|
// src/connectors/render/index.ts
|
|
19153
19837
|
init_cjs_shims();
|
|
19154
|
-
var
|
|
19838
|
+
var import_types81 = require("@neat.is/types");
|
|
19155
19839
|
|
|
19156
19840
|
// src/connectors/render/types.ts
|
|
19157
19841
|
init_cjs_shims();
|
|
@@ -19229,7 +19913,7 @@ function buildRenderRouteIndex(graph, serviceName) {
|
|
|
19229
19913
|
const out = [];
|
|
19230
19914
|
graph.forEachNode((_id, attrs) => {
|
|
19231
19915
|
const node = attrs;
|
|
19232
|
-
if (node.type !==
|
|
19916
|
+
if (node.type !== import_types81.NodeType.RouteNode) return;
|
|
19233
19917
|
const route = attrs;
|
|
19234
19918
|
if (route.service !== serviceName) return;
|
|
19235
19919
|
out.push({
|
|
@@ -19314,7 +19998,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
|
|
|
19314
19998
|
function createRenderResolveTarget(config) {
|
|
19315
19999
|
return (signal) => {
|
|
19316
20000
|
if (signal.targetKind === ROUTE_TARGET_KIND2) {
|
|
19317
|
-
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType:
|
|
20001
|
+
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types81.EdgeType.CALLS };
|
|
19318
20002
|
}
|
|
19319
20003
|
return null;
|
|
19320
20004
|
};
|
|
@@ -19452,21 +20136,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
|
|
|
19452
20136
|
|
|
19453
20137
|
// src/connectors/planetscale/resolve.ts
|
|
19454
20138
|
init_cjs_shims();
|
|
19455
|
-
var
|
|
20139
|
+
var import_types85 = require("@neat.is/types");
|
|
19456
20140
|
var PLANETSCALE_DATABASE_KIND = "planetscale-database";
|
|
19457
20141
|
function createPlanetscaleResolveTarget(graph, config) {
|
|
19458
20142
|
const databaseName = `${config.organization}/${config.database}`;
|
|
19459
20143
|
return (signal, _ctx) => {
|
|
19460
20144
|
if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
19461
|
-
const tableId = (0,
|
|
20145
|
+
const tableId = (0, import_types85.infraId)("sql-table", signal.targetName);
|
|
19462
20146
|
if (graph.hasNode(tableId)) {
|
|
19463
|
-
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType:
|
|
20147
|
+
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types85.EdgeType.CALLS };
|
|
19464
20148
|
}
|
|
19465
|
-
const providerId = (0,
|
|
20149
|
+
const providerId = (0, import_types85.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
|
|
19466
20150
|
return {
|
|
19467
20151
|
targetNodeId: providerId,
|
|
19468
20152
|
serviceName: config.serviceName,
|
|
19469
|
-
edgeType:
|
|
20153
|
+
edgeType: import_types85.EdgeType.CALLS,
|
|
19470
20154
|
ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
|
|
19471
20155
|
};
|
|
19472
20156
|
};
|
|
@@ -19731,7 +20415,7 @@ function mapBuildsToSignals(builds, serviceName) {
|
|
|
19731
20415
|
|
|
19732
20416
|
// src/connectors/eas/resolve.ts
|
|
19733
20417
|
init_cjs_shims();
|
|
19734
|
-
var
|
|
20418
|
+
var import_types90 = require("@neat.is/types");
|
|
19735
20419
|
var NO_ENV2 = "unknown";
|
|
19736
20420
|
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
19737
20421
|
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
@@ -19747,8 +20431,8 @@ function configBasenamesForPhase(phase) {
|
|
|
19747
20431
|
function configNodeService(graph, configNodeId) {
|
|
19748
20432
|
for (const edgeId of graph.inboundEdges(configNodeId)) {
|
|
19749
20433
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
19750
|
-
if (edge.type !==
|
|
19751
|
-
const parsed = (0,
|
|
20434
|
+
if (edge.type !== import_types90.EdgeType.CONFIGURED_BY) continue;
|
|
20435
|
+
const parsed = (0, import_types90.parseFileId)(edge.source);
|
|
19752
20436
|
if (parsed) return parsed.service;
|
|
19753
20437
|
}
|
|
19754
20438
|
return null;
|
|
@@ -19759,7 +20443,7 @@ function findConfigNode(graph, basenames, serviceName) {
|
|
|
19759
20443
|
graph.forEachNode((id, attrs) => {
|
|
19760
20444
|
if (scoped) return;
|
|
19761
20445
|
const node = attrs;
|
|
19762
|
-
if (node.type !==
|
|
20446
|
+
if (node.type !== import_types90.NodeType.ConfigNode) return;
|
|
19763
20447
|
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
19764
20448
|
if (anyMatch === null) anyMatch = id;
|
|
19765
20449
|
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
@@ -19776,13 +20460,13 @@ function createEasResolveTarget(graph) {
|
|
|
19776
20460
|
if (basenames.length > 0) {
|
|
19777
20461
|
const configNodeId = findConfigNode(graph, basenames, serviceName);
|
|
19778
20462
|
if (configNodeId) {
|
|
19779
|
-
return { targetNodeId: configNodeId, serviceName, edgeType:
|
|
20463
|
+
return { targetNodeId: configNodeId, serviceName, edgeType: import_types90.EdgeType.CALLS };
|
|
19780
20464
|
}
|
|
19781
20465
|
}
|
|
19782
20466
|
return {
|
|
19783
20467
|
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
|
|
19784
20468
|
serviceName,
|
|
19785
|
-
edgeType:
|
|
20469
|
+
edgeType: import_types90.EdgeType.CALLS
|
|
19786
20470
|
};
|
|
19787
20471
|
};
|
|
19788
20472
|
}
|
|
@@ -20507,11 +21191,11 @@ function registerRoutes(scope, ctx) {
|
|
|
20507
21191
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
20508
21192
|
const parsed = [];
|
|
20509
21193
|
for (const c of candidates) {
|
|
20510
|
-
const r =
|
|
21194
|
+
const r = import_types93.DivergenceTypeSchema.safeParse(c);
|
|
20511
21195
|
if (!r.success) {
|
|
20512
21196
|
return reply.code(400).send({
|
|
20513
21197
|
error: `unknown divergence type "${c}"`,
|
|
20514
|
-
allowed:
|
|
21198
|
+
allowed: import_types93.DivergenceTypeSchema.options
|
|
20515
21199
|
});
|
|
20516
21200
|
}
|
|
20517
21201
|
parsed.push(r.data);
|
|
@@ -20753,6 +21437,18 @@ function registerRoutes(scope, ctx) {
|
|
|
20753
21437
|
matches: matches.slice(0, safeLimit)
|
|
20754
21438
|
};
|
|
20755
21439
|
});
|
|
21440
|
+
scope.get("/graph/ask", async (req2, reply) => {
|
|
21441
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
21442
|
+
if (!proj) return;
|
|
21443
|
+
const question = (req2.query.q ?? "").trim();
|
|
21444
|
+
if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
|
|
21445
|
+
const epath = errorsPathFor(proj);
|
|
21446
|
+
const incidents = epath ? await readErrorEvents(epath) : [];
|
|
21447
|
+
return askGraph(proj.graph, question, {
|
|
21448
|
+
...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
|
|
21449
|
+
incidents
|
|
21450
|
+
});
|
|
21451
|
+
});
|
|
20756
21452
|
scope.get(
|
|
20757
21453
|
"/graph/diff",
|
|
20758
21454
|
async (req2, reply) => {
|
|
@@ -20853,7 +21549,7 @@ function registerRoutes(scope, ctx) {
|
|
|
20853
21549
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
20854
21550
|
let violations = await log.readAll();
|
|
20855
21551
|
if (req2.query.severity) {
|
|
20856
|
-
const sev =
|
|
21552
|
+
const sev = import_types93.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
20857
21553
|
if (!sev.success) {
|
|
20858
21554
|
return reply.code(400).send({
|
|
20859
21555
|
error: "invalid severity",
|
|
@@ -20892,7 +21588,7 @@ function registerRoutes(scope, ctx) {
|
|
|
20892
21588
|
scope.post("/policies/check", async (req2, reply) => {
|
|
20893
21589
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
20894
21590
|
if (!proj) return;
|
|
20895
|
-
const parsed =
|
|
21591
|
+
const parsed = import_types93.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
20896
21592
|
if (!parsed.success) {
|
|
20897
21593
|
return reply.code(400).send({
|
|
20898
21594
|
error: "invalid /policies/check body",
|
|
@@ -21233,7 +21929,7 @@ function unroutedErrorsPath(neatHome4) {
|
|
|
21233
21929
|
}
|
|
21234
21930
|
|
|
21235
21931
|
// src/daemon.ts
|
|
21236
|
-
var
|
|
21932
|
+
var import_types94 = require("@neat.is/types");
|
|
21237
21933
|
function daemonJsonPath(scanPath) {
|
|
21238
21934
|
return import_node_path75.default.join(scanPath, "neat-out", "daemon.json");
|
|
21239
21935
|
}
|
|
@@ -21359,8 +22055,8 @@ function isTokenContained(needle, haystack) {
|
|
|
21359
22055
|
const n = needle.toLowerCase();
|
|
21360
22056
|
const h = haystack.toLowerCase();
|
|
21361
22057
|
if (!h.includes(n)) return false;
|
|
21362
|
-
const
|
|
21363
|
-
return
|
|
22058
|
+
const tokens2 = h.split(/[-_]/);
|
|
22059
|
+
return tokens2.includes(n);
|
|
21364
22060
|
}
|
|
21365
22061
|
function serviceNameMatchesProject(serviceName, project) {
|
|
21366
22062
|
if (serviceName.toLowerCase() === project.toLowerCase()) return true;
|
|
@@ -21372,7 +22068,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
21372
22068
|
if (!serviceName) return true;
|
|
21373
22069
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
21374
22070
|
return graph.someNode(
|
|
21375
|
-
(_id, attrs) => attrs.type ===
|
|
22071
|
+
(_id, attrs) => attrs.type === import_types94.NodeType.ServiceNode && attrs.name === serviceName
|
|
21376
22072
|
);
|
|
21377
22073
|
}
|
|
21378
22074
|
async function bootstrapProject(entry2, connectors = [], neatHome4) {
|