@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/index.cjs
CHANGED
|
@@ -1392,7 +1392,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
|
|
|
1392
1392
|
for (const id of walk10.path) {
|
|
1393
1393
|
const owner = resolveOwningService(graph, id);
|
|
1394
1394
|
if (!owner) continue;
|
|
1395
|
-
const { id:
|
|
1395
|
+
const { id: serviceId16, svc } = owner;
|
|
1396
1396
|
const deps = svc.dependencies ?? {};
|
|
1397
1397
|
for (const pair of candidatePairs) {
|
|
1398
1398
|
const declared = deps[pair.driver];
|
|
@@ -1405,7 +1405,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
|
|
|
1405
1405
|
);
|
|
1406
1406
|
if (!result.compatible) {
|
|
1407
1407
|
return {
|
|
1408
|
-
rootCauseNode:
|
|
1408
|
+
rootCauseNode: serviceId16,
|
|
1409
1409
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1410
1410
|
...result.minDriverVersion ? {
|
|
1411
1411
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1420,7 +1420,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
1420
1420
|
for (const id of walk10.path) {
|
|
1421
1421
|
const owner = resolveOwningService(graph, id);
|
|
1422
1422
|
if (!owner) continue;
|
|
1423
|
-
const { id:
|
|
1423
|
+
const { id: serviceId16, svc } = owner;
|
|
1424
1424
|
const deps = svc.dependencies ?? {};
|
|
1425
1425
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1426
1426
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1429,7 +1429,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
1429
1429
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1430
1430
|
if (!result.compatible && result.reason) {
|
|
1431
1431
|
return {
|
|
1432
|
-
rootCauseNode:
|
|
1432
|
+
rootCauseNode: serviceId16,
|
|
1433
1433
|
rootCauseReason: result.reason,
|
|
1434
1434
|
...result.requiredNodeVersion ? {
|
|
1435
1435
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1444,7 +1444,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
1444
1444
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1445
1445
|
if (!result.compatible && result.reason) {
|
|
1446
1446
|
return {
|
|
1447
|
-
rootCauseNode:
|
|
1447
|
+
rootCauseNode: serviceId16,
|
|
1448
1448
|
rootCauseReason: result.reason,
|
|
1449
1449
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1450
1450
|
};
|
|
@@ -1549,9 +1549,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1549
1549
|
function isFailingCallEdge(e) {
|
|
1550
1550
|
return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
1551
1551
|
}
|
|
1552
|
-
function callSourcesForService(graph,
|
|
1553
|
-
const ids = [
|
|
1554
|
-
for (const edgeId of graph.outboundEdges(
|
|
1552
|
+
function callSourcesForService(graph, serviceId16) {
|
|
1553
|
+
const ids = [serviceId16];
|
|
1554
|
+
for (const edgeId of graph.outboundEdges(serviceId16)) {
|
|
1555
1555
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1556
1556
|
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1557
1557
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -1575,9 +1575,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
1575
1575
|
}
|
|
1576
1576
|
return id < curId;
|
|
1577
1577
|
}
|
|
1578
|
-
function dominantFailingCall(graph,
|
|
1578
|
+
function dominantFailingCall(graph, serviceId16, visited) {
|
|
1579
1579
|
let best = null;
|
|
1580
|
-
for (const src of callSourcesForService(graph,
|
|
1580
|
+
for (const src of callSourcesForService(graph, serviceId16)) {
|
|
1581
1581
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1582
1582
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1583
1583
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -1616,9 +1616,9 @@ function staleCallDominates(e, id, curEdge, curId) {
|
|
|
1616
1616
|
if (ev !== cv) return ev > cv;
|
|
1617
1617
|
return id < curId;
|
|
1618
1618
|
}
|
|
1619
|
-
function dominantStaleCall(graph,
|
|
1619
|
+
function dominantStaleCall(graph, serviceId16, visited) {
|
|
1620
1620
|
const bestByCallee = /* @__PURE__ */ new Map();
|
|
1621
|
-
for (const src of callSourcesForService(graph,
|
|
1621
|
+
for (const src of callSourcesForService(graph, serviceId16)) {
|
|
1622
1622
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1623
1623
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1624
1624
|
if (e.type !== import_types.EdgeType.CALLS) continue;
|
|
@@ -4817,11 +4817,11 @@ function actixAttributeRoute(attrItem, out) {
|
|
|
4817
4817
|
if (!nameNode) return;
|
|
4818
4818
|
const macro = nameNode.type === "identifier" ? nameNode.text : nameNode.type === "scoped_identifier" ? nameNode.childForFieldName("name")?.text ?? null : null;
|
|
4819
4819
|
if (!macro) return;
|
|
4820
|
-
const
|
|
4821
|
-
if (!
|
|
4820
|
+
const tokens2 = attr.childForFieldName("arguments");
|
|
4821
|
+
if (!tokens2 || tokens2.type !== "token_tree") return;
|
|
4822
4822
|
const strings = [];
|
|
4823
|
-
for (let i = 0; i <
|
|
4824
|
-
const s = rustStringContent(
|
|
4823
|
+
for (let i = 0; i < tokens2.namedChildCount; i++) {
|
|
4824
|
+
const s = rustStringContent(tokens2.namedChild(i));
|
|
4825
4825
|
if (s !== null) strings.push(s);
|
|
4826
4826
|
}
|
|
4827
4827
|
const pathStr = strings[0];
|
|
@@ -6715,29 +6715,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
6715
6715
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
6716
6716
|
});
|
|
6717
6717
|
let promoted = 0;
|
|
6718
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
6718
|
+
for (const { frontierId: frontierId2, serviceId: serviceId16 } of toPromote) {
|
|
6719
6719
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
6720
6720
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
6721
6721
|
if (!gate.allowed) {
|
|
6722
6722
|
continue;
|
|
6723
6723
|
}
|
|
6724
6724
|
}
|
|
6725
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
6725
|
+
rewireFrontierEdges(graph, frontierId2, serviceId16);
|
|
6726
6726
|
graph.dropNode(frontierId2);
|
|
6727
6727
|
promoted++;
|
|
6728
6728
|
}
|
|
6729
6729
|
return promoted;
|
|
6730
6730
|
}
|
|
6731
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
6731
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId16) {
|
|
6732
6732
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
6733
6733
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
6734
6734
|
for (const edgeId of inbound) {
|
|
6735
6735
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6736
|
-
rebuildEdge(graph, edge, edge.source,
|
|
6736
|
+
rebuildEdge(graph, edge, edge.source, serviceId16, edgeId);
|
|
6737
6737
|
}
|
|
6738
6738
|
for (const edgeId of outbound) {
|
|
6739
6739
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6740
|
-
rebuildEdge(graph, edge,
|
|
6740
|
+
rebuildEdge(graph, edge, serviceId16, edge.target, edgeId);
|
|
6741
6741
|
}
|
|
6742
6742
|
}
|
|
6743
6743
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -7852,9 +7852,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
7852
7852
|
"StatefulSet",
|
|
7853
7853
|
"DaemonSet"
|
|
7854
7854
|
]);
|
|
7855
|
-
function addAliases(graph,
|
|
7856
|
-
if (!graph.hasNode(
|
|
7857
|
-
const node = graph.getNodeAttributes(
|
|
7855
|
+
function addAliases(graph, serviceId16, candidates) {
|
|
7856
|
+
if (!graph.hasNode(serviceId16)) return;
|
|
7857
|
+
const node = graph.getNodeAttributes(serviceId16);
|
|
7858
7858
|
if (node.type !== import_types19.NodeType.ServiceNode) return;
|
|
7859
7859
|
const set = new Set(node.aliases ?? []);
|
|
7860
7860
|
for (const c of candidates) {
|
|
@@ -7864,7 +7864,7 @@ function addAliases(graph, serviceId15, candidates) {
|
|
|
7864
7864
|
}
|
|
7865
7865
|
if (set.size === 0) return;
|
|
7866
7866
|
const updated = { ...node, aliases: [...set].sort() };
|
|
7867
|
-
graph.replaceNodeAttributes(
|
|
7867
|
+
graph.replaceNodeAttributes(serviceId16, updated);
|
|
7868
7868
|
}
|
|
7869
7869
|
function indexServicesByName(services) {
|
|
7870
7870
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -7897,12 +7897,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
7897
7897
|
}
|
|
7898
7898
|
if (!compose?.services) return;
|
|
7899
7899
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
7900
|
-
const
|
|
7901
|
-
if (!
|
|
7900
|
+
const serviceId16 = serviceIndex.get(composeName);
|
|
7901
|
+
if (!serviceId16) continue;
|
|
7902
7902
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
7903
7903
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
7904
7904
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
7905
|
-
addAliases(graph,
|
|
7905
|
+
addAliases(graph, serviceId16, aliases);
|
|
7906
7906
|
}
|
|
7907
7907
|
}
|
|
7908
7908
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -9892,12 +9892,12 @@ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
|
|
|
9892
9892
|
const value = await interpolateEnvRefs(raw, serviceDir);
|
|
9893
9893
|
if (!looksLike(value)) continue;
|
|
9894
9894
|
const parsed = parse11(value);
|
|
9895
|
-
if (parsed) out.push(parsed);
|
|
9895
|
+
if (parsed) out.push({ ...parsed, hostSource: "config" });
|
|
9896
9896
|
}
|
|
9897
9897
|
for (const lit of literals) {
|
|
9898
9898
|
if (!looksLike(lit)) continue;
|
|
9899
9899
|
const parsed = parse11(lit);
|
|
9900
|
-
if (parsed) out.push(parsed);
|
|
9900
|
+
if (parsed) out.push({ ...parsed, hostSource: "literal" });
|
|
9901
9901
|
}
|
|
9902
9902
|
return out;
|
|
9903
9903
|
}
|
|
@@ -10166,7 +10166,10 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
10166
10166
|
type: import_types23.EdgeType.CONNECTS_TO,
|
|
10167
10167
|
provenance: import_types23.Provenance.EXTRACTED,
|
|
10168
10168
|
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
10169
|
-
|
|
10169
|
+
// Carry how the host was recovered (ADR-213) so the divergence ranker
|
|
10170
|
+
// can tell a real declared store from a hardcoded fault-injection /
|
|
10171
|
+
// flag-gated probe. Only set when the parser distinguished the two.
|
|
10172
|
+
evidence: config.hostSource ? { file: evidenceFile, hostSource: config.hostSource } : { file: evidenceFile }
|
|
10170
10173
|
};
|
|
10171
10174
|
if (!graph.hasEdge(edge.id)) {
|
|
10172
10175
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -15839,7 +15842,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
15839
15842
|
init_cjs_shims();
|
|
15840
15843
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
15841
15844
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
15842
|
-
var
|
|
15845
|
+
var import_types93 = require("@neat.is/types");
|
|
15843
15846
|
|
|
15844
15847
|
// src/extend/index.ts
|
|
15845
15848
|
init_cjs_shims();
|
|
@@ -16443,6 +16446,53 @@ function detectColumnDrift(node) {
|
|
|
16443
16446
|
function involvesNode(d, nodeId) {
|
|
16444
16447
|
return d.source === nodeId || d.target === nodeId;
|
|
16445
16448
|
}
|
|
16449
|
+
function datastoreEverObserved(graph, nodeId) {
|
|
16450
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
16451
|
+
const n = graph.getNodeAttributes(nodeId);
|
|
16452
|
+
if (n.type === import_types58.NodeType.DatabaseNode) {
|
|
16453
|
+
const via = n.discoveredVia;
|
|
16454
|
+
if (via === "otel" || via === "merged") return true;
|
|
16455
|
+
}
|
|
16456
|
+
for (const edgeId of graph.inboundEdges(nodeId)) {
|
|
16457
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
16458
|
+
if (e.provenance === import_types58.Provenance.OBSERVED || e.provenance === import_types58.Provenance.STALE) return true;
|
|
16459
|
+
}
|
|
16460
|
+
return false;
|
|
16461
|
+
}
|
|
16462
|
+
function serviceHasObservedSameEngineStore(graph, buckets2, serviceId16, engine, excludeTarget) {
|
|
16463
|
+
for (const bucket of buckets2.values()) {
|
|
16464
|
+
if (bucket.type !== import_types58.EdgeType.CONNECTS_TO) continue;
|
|
16465
|
+
if (bucket.source !== serviceId16) continue;
|
|
16466
|
+
if (bucket.target === excludeTarget) continue;
|
|
16467
|
+
if (!graph.hasNode(bucket.target)) continue;
|
|
16468
|
+
const target = graph.getNodeAttributes(bucket.target);
|
|
16469
|
+
if (target.type !== import_types58.NodeType.DatabaseNode) continue;
|
|
16470
|
+
if (target.engine !== engine) continue;
|
|
16471
|
+
if (bucket.observed || datastoreEverObserved(graph, bucket.target)) return true;
|
|
16472
|
+
}
|
|
16473
|
+
return false;
|
|
16474
|
+
}
|
|
16475
|
+
var DEAD_CODE_PROBE_CONFIDENCE = 0.1;
|
|
16476
|
+
function dampenDeadCodeProbes(graph, buckets2, all) {
|
|
16477
|
+
return all.map((d) => {
|
|
16478
|
+
if (d.type !== "missing-observed") return d;
|
|
16479
|
+
if (!d.extracted || d.edgeType !== import_types58.EdgeType.CONNECTS_TO) return d;
|
|
16480
|
+
if (!graph.hasNode(d.target)) return d;
|
|
16481
|
+
const target = graph.getNodeAttributes(d.target);
|
|
16482
|
+
if (target.type !== import_types58.NodeType.DatabaseNode) return d;
|
|
16483
|
+
if (d.extracted.evidence?.hostSource !== "literal") return d;
|
|
16484
|
+
if (datastoreEverObserved(graph, d.target)) return d;
|
|
16485
|
+
const engine = target.engine;
|
|
16486
|
+
if (!serviceHasObservedSameEngineStore(graph, buckets2, d.source, engine, d.target)) return d;
|
|
16487
|
+
const host = target.host ?? target.name;
|
|
16488
|
+
return {
|
|
16489
|
+
...d,
|
|
16490
|
+
confidence: Math.min(d.confidence, DEAD_CODE_PROBE_CONFIDENCE),
|
|
16491
|
+
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.`,
|
|
16492
|
+
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."
|
|
16493
|
+
};
|
|
16494
|
+
});
|
|
16495
|
+
}
|
|
16446
16496
|
function suppressHostMismatchHalves(all) {
|
|
16447
16497
|
const observedHalf = /* @__PURE__ */ new Set();
|
|
16448
16498
|
const declaredHalf = /* @__PURE__ */ new Set();
|
|
@@ -16479,7 +16529,8 @@ function computeDivergences(graph, opts = {}) {
|
|
|
16479
16529
|
}
|
|
16480
16530
|
});
|
|
16481
16531
|
const reconciled = suppressHostMismatchHalves(all);
|
|
16482
|
-
|
|
16532
|
+
const dampened = dampenDeadCodeProbes(graph, buckets2, reconciled);
|
|
16533
|
+
let filtered = dampened;
|
|
16483
16534
|
if (opts.type) {
|
|
16484
16535
|
const allowed = opts.type;
|
|
16485
16536
|
filtered = filtered.filter((d) => allowed.has(d.type));
|
|
@@ -16562,6 +16613,639 @@ function queryLogEntries(opts) {
|
|
|
16562
16613
|
return merged;
|
|
16563
16614
|
}
|
|
16564
16615
|
|
|
16616
|
+
// src/ask.ts
|
|
16617
|
+
init_cjs_shims();
|
|
16618
|
+
var import_types59 = require("@neat.is/types");
|
|
16619
|
+
var DEFAULT_MAX_NODES = 3;
|
|
16620
|
+
var MAX_FACTS_PER_SECTION = 6;
|
|
16621
|
+
var INTENT_RULES = [
|
|
16622
|
+
{
|
|
16623
|
+
intent: "root-cause",
|
|
16624
|
+
test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
|
|
16625
|
+
},
|
|
16626
|
+
{
|
|
16627
|
+
intent: "blast-radius",
|
|
16628
|
+
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/
|
|
16629
|
+
},
|
|
16630
|
+
{
|
|
16631
|
+
intent: "divergence",
|
|
16632
|
+
test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
|
|
16633
|
+
},
|
|
16634
|
+
{
|
|
16635
|
+
intent: "incidents",
|
|
16636
|
+
test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
|
|
16637
|
+
},
|
|
16638
|
+
{
|
|
16639
|
+
intent: "observed",
|
|
16640
|
+
test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
|
|
16641
|
+
},
|
|
16642
|
+
{
|
|
16643
|
+
intent: "dependencies",
|
|
16644
|
+
test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
|
|
16645
|
+
}
|
|
16646
|
+
];
|
|
16647
|
+
function classifyIntent(question) {
|
|
16648
|
+
const q = question.toLowerCase();
|
|
16649
|
+
for (const rule of INTENT_RULES) {
|
|
16650
|
+
if (rule.test.test(q)) return rule.intent;
|
|
16651
|
+
}
|
|
16652
|
+
return "overview";
|
|
16653
|
+
}
|
|
16654
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
16655
|
+
"the",
|
|
16656
|
+
"a",
|
|
16657
|
+
"an",
|
|
16658
|
+
"is",
|
|
16659
|
+
"are",
|
|
16660
|
+
"was",
|
|
16661
|
+
"were",
|
|
16662
|
+
"be",
|
|
16663
|
+
"been",
|
|
16664
|
+
"being",
|
|
16665
|
+
"of",
|
|
16666
|
+
"to",
|
|
16667
|
+
"in",
|
|
16668
|
+
"on",
|
|
16669
|
+
"at",
|
|
16670
|
+
"by",
|
|
16671
|
+
"for",
|
|
16672
|
+
"with",
|
|
16673
|
+
"and",
|
|
16674
|
+
"or",
|
|
16675
|
+
"but",
|
|
16676
|
+
"if",
|
|
16677
|
+
"as",
|
|
16678
|
+
"this",
|
|
16679
|
+
"that",
|
|
16680
|
+
"these",
|
|
16681
|
+
"those",
|
|
16682
|
+
"it",
|
|
16683
|
+
"its",
|
|
16684
|
+
"do",
|
|
16685
|
+
"does",
|
|
16686
|
+
"did",
|
|
16687
|
+
"has",
|
|
16688
|
+
"have",
|
|
16689
|
+
"had",
|
|
16690
|
+
"what",
|
|
16691
|
+
"which",
|
|
16692
|
+
"who",
|
|
16693
|
+
"whom",
|
|
16694
|
+
"how",
|
|
16695
|
+
"when",
|
|
16696
|
+
"where",
|
|
16697
|
+
"my",
|
|
16698
|
+
"me",
|
|
16699
|
+
"i",
|
|
16700
|
+
"we",
|
|
16701
|
+
"you",
|
|
16702
|
+
"they",
|
|
16703
|
+
"them",
|
|
16704
|
+
"from",
|
|
16705
|
+
"into",
|
|
16706
|
+
"about",
|
|
16707
|
+
"would",
|
|
16708
|
+
"will",
|
|
16709
|
+
"can",
|
|
16710
|
+
"could",
|
|
16711
|
+
"should",
|
|
16712
|
+
"anything",
|
|
16713
|
+
"everything",
|
|
16714
|
+
"something",
|
|
16715
|
+
"all",
|
|
16716
|
+
"any",
|
|
16717
|
+
"some",
|
|
16718
|
+
"no",
|
|
16719
|
+
"not",
|
|
16720
|
+
"so",
|
|
16721
|
+
"up",
|
|
16722
|
+
"out",
|
|
16723
|
+
"over",
|
|
16724
|
+
"get",
|
|
16725
|
+
"got",
|
|
16726
|
+
"show",
|
|
16727
|
+
"tell",
|
|
16728
|
+
"explain",
|
|
16729
|
+
"find",
|
|
16730
|
+
"give",
|
|
16731
|
+
"happen",
|
|
16732
|
+
"happens",
|
|
16733
|
+
"happening",
|
|
16734
|
+
"here",
|
|
16735
|
+
"there",
|
|
16736
|
+
"system",
|
|
16737
|
+
"code",
|
|
16738
|
+
"service",
|
|
16739
|
+
"node",
|
|
16740
|
+
"graph"
|
|
16741
|
+
]);
|
|
16742
|
+
var INTENT_WORDS = /* @__PURE__ */ new Set([
|
|
16743
|
+
"why",
|
|
16744
|
+
"root",
|
|
16745
|
+
"cause",
|
|
16746
|
+
"failing",
|
|
16747
|
+
"fail",
|
|
16748
|
+
"fails",
|
|
16749
|
+
"failed",
|
|
16750
|
+
"breaking",
|
|
16751
|
+
"broke",
|
|
16752
|
+
"broken",
|
|
16753
|
+
"crash",
|
|
16754
|
+
"crashing",
|
|
16755
|
+
"crashed",
|
|
16756
|
+
"throw",
|
|
16757
|
+
"throwing",
|
|
16758
|
+
"throws",
|
|
16759
|
+
"culprit",
|
|
16760
|
+
"blast",
|
|
16761
|
+
"radius",
|
|
16762
|
+
"break",
|
|
16763
|
+
"breaks",
|
|
16764
|
+
"impact",
|
|
16765
|
+
"downstream",
|
|
16766
|
+
"dependent",
|
|
16767
|
+
"dependents",
|
|
16768
|
+
"redeploy",
|
|
16769
|
+
"affect",
|
|
16770
|
+
"affects",
|
|
16771
|
+
"affected",
|
|
16772
|
+
"diverge",
|
|
16773
|
+
"divergence",
|
|
16774
|
+
"divergences",
|
|
16775
|
+
"weird",
|
|
16776
|
+
"mismatch",
|
|
16777
|
+
"drift",
|
|
16778
|
+
"inconsistent",
|
|
16779
|
+
"disagree",
|
|
16780
|
+
"declared",
|
|
16781
|
+
"observed",
|
|
16782
|
+
"incident",
|
|
16783
|
+
"incidents",
|
|
16784
|
+
"recent",
|
|
16785
|
+
"errors",
|
|
16786
|
+
"error",
|
|
16787
|
+
"failures",
|
|
16788
|
+
"failure",
|
|
16789
|
+
"history",
|
|
16790
|
+
"runtime",
|
|
16791
|
+
"production",
|
|
16792
|
+
"prod",
|
|
16793
|
+
"traffic",
|
|
16794
|
+
"actually",
|
|
16795
|
+
"really",
|
|
16796
|
+
"depend",
|
|
16797
|
+
"depends",
|
|
16798
|
+
"dependency",
|
|
16799
|
+
"dependencies",
|
|
16800
|
+
"call",
|
|
16801
|
+
"calls",
|
|
16802
|
+
"use",
|
|
16803
|
+
"uses",
|
|
16804
|
+
"using",
|
|
16805
|
+
"import",
|
|
16806
|
+
"imports",
|
|
16807
|
+
"relies",
|
|
16808
|
+
"rely",
|
|
16809
|
+
"needs",
|
|
16810
|
+
"need",
|
|
16811
|
+
"sync"
|
|
16812
|
+
]);
|
|
16813
|
+
function tokens(input) {
|
|
16814
|
+
const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
16815
|
+
return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
|
|
16816
|
+
}
|
|
16817
|
+
function queryTokens(question) {
|
|
16818
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16819
|
+
const out = [];
|
|
16820
|
+
for (const t of tokens(question)) {
|
|
16821
|
+
if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
|
|
16822
|
+
if (seen.has(t)) continue;
|
|
16823
|
+
seen.add(t);
|
|
16824
|
+
out.push(t);
|
|
16825
|
+
}
|
|
16826
|
+
return out;
|
|
16827
|
+
}
|
|
16828
|
+
function nodeName(node) {
|
|
16829
|
+
return node.name ?? node.id;
|
|
16830
|
+
}
|
|
16831
|
+
function idBody(id) {
|
|
16832
|
+
const colon = id.indexOf(":");
|
|
16833
|
+
return colon >= 0 ? id.slice(colon + 1) : id;
|
|
16834
|
+
}
|
|
16835
|
+
var EMBED_MIN_SCORE = 0.35;
|
|
16836
|
+
async function resolveEntities(graph, question, searchIndex, maxNodes) {
|
|
16837
|
+
const qTokens = queryTokens(question);
|
|
16838
|
+
const normalized = question.toLowerCase();
|
|
16839
|
+
const best = /* @__PURE__ */ new Map();
|
|
16840
|
+
const consider = (cand) => {
|
|
16841
|
+
const cur = best.get(cand.nodeId);
|
|
16842
|
+
if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
|
|
16843
|
+
};
|
|
16844
|
+
graph.forEachNode((id, attrs) => {
|
|
16845
|
+
const node = attrs;
|
|
16846
|
+
if (node.type === import_types59.NodeType.FrontierNode) return;
|
|
16847
|
+
const name = nodeName(node);
|
|
16848
|
+
const body = idBody(id);
|
|
16849
|
+
const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
|
|
16850
|
+
if (labelTokens.size === 0) return;
|
|
16851
|
+
let matched = 0;
|
|
16852
|
+
for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
|
|
16853
|
+
const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
|
|
16854
|
+
if (matched === 0 && !named) return;
|
|
16855
|
+
const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
|
|
16856
|
+
const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
|
|
16857
|
+
const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
|
|
16858
|
+
consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
|
|
16859
|
+
});
|
|
16860
|
+
if (searchIndex) {
|
|
16861
|
+
try {
|
|
16862
|
+
const res = await searchIndex.search(question, 10);
|
|
16863
|
+
if (res.provider !== "substring") {
|
|
16864
|
+
for (const m of res.matches) {
|
|
16865
|
+
if (m.node.type === import_types59.NodeType.FrontierNode) continue;
|
|
16866
|
+
const already = best.get(m.node.id);
|
|
16867
|
+
if (!already && m.score < EMBED_MIN_SCORE) continue;
|
|
16868
|
+
consider({
|
|
16869
|
+
nodeId: m.node.id,
|
|
16870
|
+
label: nodeName(m.node),
|
|
16871
|
+
via: "embedding",
|
|
16872
|
+
score: Math.max(0, Math.min(1, m.score))
|
|
16873
|
+
});
|
|
16874
|
+
}
|
|
16875
|
+
}
|
|
16876
|
+
} catch {
|
|
16877
|
+
}
|
|
16878
|
+
}
|
|
16879
|
+
const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
|
|
16880
|
+
return [...best.values()].sort(
|
|
16881
|
+
(a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
|
|
16882
|
+
).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
|
|
16883
|
+
}
|
|
16884
|
+
function incidentsForNode(nodeId, incidents) {
|
|
16885
|
+
if (!incidents || incidents.length === 0) return [];
|
|
16886
|
+
const svc = nodeId.replace(/^service:/, "");
|
|
16887
|
+
return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
|
|
16888
|
+
}
|
|
16889
|
+
function edgeSignalNote(e) {
|
|
16890
|
+
const bits = [];
|
|
16891
|
+
if (e.signal) {
|
|
16892
|
+
bits.push(`${e.signal.spanCount} spans`);
|
|
16893
|
+
if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
|
|
16894
|
+
if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
|
|
16895
|
+
} else if (e.callCount !== void 0) {
|
|
16896
|
+
bits.push(`${e.callCount} calls`);
|
|
16897
|
+
}
|
|
16898
|
+
return bits.length ? ` (${bits.join(", ")})` : "";
|
|
16899
|
+
}
|
|
16900
|
+
function buildRootCauseSection(graph, node, incidents, now) {
|
|
16901
|
+
const result = getRootCause(graph, node, void 0, incidents, { now });
|
|
16902
|
+
if (!result) return null;
|
|
16903
|
+
const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types59.Provenance.OBSERVED;
|
|
16904
|
+
const facts = [
|
|
16905
|
+
{
|
|
16906
|
+
text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
|
|
16907
|
+
provenance: lastProv,
|
|
16908
|
+
confidence: result.confidence
|
|
16909
|
+
}
|
|
16910
|
+
];
|
|
16911
|
+
const candidates = result.candidates ?? [];
|
|
16912
|
+
for (const c of candidates.slice(0, 3)) {
|
|
16913
|
+
facts.push({
|
|
16914
|
+
text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
|
|
16915
|
+
...c.provenance ? { provenance: c.provenance } : {},
|
|
16916
|
+
confidence: c.confidence
|
|
16917
|
+
});
|
|
16918
|
+
}
|
|
16919
|
+
if (result.traversalPath.length > 1) {
|
|
16920
|
+
facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
|
|
16921
|
+
}
|
|
16922
|
+
if (result.fixRecommendation) {
|
|
16923
|
+
facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
|
|
16924
|
+
}
|
|
16925
|
+
return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
|
|
16926
|
+
}
|
|
16927
|
+
function buildDependenciesSection(graph, node) {
|
|
16928
|
+
const result = getTransitiveDependencies(graph, node, 2);
|
|
16929
|
+
if (result.total === 0) return null;
|
|
16930
|
+
const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16931
|
+
text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
|
|
16932
|
+
// Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
|
|
16933
|
+
// edges, so the tag is on the fact, not asserted for the whole section.
|
|
16934
|
+
provenance: d.provenance
|
|
16935
|
+
}));
|
|
16936
|
+
return { heading: "Dependencies", facts };
|
|
16937
|
+
}
|
|
16938
|
+
function buildObservedSection(graph, node) {
|
|
16939
|
+
const result = getObservedDependencies(graph, node);
|
|
16940
|
+
const facts = [];
|
|
16941
|
+
for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
|
|
16942
|
+
const via = e.source !== node ? ` via ${e.source}` : "";
|
|
16943
|
+
facts.push({
|
|
16944
|
+
text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
|
|
16945
|
+
provenance: e.provenance,
|
|
16946
|
+
confidence: confidenceForEdge(e)
|
|
16947
|
+
});
|
|
16948
|
+
}
|
|
16949
|
+
if (facts.length === 0) {
|
|
16950
|
+
if (result.observed && result.inboundObservedCount > 0) {
|
|
16951
|
+
facts.push({
|
|
16952
|
+
text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
|
|
16953
|
+
provenance: import_types59.Provenance.OBSERVED
|
|
16954
|
+
});
|
|
16955
|
+
} else {
|
|
16956
|
+
return null;
|
|
16957
|
+
}
|
|
16958
|
+
}
|
|
16959
|
+
return { heading: "Runtime dependencies (OBSERVED)", facts };
|
|
16960
|
+
}
|
|
16961
|
+
function buildBlastSection(graph, node) {
|
|
16962
|
+
const result = getBlastRadius(graph, node);
|
|
16963
|
+
if (result.totalAffected === 0) return null;
|
|
16964
|
+
const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
|
|
16965
|
+
text: `${n.nodeId} (distance ${n.distance})`,
|
|
16966
|
+
provenance: n.edgeProvenance,
|
|
16967
|
+
confidence: n.confidence
|
|
16968
|
+
}));
|
|
16969
|
+
return {
|
|
16970
|
+
heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
|
|
16971
|
+
facts
|
|
16972
|
+
};
|
|
16973
|
+
}
|
|
16974
|
+
function buildIncidentsSection(node, incidents) {
|
|
16975
|
+
const relevant = incidentsForNode(node, incidents);
|
|
16976
|
+
if (relevant.length === 0) return null;
|
|
16977
|
+
const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
|
|
16978
|
+
const facts = ordered.map((ev) => ({
|
|
16979
|
+
text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
|
|
16980
|
+
// ErrorEvents are observation records — OBSERVED by definition.
|
|
16981
|
+
provenance: import_types59.Provenance.OBSERVED
|
|
16982
|
+
}));
|
|
16983
|
+
return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
|
|
16984
|
+
}
|
|
16985
|
+
function divergenceLine(d) {
|
|
16986
|
+
if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
|
|
16987
|
+
return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
|
|
16988
|
+
}
|
|
16989
|
+
return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
|
|
16990
|
+
}
|
|
16991
|
+
function buildDivergenceSection(graph, node) {
|
|
16992
|
+
const result = computeDivergences(graph, { node });
|
|
16993
|
+
if (result.totalAffected === 0) return null;
|
|
16994
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16995
|
+
text: divergenceLine(d),
|
|
16996
|
+
confidence: d.confidence
|
|
16997
|
+
// A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
|
|
16998
|
+
// carries no single provenance — the fact leaves it unset.
|
|
16999
|
+
}));
|
|
17000
|
+
return {
|
|
17001
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
17002
|
+
facts
|
|
17003
|
+
};
|
|
17004
|
+
}
|
|
17005
|
+
function buildGlobalDivergenceSection(graph) {
|
|
17006
|
+
const result = computeDivergences(graph);
|
|
17007
|
+
if (result.totalAffected === 0) {
|
|
17008
|
+
return {
|
|
17009
|
+
heading: "Divergences (EXTRACTED vs OBSERVED)",
|
|
17010
|
+
facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
|
|
17011
|
+
};
|
|
17012
|
+
}
|
|
17013
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
17014
|
+
text: divergenceLine(d),
|
|
17015
|
+
confidence: d.confidence
|
|
17016
|
+
// Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
|
|
17017
|
+
}));
|
|
17018
|
+
return {
|
|
17019
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
17020
|
+
facts
|
|
17021
|
+
};
|
|
17022
|
+
}
|
|
17023
|
+
function buildGlobalIncidentsSection(incidents) {
|
|
17024
|
+
if (!incidents || incidents.length === 0) {
|
|
17025
|
+
return {
|
|
17026
|
+
heading: "Incidents across the system",
|
|
17027
|
+
facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
|
|
17028
|
+
};
|
|
17029
|
+
}
|
|
17030
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
17031
|
+
for (const ev of incidents) {
|
|
17032
|
+
const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
|
|
17033
|
+
const cur = byKey.get(key);
|
|
17034
|
+
if (!cur) {
|
|
17035
|
+
byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
|
|
17036
|
+
} else {
|
|
17037
|
+
cur.count += 1;
|
|
17038
|
+
if (ev.timestamp > cur.latest) {
|
|
17039
|
+
cur.latest = ev.timestamp;
|
|
17040
|
+
cur.sampleMsg = ev.errorMessage;
|
|
17041
|
+
}
|
|
17042
|
+
}
|
|
17043
|
+
}
|
|
17044
|
+
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);
|
|
17045
|
+
const facts = rows.map((r) => ({
|
|
17046
|
+
text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
|
|
17047
|
+
provenance: import_types59.Provenance.OBSERVED
|
|
17048
|
+
}));
|
|
17049
|
+
return {
|
|
17050
|
+
heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
|
|
17051
|
+
facts
|
|
17052
|
+
};
|
|
17053
|
+
}
|
|
17054
|
+
function buildOverviewSections(graph, incidents) {
|
|
17055
|
+
const nodeByType = /* @__PURE__ */ new Map();
|
|
17056
|
+
const services = [];
|
|
17057
|
+
graph.forEachNode((_id, attrs) => {
|
|
17058
|
+
const node = attrs;
|
|
17059
|
+
nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
|
|
17060
|
+
if (node.type === import_types59.NodeType.ServiceNode) services.push(node.id);
|
|
17061
|
+
});
|
|
17062
|
+
const edgeByProv = /* @__PURE__ */ new Map();
|
|
17063
|
+
graph.forEachEdge((_id, attrs) => {
|
|
17064
|
+
const p = attrs.provenance;
|
|
17065
|
+
edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
|
|
17066
|
+
});
|
|
17067
|
+
const sections = [];
|
|
17068
|
+
const count = (t) => nodeByType.get(t) ?? 0;
|
|
17069
|
+
const shapeFacts = [
|
|
17070
|
+
{ text: `${graph.order} nodes, ${graph.size} edges` },
|
|
17071
|
+
{
|
|
17072
|
+
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`
|
|
17073
|
+
}
|
|
17074
|
+
];
|
|
17075
|
+
for (const p of [import_types59.Provenance.EXTRACTED, import_types59.Provenance.OBSERVED, import_types59.Provenance.INFERRED, import_types59.Provenance.STALE]) {
|
|
17076
|
+
const n = edgeByProv.get(p) ?? 0;
|
|
17077
|
+
if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
|
|
17078
|
+
}
|
|
17079
|
+
sections.push({ heading: "System shape", facts: shapeFacts });
|
|
17080
|
+
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);
|
|
17081
|
+
if (depCounts.length > 0) {
|
|
17082
|
+
sections.push({
|
|
17083
|
+
heading: "Busiest services (by direct dependencies)",
|
|
17084
|
+
facts: depCounts.map((r) => ({
|
|
17085
|
+
text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
|
|
17086
|
+
}))
|
|
17087
|
+
});
|
|
17088
|
+
}
|
|
17089
|
+
if (incidents && incidents.length > 0) {
|
|
17090
|
+
const incCount = /* @__PURE__ */ new Map();
|
|
17091
|
+
for (const ev of incidents) {
|
|
17092
|
+
const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
|
|
17093
|
+
incCount.set(key, (incCount.get(key) ?? 0) + 1);
|
|
17094
|
+
}
|
|
17095
|
+
const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
|
|
17096
|
+
sections.push({
|
|
17097
|
+
heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
|
|
17098
|
+
facts: top.map(([k, n]) => ({
|
|
17099
|
+
text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
|
|
17100
|
+
provenance: import_types59.Provenance.OBSERVED
|
|
17101
|
+
}))
|
|
17102
|
+
});
|
|
17103
|
+
}
|
|
17104
|
+
const div = computeDivergences(graph);
|
|
17105
|
+
sections.push({
|
|
17106
|
+
heading: "Divergences",
|
|
17107
|
+
facts: [
|
|
17108
|
+
{
|
|
17109
|
+
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.`
|
|
17110
|
+
}
|
|
17111
|
+
]
|
|
17112
|
+
});
|
|
17113
|
+
return sections;
|
|
17114
|
+
}
|
|
17115
|
+
function buildGlobalSections(intent, graph, incidents) {
|
|
17116
|
+
switch (intent) {
|
|
17117
|
+
case "divergence":
|
|
17118
|
+
return [buildGlobalDivergenceSection(graph)];
|
|
17119
|
+
case "incidents":
|
|
17120
|
+
return [buildGlobalIncidentsSection(incidents)];
|
|
17121
|
+
case "overview":
|
|
17122
|
+
return buildOverviewSections(graph, incidents);
|
|
17123
|
+
default:
|
|
17124
|
+
return null;
|
|
17125
|
+
}
|
|
17126
|
+
}
|
|
17127
|
+
var SECTION_ORDER = {
|
|
17128
|
+
"root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
|
|
17129
|
+
"blast-radius": ["blast", "dependencies", "observed", "incidents"],
|
|
17130
|
+
dependencies: ["dependencies", "observed", "blast"],
|
|
17131
|
+
observed: ["observed", "dependencies", "incidents"],
|
|
17132
|
+
incidents: ["incidents", "root-cause", "observed"],
|
|
17133
|
+
divergence: ["divergence", "observed", "dependencies", "incidents"],
|
|
17134
|
+
overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
|
|
17135
|
+
};
|
|
17136
|
+
function buildSection(kind, graph, node, incidents, now) {
|
|
17137
|
+
switch (kind) {
|
|
17138
|
+
case "root-cause":
|
|
17139
|
+
return buildRootCauseSection(graph, node, incidents, now);
|
|
17140
|
+
case "dependencies":
|
|
17141
|
+
return buildDependenciesSection(graph, node);
|
|
17142
|
+
case "observed":
|
|
17143
|
+
return buildObservedSection(graph, node);
|
|
17144
|
+
case "blast":
|
|
17145
|
+
return buildBlastSection(graph, node);
|
|
17146
|
+
case "incidents":
|
|
17147
|
+
return buildIncidentsSection(node, incidents);
|
|
17148
|
+
case "divergence":
|
|
17149
|
+
return buildDivergenceSection(graph, node);
|
|
17150
|
+
}
|
|
17151
|
+
}
|
|
17152
|
+
function summarizeGlobal(intent, sections) {
|
|
17153
|
+
const lead = sections[0];
|
|
17154
|
+
switch (intent) {
|
|
17155
|
+
case "divergence": {
|
|
17156
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
17157
|
+
return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
|
|
17158
|
+
}
|
|
17159
|
+
case "incidents": {
|
|
17160
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
17161
|
+
return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
|
|
17162
|
+
}
|
|
17163
|
+
case "overview":
|
|
17164
|
+
default:
|
|
17165
|
+
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.`;
|
|
17166
|
+
}
|
|
17167
|
+
}
|
|
17168
|
+
function summarize(question, intent, matched, primary, sections, scope) {
|
|
17169
|
+
if (scope === "global") {
|
|
17170
|
+
return summarizeGlobal(intent, sections);
|
|
17171
|
+
}
|
|
17172
|
+
if (!primary) {
|
|
17173
|
+
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.`;
|
|
17174
|
+
}
|
|
17175
|
+
const lead = sections[0];
|
|
17176
|
+
const others = matched.slice(1);
|
|
17177
|
+
const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
|
|
17178
|
+
let core;
|
|
17179
|
+
switch (intent) {
|
|
17180
|
+
case "root-cause": {
|
|
17181
|
+
const rc = sections.find((s) => s.heading === "Root cause (navigation)");
|
|
17182
|
+
if (rc) {
|
|
17183
|
+
core = rc.facts[0]?.text ?? "";
|
|
17184
|
+
} else if (lead) {
|
|
17185
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
|
|
17186
|
+
} else {
|
|
17187
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
|
|
17188
|
+
}
|
|
17189
|
+
break;
|
|
17190
|
+
}
|
|
17191
|
+
case "blast-radius":
|
|
17192
|
+
core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
|
|
17193
|
+
break;
|
|
17194
|
+
case "dependencies":
|
|
17195
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
|
|
17196
|
+
break;
|
|
17197
|
+
case "observed":
|
|
17198
|
+
core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
|
|
17199
|
+
break;
|
|
17200
|
+
case "incidents":
|
|
17201
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
|
|
17202
|
+
break;
|
|
17203
|
+
case "divergence":
|
|
17204
|
+
core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
|
|
17205
|
+
break;
|
|
17206
|
+
default:
|
|
17207
|
+
core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
|
|
17208
|
+
}
|
|
17209
|
+
return `${core}${alsoNote}`;
|
|
17210
|
+
}
|
|
17211
|
+
async function askGraph(graph, question, opts = {}) {
|
|
17212
|
+
const now = opts.now ?? Date.now();
|
|
17213
|
+
const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
|
|
17214
|
+
const intent = classifyIntent(question);
|
|
17215
|
+
const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
|
|
17216
|
+
const primary = matched[0]?.nodeId;
|
|
17217
|
+
const sections = [];
|
|
17218
|
+
let scope;
|
|
17219
|
+
if (primary) {
|
|
17220
|
+
scope = "node";
|
|
17221
|
+
for (const kind of SECTION_ORDER[intent]) {
|
|
17222
|
+
const s = buildSection(kind, graph, primary, opts.incidents, now);
|
|
17223
|
+
if (s && s.facts.length > 0) sections.push(s);
|
|
17224
|
+
}
|
|
17225
|
+
} else {
|
|
17226
|
+
const global = buildGlobalSections(intent, graph, opts.incidents);
|
|
17227
|
+
if (global) {
|
|
17228
|
+
scope = "global";
|
|
17229
|
+
for (const s of global) if (s.facts.length > 0) sections.push(s);
|
|
17230
|
+
}
|
|
17231
|
+
}
|
|
17232
|
+
const answer = summarize(question, intent, matched, primary, sections, scope);
|
|
17233
|
+
const provSet = /* @__PURE__ */ new Set();
|
|
17234
|
+
for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
|
|
17235
|
+
const confidence = sections[0]?.facts[0]?.confidence;
|
|
17236
|
+
return import_types59.AskResultSchema.parse({
|
|
17237
|
+
question,
|
|
17238
|
+
intent,
|
|
17239
|
+
matched,
|
|
17240
|
+
...primary ? { primaryNode: primary } : {},
|
|
17241
|
+
...scope ? { scope } : {},
|
|
17242
|
+
sections,
|
|
17243
|
+
answer,
|
|
17244
|
+
...confidence !== void 0 ? { confidence } : {},
|
|
17245
|
+
provenance: [...provSet]
|
|
17246
|
+
});
|
|
17247
|
+
}
|
|
17248
|
+
|
|
16565
17249
|
// src/diff.ts
|
|
16566
17250
|
init_cjs_shims();
|
|
16567
17251
|
var import_node_fs36 = require("fs");
|
|
@@ -16696,7 +17380,7 @@ init_cjs_shims();
|
|
|
16696
17380
|
var import_node_fs37 = require("fs");
|
|
16697
17381
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
16698
17382
|
var import_node_path72 = __toESM(require("path"), 1);
|
|
16699
|
-
var
|
|
17383
|
+
var import_types60 = require("@neat.is/types");
|
|
16700
17384
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
16701
17385
|
var LOCK_RETRY_MS = 50;
|
|
16702
17386
|
function neatHome() {
|
|
@@ -16906,10 +17590,10 @@ async function readRegistry() {
|
|
|
16906
17590
|
throw err;
|
|
16907
17591
|
}
|
|
16908
17592
|
const parsed = JSON.parse(raw);
|
|
16909
|
-
return
|
|
17593
|
+
return import_types60.RegistryFileSchema.parse(parsed);
|
|
16910
17594
|
}
|
|
16911
17595
|
async function writeRegistry(reg) {
|
|
16912
|
-
const validated =
|
|
17596
|
+
const validated = import_types60.RegistryFileSchema.parse(reg);
|
|
16913
17597
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
16914
17598
|
}
|
|
16915
17599
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -17310,15 +17994,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
17310
17994
|
|
|
17311
17995
|
// src/connectors/index.ts
|
|
17312
17996
|
init_cjs_shims();
|
|
17313
|
-
var
|
|
17997
|
+
var import_types61 = require("@neat.is/types");
|
|
17314
17998
|
var NO_ENV = "unknown";
|
|
17315
17999
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
17316
18000
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
17317
18001
|
const sites = [];
|
|
17318
18002
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
17319
18003
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
17320
|
-
if (edge.provenance !==
|
|
17321
|
-
const parsed = (0,
|
|
18004
|
+
if (edge.provenance !== import_types61.Provenance.EXTRACTED) continue;
|
|
18005
|
+
const parsed = (0, import_types61.parseFileId)(edge.source);
|
|
17322
18006
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
17323
18007
|
const site = { relPath: edge.evidence.file };
|
|
17324
18008
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -17329,7 +18013,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
17329
18013
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
17330
18014
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
17331
18015
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
17332
|
-
if (attrs.type !==
|
|
18016
|
+
if (attrs.type !== import_types61.NodeType.RouteNode || !attrs.path) return void 0;
|
|
17333
18017
|
const site = { relPath: attrs.path };
|
|
17334
18018
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
17335
18019
|
return site;
|
|
@@ -17941,23 +18625,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
17941
18625
|
|
|
17942
18626
|
// src/connectors/supabase/resolve.ts
|
|
17943
18627
|
init_cjs_shims();
|
|
17944
|
-
var
|
|
18628
|
+
var import_types63 = require("@neat.is/types");
|
|
17945
18629
|
function createSupabaseResolveTarget(graph, config) {
|
|
17946
18630
|
return (signal, _ctx) => {
|
|
17947
18631
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
17948
18632
|
return null;
|
|
17949
18633
|
}
|
|
17950
|
-
const subResourceId = (0,
|
|
18634
|
+
const subResourceId = (0, import_types63.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
17951
18635
|
if (graph.hasNode(subResourceId)) {
|
|
17952
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
18636
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17953
18637
|
}
|
|
17954
|
-
const bareResourceId = (0,
|
|
18638
|
+
const bareResourceId = (0, import_types63.infraId)(signal.targetKind, signal.targetName);
|
|
17955
18639
|
if (graph.hasNode(bareResourceId)) {
|
|
17956
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
18640
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17957
18641
|
}
|
|
17958
|
-
const projectLevelId = (0,
|
|
18642
|
+
const projectLevelId = (0, import_types63.infraId)("supabase", config.nodeRef);
|
|
17959
18643
|
if (graph.hasNode(projectLevelId)) {
|
|
17960
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
18644
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17961
18645
|
}
|
|
17962
18646
|
return null;
|
|
17963
18647
|
};
|
|
@@ -18050,7 +18734,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
18050
18734
|
|
|
18051
18735
|
// src/connectors/railway/index.ts
|
|
18052
18736
|
init_cjs_shims();
|
|
18053
|
-
var
|
|
18737
|
+
var import_types67 = require("@neat.is/types");
|
|
18054
18738
|
|
|
18055
18739
|
// src/connectors/railway/client.ts
|
|
18056
18740
|
init_cjs_shims();
|
|
@@ -18201,7 +18885,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
18201
18885
|
const out = [];
|
|
18202
18886
|
graph.forEachNode((_id, attrs) => {
|
|
18203
18887
|
const node = attrs;
|
|
18204
|
-
if (node.type !==
|
|
18888
|
+
if (node.type !== import_types67.NodeType.RouteNode) return;
|
|
18205
18889
|
const route = attrs;
|
|
18206
18890
|
if (route.service !== serviceName) return;
|
|
18207
18891
|
out.push({
|
|
@@ -18305,12 +18989,12 @@ function createRailwayResolveTarget(config) {
|
|
|
18305
18989
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
18306
18990
|
if (!serviceName) return null;
|
|
18307
18991
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
18308
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
18992
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types67.EdgeType.CALLS };
|
|
18309
18993
|
}
|
|
18310
18994
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
18311
18995
|
const peerName = config.serviceNameById[signal.targetName];
|
|
18312
18996
|
if (!peerName) return null;
|
|
18313
|
-
return { targetNodeId: (0,
|
|
18997
|
+
return { targetNodeId: (0, import_types67.serviceId)(peerName), serviceName, edgeType: import_types67.EdgeType.CONNECTS_TO };
|
|
18314
18998
|
}
|
|
18315
18999
|
return null;
|
|
18316
19000
|
};
|
|
@@ -18498,7 +19182,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
18498
19182
|
|
|
18499
19183
|
// src/connectors/firebase/resolve.ts
|
|
18500
19184
|
init_cjs_shims();
|
|
18501
|
-
var
|
|
19185
|
+
var import_types68 = require("@neat.is/types");
|
|
18502
19186
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
18503
19187
|
switch (resourceType) {
|
|
18504
19188
|
case "cloud_function":
|
|
@@ -18513,7 +19197,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
18513
19197
|
const entries = [];
|
|
18514
19198
|
graph.forEachNode((_id, attrs) => {
|
|
18515
19199
|
const node = attrs;
|
|
18516
|
-
if (node.type !==
|
|
19200
|
+
if (node.type !== import_types68.NodeType.RouteNode) return;
|
|
18517
19201
|
const route = attrs;
|
|
18518
19202
|
if (route.service !== serviceName) return;
|
|
18519
19203
|
entries.push({
|
|
@@ -18545,7 +19229,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
18545
19229
|
return {
|
|
18546
19230
|
targetNodeId: match.routeNodeId,
|
|
18547
19231
|
serviceName,
|
|
18548
|
-
edgeType:
|
|
19232
|
+
edgeType: import_types68.EdgeType.CALLS
|
|
18549
19233
|
};
|
|
18550
19234
|
};
|
|
18551
19235
|
}
|
|
@@ -18572,7 +19256,7 @@ init_cjs_shims();
|
|
|
18572
19256
|
|
|
18573
19257
|
// src/connectors/cloudflare/connector.ts
|
|
18574
19258
|
init_cjs_shims();
|
|
18575
|
-
var
|
|
19259
|
+
var import_types70 = require("@neat.is/types");
|
|
18576
19260
|
|
|
18577
19261
|
// src/connectors/cloudflare/client.ts
|
|
18578
19262
|
init_cjs_shims();
|
|
@@ -18736,7 +19420,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
18736
19420
|
graph.forEachNode((id, attrs) => {
|
|
18737
19421
|
if (found) return;
|
|
18738
19422
|
const a = attrs;
|
|
18739
|
-
if (a.type ===
|
|
19423
|
+
if (a.type === import_types70.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
18740
19424
|
found = id;
|
|
18741
19425
|
}
|
|
18742
19426
|
});
|
|
@@ -18748,7 +19432,7 @@ function findMatchingRouteNode(graph, serviceName, method, path76) {
|
|
|
18748
19432
|
graph.forEachNode((id, attrs) => {
|
|
18749
19433
|
if (found) return;
|
|
18750
19434
|
const a = attrs;
|
|
18751
|
-
if (a.type !==
|
|
19435
|
+
if (a.type !== import_types70.NodeType.RouteNode || a.service !== serviceName) return;
|
|
18752
19436
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
18753
19437
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
18754
19438
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -18767,11 +19451,11 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
18767
19451
|
};
|
|
18768
19452
|
const mapping = config.workers?.[scriptName];
|
|
18769
19453
|
if (mapping) {
|
|
18770
|
-
const wholeFileId = (0,
|
|
19454
|
+
const wholeFileId = (0, import_types70.fileId)(mapping.service, mapping.entryFile);
|
|
18771
19455
|
return {
|
|
18772
19456
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
18773
19457
|
serviceName: mapping.service,
|
|
18774
|
-
edgeType:
|
|
19458
|
+
edgeType: import_types70.EdgeType.CALLS
|
|
18775
19459
|
};
|
|
18776
19460
|
}
|
|
18777
19461
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -18780,13 +19464,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
18780
19464
|
return {
|
|
18781
19465
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
18782
19466
|
serviceName: fileNode.service,
|
|
18783
|
-
edgeType:
|
|
19467
|
+
edgeType: import_types70.EdgeType.CALLS
|
|
18784
19468
|
};
|
|
18785
19469
|
}
|
|
18786
19470
|
return {
|
|
18787
|
-
targetNodeId: (0,
|
|
19471
|
+
targetNodeId: (0, import_types70.infraId)("cloudflare-worker", scriptName),
|
|
18788
19472
|
serviceName: scriptName,
|
|
18789
|
-
edgeType:
|
|
19473
|
+
edgeType: import_types70.EdgeType.CALLS,
|
|
18790
19474
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
18791
19475
|
};
|
|
18792
19476
|
};
|
|
@@ -18982,14 +19666,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
18982
19666
|
|
|
18983
19667
|
// src/connectors/neon/resolve.ts
|
|
18984
19668
|
init_cjs_shims();
|
|
18985
|
-
var
|
|
19669
|
+
var import_types74 = require("@neat.is/types");
|
|
18986
19670
|
function createNeonResolveTarget(config) {
|
|
18987
19671
|
return (signal) => {
|
|
18988
19672
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
18989
19673
|
return {
|
|
18990
|
-
targetNodeId: (0,
|
|
19674
|
+
targetNodeId: (0, import_types74.infraId)("sql-table", signal.targetName),
|
|
18991
19675
|
serviceName: config.serviceName,
|
|
18992
|
-
edgeType:
|
|
19676
|
+
edgeType: import_types74.EdgeType.CALLS,
|
|
18993
19677
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
18994
19678
|
};
|
|
18995
19679
|
};
|
|
@@ -19170,14 +19854,14 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
19170
19854
|
|
|
19171
19855
|
// src/connectors/cloud-run/resolve.ts
|
|
19172
19856
|
init_cjs_shims();
|
|
19173
|
-
var
|
|
19857
|
+
var import_types78 = require("@neat.is/types");
|
|
19174
19858
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
19175
19859
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
19176
19860
|
let found = null;
|
|
19177
19861
|
graph.forEachNode((_id, attrs) => {
|
|
19178
19862
|
if (found) return;
|
|
19179
19863
|
const node = attrs;
|
|
19180
|
-
if (node.type !==
|
|
19864
|
+
if (node.type !== import_types78.NodeType.RouteNode) return;
|
|
19181
19865
|
const route = attrs;
|
|
19182
19866
|
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
19183
19867
|
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
@@ -19202,13 +19886,13 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
19202
19886
|
normalizePathTemplate(path76)
|
|
19203
19887
|
);
|
|
19204
19888
|
if (routeNodeId) {
|
|
19205
|
-
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType:
|
|
19889
|
+
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types78.EdgeType.CALLS };
|
|
19206
19890
|
}
|
|
19207
19891
|
}
|
|
19208
19892
|
return {
|
|
19209
|
-
targetNodeId: (0,
|
|
19893
|
+
targetNodeId: (0, import_types78.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
19210
19894
|
serviceName: mappedService ?? gcpServiceName,
|
|
19211
|
-
edgeType:
|
|
19895
|
+
edgeType: import_types78.EdgeType.CALLS,
|
|
19212
19896
|
ensureInfraNode: {
|
|
19213
19897
|
kind: CLOUD_RUN_SERVICE_INFRA_KIND,
|
|
19214
19898
|
name: gcpServiceName,
|
|
@@ -19249,7 +19933,7 @@ function createCloudRunConnector(graph, config = {}) {
|
|
|
19249
19933
|
|
|
19250
19934
|
// src/connectors/render/index.ts
|
|
19251
19935
|
init_cjs_shims();
|
|
19252
|
-
var
|
|
19936
|
+
var import_types81 = require("@neat.is/types");
|
|
19253
19937
|
|
|
19254
19938
|
// src/connectors/render/types.ts
|
|
19255
19939
|
init_cjs_shims();
|
|
@@ -19327,7 +20011,7 @@ function buildRenderRouteIndex(graph, serviceName) {
|
|
|
19327
20011
|
const out = [];
|
|
19328
20012
|
graph.forEachNode((_id, attrs) => {
|
|
19329
20013
|
const node = attrs;
|
|
19330
|
-
if (node.type !==
|
|
20014
|
+
if (node.type !== import_types81.NodeType.RouteNode) return;
|
|
19331
20015
|
const route = attrs;
|
|
19332
20016
|
if (route.service !== serviceName) return;
|
|
19333
20017
|
out.push({
|
|
@@ -19412,7 +20096,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
|
|
|
19412
20096
|
function createRenderResolveTarget(config) {
|
|
19413
20097
|
return (signal) => {
|
|
19414
20098
|
if (signal.targetKind === ROUTE_TARGET_KIND2) {
|
|
19415
|
-
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType:
|
|
20099
|
+
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types81.EdgeType.CALLS };
|
|
19416
20100
|
}
|
|
19417
20101
|
return null;
|
|
19418
20102
|
};
|
|
@@ -19550,21 +20234,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
|
|
|
19550
20234
|
|
|
19551
20235
|
// src/connectors/planetscale/resolve.ts
|
|
19552
20236
|
init_cjs_shims();
|
|
19553
|
-
var
|
|
20237
|
+
var import_types85 = require("@neat.is/types");
|
|
19554
20238
|
var PLANETSCALE_DATABASE_KIND = "planetscale-database";
|
|
19555
20239
|
function createPlanetscaleResolveTarget(graph, config) {
|
|
19556
20240
|
const databaseName = `${config.organization}/${config.database}`;
|
|
19557
20241
|
return (signal, _ctx) => {
|
|
19558
20242
|
if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
19559
|
-
const tableId = (0,
|
|
20243
|
+
const tableId = (0, import_types85.infraId)("sql-table", signal.targetName);
|
|
19560
20244
|
if (graph.hasNode(tableId)) {
|
|
19561
|
-
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType:
|
|
20245
|
+
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types85.EdgeType.CALLS };
|
|
19562
20246
|
}
|
|
19563
|
-
const providerId = (0,
|
|
20247
|
+
const providerId = (0, import_types85.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
|
|
19564
20248
|
return {
|
|
19565
20249
|
targetNodeId: providerId,
|
|
19566
20250
|
serviceName: config.serviceName,
|
|
19567
|
-
edgeType:
|
|
20251
|
+
edgeType: import_types85.EdgeType.CALLS,
|
|
19568
20252
|
ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
|
|
19569
20253
|
};
|
|
19570
20254
|
};
|
|
@@ -19829,7 +20513,7 @@ function mapBuildsToSignals(builds, serviceName) {
|
|
|
19829
20513
|
|
|
19830
20514
|
// src/connectors/eas/resolve.ts
|
|
19831
20515
|
init_cjs_shims();
|
|
19832
|
-
var
|
|
20516
|
+
var import_types90 = require("@neat.is/types");
|
|
19833
20517
|
var NO_ENV2 = "unknown";
|
|
19834
20518
|
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
19835
20519
|
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
@@ -19845,8 +20529,8 @@ function configBasenamesForPhase(phase) {
|
|
|
19845
20529
|
function configNodeService(graph, configNodeId) {
|
|
19846
20530
|
for (const edgeId of graph.inboundEdges(configNodeId)) {
|
|
19847
20531
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
19848
|
-
if (edge.type !==
|
|
19849
|
-
const parsed = (0,
|
|
20532
|
+
if (edge.type !== import_types90.EdgeType.CONFIGURED_BY) continue;
|
|
20533
|
+
const parsed = (0, import_types90.parseFileId)(edge.source);
|
|
19850
20534
|
if (parsed) return parsed.service;
|
|
19851
20535
|
}
|
|
19852
20536
|
return null;
|
|
@@ -19857,7 +20541,7 @@ function findConfigNode(graph, basenames, serviceName) {
|
|
|
19857
20541
|
graph.forEachNode((id, attrs) => {
|
|
19858
20542
|
if (scoped) return;
|
|
19859
20543
|
const node = attrs;
|
|
19860
|
-
if (node.type !==
|
|
20544
|
+
if (node.type !== import_types90.NodeType.ConfigNode) return;
|
|
19861
20545
|
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
19862
20546
|
if (anyMatch === null) anyMatch = id;
|
|
19863
20547
|
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
@@ -19874,13 +20558,13 @@ function createEasResolveTarget(graph) {
|
|
|
19874
20558
|
if (basenames.length > 0) {
|
|
19875
20559
|
const configNodeId = findConfigNode(graph, basenames, serviceName);
|
|
19876
20560
|
if (configNodeId) {
|
|
19877
|
-
return { targetNodeId: configNodeId, serviceName, edgeType:
|
|
20561
|
+
return { targetNodeId: configNodeId, serviceName, edgeType: import_types90.EdgeType.CALLS };
|
|
19878
20562
|
}
|
|
19879
20563
|
}
|
|
19880
20564
|
return {
|
|
19881
20565
|
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
|
|
19882
20566
|
serviceName,
|
|
19883
|
-
edgeType:
|
|
20567
|
+
edgeType: import_types90.EdgeType.CALLS
|
|
19884
20568
|
};
|
|
19885
20569
|
};
|
|
19886
20570
|
}
|
|
@@ -20605,11 +21289,11 @@ function registerRoutes(scope, ctx) {
|
|
|
20605
21289
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
20606
21290
|
const parsed = [];
|
|
20607
21291
|
for (const c of candidates) {
|
|
20608
|
-
const r =
|
|
21292
|
+
const r = import_types93.DivergenceTypeSchema.safeParse(c);
|
|
20609
21293
|
if (!r.success) {
|
|
20610
21294
|
return reply.code(400).send({
|
|
20611
21295
|
error: `unknown divergence type "${c}"`,
|
|
20612
|
-
allowed:
|
|
21296
|
+
allowed: import_types93.DivergenceTypeSchema.options
|
|
20613
21297
|
});
|
|
20614
21298
|
}
|
|
20615
21299
|
parsed.push(r.data);
|
|
@@ -20851,6 +21535,18 @@ function registerRoutes(scope, ctx) {
|
|
|
20851
21535
|
matches: matches.slice(0, safeLimit)
|
|
20852
21536
|
};
|
|
20853
21537
|
});
|
|
21538
|
+
scope.get("/graph/ask", async (req, reply) => {
|
|
21539
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
21540
|
+
if (!proj) return;
|
|
21541
|
+
const question = (req.query.q ?? "").trim();
|
|
21542
|
+
if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
|
|
21543
|
+
const epath = errorsPathFor(proj);
|
|
21544
|
+
const incidents = epath ? await readErrorEvents(epath) : [];
|
|
21545
|
+
return askGraph(proj.graph, question, {
|
|
21546
|
+
...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
|
|
21547
|
+
incidents
|
|
21548
|
+
});
|
|
21549
|
+
});
|
|
20854
21550
|
scope.get(
|
|
20855
21551
|
"/graph/diff",
|
|
20856
21552
|
async (req, reply) => {
|
|
@@ -20951,7 +21647,7 @@ function registerRoutes(scope, ctx) {
|
|
|
20951
21647
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
20952
21648
|
let violations = await log.readAll();
|
|
20953
21649
|
if (req.query.severity) {
|
|
20954
|
-
const sev =
|
|
21650
|
+
const sev = import_types93.PolicySeveritySchema.safeParse(req.query.severity);
|
|
20955
21651
|
if (!sev.success) {
|
|
20956
21652
|
return reply.code(400).send({
|
|
20957
21653
|
error: "invalid severity",
|
|
@@ -20990,7 +21686,7 @@ function registerRoutes(scope, ctx) {
|
|
|
20990
21686
|
scope.post("/policies/check", async (req, reply) => {
|
|
20991
21687
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
20992
21688
|
if (!proj) return;
|
|
20993
|
-
const parsed =
|
|
21689
|
+
const parsed = import_types93.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
20994
21690
|
if (!parsed.success) {
|
|
20995
21691
|
return reply.code(400).send({
|
|
20996
21692
|
error: "invalid /policies/check body",
|
|
@@ -21339,7 +22035,7 @@ function unroutedErrorsPath(neatHome3) {
|
|
|
21339
22035
|
}
|
|
21340
22036
|
|
|
21341
22037
|
// src/daemon.ts
|
|
21342
|
-
var
|
|
22038
|
+
var import_types94 = require("@neat.is/types");
|
|
21343
22039
|
function daemonJsonPath(scanPath) {
|
|
21344
22040
|
return import_node_path75.default.join(scanPath, "neat-out", "daemon.json");
|
|
21345
22041
|
}
|
|
@@ -21451,8 +22147,8 @@ function isTokenContained(needle, haystack) {
|
|
|
21451
22147
|
const n = needle.toLowerCase();
|
|
21452
22148
|
const h = haystack.toLowerCase();
|
|
21453
22149
|
if (!h.includes(n)) return false;
|
|
21454
|
-
const
|
|
21455
|
-
return
|
|
22150
|
+
const tokens2 = h.split(/[-_]/);
|
|
22151
|
+
return tokens2.includes(n);
|
|
21456
22152
|
}
|
|
21457
22153
|
function serviceNameMatchesProject(serviceName, project) {
|
|
21458
22154
|
if (serviceName.toLowerCase() === project.toLowerCase()) return true;
|
|
@@ -21464,7 +22160,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
21464
22160
|
if (!serviceName) return true;
|
|
21465
22161
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
21466
22162
|
return graph.someNode(
|
|
21467
|
-
(_id, attrs) => attrs.type ===
|
|
22163
|
+
(_id, attrs) => attrs.type === import_types94.NodeType.ServiceNode && attrs.name === serviceName
|
|
21468
22164
|
);
|
|
21469
22165
|
}
|
|
21470
22166
|
async function bootstrapProject(entry, connectors = [], neatHome3) {
|