@neat.is/core 0.9.2-dev.20260822 → 0.9.2
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-HD5X5TWY.js} +690 -46
- package/dist/chunk-HD5X5TWY.js.map +1 -0
- package/dist/{chunk-L4SZIIER.js → chunk-TMHCS4ZY.js} +2 -2
- package/dist/cli.cjs +1148 -279
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +333 -110
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +733 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +733 -88
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +732 -87
- 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-TMHCS4ZY.js.map} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -787,7 +787,7 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
787
787
|
init_cjs_shims();
|
|
788
788
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
789
789
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
790
|
-
var
|
|
790
|
+
var import_types93 = require("@neat.is/types");
|
|
791
791
|
|
|
792
792
|
// src/extend/index.ts
|
|
793
793
|
init_cjs_shims();
|
|
@@ -4160,11 +4160,11 @@ function actixAttributeRoute(attrItem, out) {
|
|
|
4160
4160
|
if (!nameNode) return;
|
|
4161
4161
|
const macro = nameNode.type === "identifier" ? nameNode.text : nameNode.type === "scoped_identifier" ? nameNode.childForFieldName("name")?.text ?? null : null;
|
|
4162
4162
|
if (!macro) return;
|
|
4163
|
-
const
|
|
4164
|
-
if (!
|
|
4163
|
+
const tokens2 = attr.childForFieldName("arguments");
|
|
4164
|
+
if (!tokens2 || tokens2.type !== "token_tree") return;
|
|
4165
4165
|
const strings = [];
|
|
4166
|
-
for (let i = 0; i <
|
|
4167
|
-
const s = rustStringContent(
|
|
4166
|
+
for (let i = 0; i < tokens2.namedChildCount; i++) {
|
|
4167
|
+
const s = rustStringContent(tokens2.namedChild(i));
|
|
4168
4168
|
if (s !== null) strings.push(s);
|
|
4169
4169
|
}
|
|
4170
4170
|
const pathStr = strings[0];
|
|
@@ -5982,29 +5982,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
5982
5982
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
5983
5983
|
});
|
|
5984
5984
|
let promoted = 0;
|
|
5985
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
5985
|
+
for (const { frontierId: frontierId2, serviceId: serviceId16 } of toPromote) {
|
|
5986
5986
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
5987
5987
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
5988
5988
|
if (!gate.allowed) {
|
|
5989
5989
|
continue;
|
|
5990
5990
|
}
|
|
5991
5991
|
}
|
|
5992
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
5992
|
+
rewireFrontierEdges(graph, frontierId2, serviceId16);
|
|
5993
5993
|
graph.dropNode(frontierId2);
|
|
5994
5994
|
promoted++;
|
|
5995
5995
|
}
|
|
5996
5996
|
return promoted;
|
|
5997
5997
|
}
|
|
5998
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
5998
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId16) {
|
|
5999
5999
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
6000
6000
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
6001
6001
|
for (const edgeId of inbound) {
|
|
6002
6002
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6003
|
-
rebuildEdge(graph, edge, edge.source,
|
|
6003
|
+
rebuildEdge(graph, edge, edge.source, serviceId16, edgeId);
|
|
6004
6004
|
}
|
|
6005
6005
|
for (const edgeId of outbound) {
|
|
6006
6006
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6007
|
-
rebuildEdge(graph, edge,
|
|
6007
|
+
rebuildEdge(graph, edge, serviceId16, edge.target, edgeId);
|
|
6008
6008
|
}
|
|
6009
6009
|
}
|
|
6010
6010
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -6350,7 +6350,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
|
|
|
6350
6350
|
for (const id of walk10.path) {
|
|
6351
6351
|
const owner = resolveOwningService(graph, id);
|
|
6352
6352
|
if (!owner) continue;
|
|
6353
|
-
const { id:
|
|
6353
|
+
const { id: serviceId16, svc } = owner;
|
|
6354
6354
|
const deps = svc.dependencies ?? {};
|
|
6355
6355
|
for (const pair of candidatePairs) {
|
|
6356
6356
|
const declared = deps[pair.driver];
|
|
@@ -6363,7 +6363,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
|
|
|
6363
6363
|
);
|
|
6364
6364
|
if (!result.compatible) {
|
|
6365
6365
|
return {
|
|
6366
|
-
rootCauseNode:
|
|
6366
|
+
rootCauseNode: serviceId16,
|
|
6367
6367
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
6368
6368
|
...result.minDriverVersion ? {
|
|
6369
6369
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -6378,7 +6378,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
6378
6378
|
for (const id of walk10.path) {
|
|
6379
6379
|
const owner = resolveOwningService(graph, id);
|
|
6380
6380
|
if (!owner) continue;
|
|
6381
|
-
const { id:
|
|
6381
|
+
const { id: serviceId16, svc } = owner;
|
|
6382
6382
|
const deps = svc.dependencies ?? {};
|
|
6383
6383
|
const serviceNodeEngine = svc.nodeEngine;
|
|
6384
6384
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -6387,7 +6387,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
6387
6387
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
6388
6388
|
if (!result.compatible && result.reason) {
|
|
6389
6389
|
return {
|
|
6390
|
-
rootCauseNode:
|
|
6390
|
+
rootCauseNode: serviceId16,
|
|
6391
6391
|
rootCauseReason: result.reason,
|
|
6392
6392
|
...result.requiredNodeVersion ? {
|
|
6393
6393
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -6402,7 +6402,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
6402
6402
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
6403
6403
|
if (!result.compatible && result.reason) {
|
|
6404
6404
|
return {
|
|
6405
|
-
rootCauseNode:
|
|
6405
|
+
rootCauseNode: serviceId16,
|
|
6406
6406
|
rootCauseReason: result.reason,
|
|
6407
6407
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
6408
6408
|
};
|
|
@@ -6507,9 +6507,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
6507
6507
|
function isFailingCallEdge(e) {
|
|
6508
6508
|
return e.type === import_types8.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
6509
6509
|
}
|
|
6510
|
-
function callSourcesForService(graph,
|
|
6511
|
-
const ids = [
|
|
6512
|
-
for (const edgeId of graph.outboundEdges(
|
|
6510
|
+
function callSourcesForService(graph, serviceId16) {
|
|
6511
|
+
const ids = [serviceId16];
|
|
6512
|
+
for (const edgeId of graph.outboundEdges(serviceId16)) {
|
|
6513
6513
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6514
6514
|
if (e.type !== import_types8.EdgeType.CONTAINS) continue;
|
|
6515
6515
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -6533,9 +6533,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
6533
6533
|
}
|
|
6534
6534
|
return id < curId;
|
|
6535
6535
|
}
|
|
6536
|
-
function dominantFailingCall(graph,
|
|
6536
|
+
function dominantFailingCall(graph, serviceId16, visited) {
|
|
6537
6537
|
let best = null;
|
|
6538
|
-
for (const src of callSourcesForService(graph,
|
|
6538
|
+
for (const src of callSourcesForService(graph, serviceId16)) {
|
|
6539
6539
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
6540
6540
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6541
6541
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -6574,9 +6574,9 @@ function staleCallDominates(e, id, curEdge, curId) {
|
|
|
6574
6574
|
if (ev !== cv) return ev > cv;
|
|
6575
6575
|
return id < curId;
|
|
6576
6576
|
}
|
|
6577
|
-
function dominantStaleCall(graph,
|
|
6577
|
+
function dominantStaleCall(graph, serviceId16, visited) {
|
|
6578
6578
|
const bestByCallee = /* @__PURE__ */ new Map();
|
|
6579
|
-
for (const src of callSourcesForService(graph,
|
|
6579
|
+
for (const src of callSourcesForService(graph, serviceId16)) {
|
|
6580
6580
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
6581
6581
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6582
6582
|
if (e.type !== import_types8.EdgeType.CALLS) continue;
|
|
@@ -8431,9 +8431,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
8431
8431
|
"StatefulSet",
|
|
8432
8432
|
"DaemonSet"
|
|
8433
8433
|
]);
|
|
8434
|
-
function addAliases(graph,
|
|
8435
|
-
if (!graph.hasNode(
|
|
8436
|
-
const node = graph.getNodeAttributes(
|
|
8434
|
+
function addAliases(graph, serviceId16, candidates) {
|
|
8435
|
+
if (!graph.hasNode(serviceId16)) return;
|
|
8436
|
+
const node = graph.getNodeAttributes(serviceId16);
|
|
8437
8437
|
if (node.type !== import_types20.NodeType.ServiceNode) return;
|
|
8438
8438
|
const set = new Set(node.aliases ?? []);
|
|
8439
8439
|
for (const c of candidates) {
|
|
@@ -8443,7 +8443,7 @@ function addAliases(graph, serviceId15, candidates) {
|
|
|
8443
8443
|
}
|
|
8444
8444
|
if (set.size === 0) return;
|
|
8445
8445
|
const updated = { ...node, aliases: [...set].sort() };
|
|
8446
|
-
graph.replaceNodeAttributes(
|
|
8446
|
+
graph.replaceNodeAttributes(serviceId16, updated);
|
|
8447
8447
|
}
|
|
8448
8448
|
function indexServicesByName(services) {
|
|
8449
8449
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -8476,12 +8476,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
8476
8476
|
}
|
|
8477
8477
|
if (!compose?.services) return;
|
|
8478
8478
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
8479
|
-
const
|
|
8480
|
-
if (!
|
|
8479
|
+
const serviceId16 = serviceIndex.get(composeName);
|
|
8480
|
+
if (!serviceId16) continue;
|
|
8481
8481
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
8482
8482
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
8483
8483
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
8484
|
-
addAliases(graph,
|
|
8484
|
+
addAliases(graph, serviceId16, aliases);
|
|
8485
8485
|
}
|
|
8486
8486
|
}
|
|
8487
8487
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -16268,6 +16268,639 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
16268
16268
|
return result;
|
|
16269
16269
|
}
|
|
16270
16270
|
|
|
16271
|
+
// src/ask.ts
|
|
16272
|
+
init_cjs_shims();
|
|
16273
|
+
var import_types58 = require("@neat.is/types");
|
|
16274
|
+
var DEFAULT_MAX_NODES = 3;
|
|
16275
|
+
var MAX_FACTS_PER_SECTION = 6;
|
|
16276
|
+
var INTENT_RULES = [
|
|
16277
|
+
{
|
|
16278
|
+
intent: "root-cause",
|
|
16279
|
+
test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
|
|
16280
|
+
},
|
|
16281
|
+
{
|
|
16282
|
+
intent: "blast-radius",
|
|
16283
|
+
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/
|
|
16284
|
+
},
|
|
16285
|
+
{
|
|
16286
|
+
intent: "divergence",
|
|
16287
|
+
test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
|
|
16288
|
+
},
|
|
16289
|
+
{
|
|
16290
|
+
intent: "incidents",
|
|
16291
|
+
test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
|
|
16292
|
+
},
|
|
16293
|
+
{
|
|
16294
|
+
intent: "observed",
|
|
16295
|
+
test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
|
|
16296
|
+
},
|
|
16297
|
+
{
|
|
16298
|
+
intent: "dependencies",
|
|
16299
|
+
test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
|
|
16300
|
+
}
|
|
16301
|
+
];
|
|
16302
|
+
function classifyIntent(question) {
|
|
16303
|
+
const q = question.toLowerCase();
|
|
16304
|
+
for (const rule of INTENT_RULES) {
|
|
16305
|
+
if (rule.test.test(q)) return rule.intent;
|
|
16306
|
+
}
|
|
16307
|
+
return "overview";
|
|
16308
|
+
}
|
|
16309
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
16310
|
+
"the",
|
|
16311
|
+
"a",
|
|
16312
|
+
"an",
|
|
16313
|
+
"is",
|
|
16314
|
+
"are",
|
|
16315
|
+
"was",
|
|
16316
|
+
"were",
|
|
16317
|
+
"be",
|
|
16318
|
+
"been",
|
|
16319
|
+
"being",
|
|
16320
|
+
"of",
|
|
16321
|
+
"to",
|
|
16322
|
+
"in",
|
|
16323
|
+
"on",
|
|
16324
|
+
"at",
|
|
16325
|
+
"by",
|
|
16326
|
+
"for",
|
|
16327
|
+
"with",
|
|
16328
|
+
"and",
|
|
16329
|
+
"or",
|
|
16330
|
+
"but",
|
|
16331
|
+
"if",
|
|
16332
|
+
"as",
|
|
16333
|
+
"this",
|
|
16334
|
+
"that",
|
|
16335
|
+
"these",
|
|
16336
|
+
"those",
|
|
16337
|
+
"it",
|
|
16338
|
+
"its",
|
|
16339
|
+
"do",
|
|
16340
|
+
"does",
|
|
16341
|
+
"did",
|
|
16342
|
+
"has",
|
|
16343
|
+
"have",
|
|
16344
|
+
"had",
|
|
16345
|
+
"what",
|
|
16346
|
+
"which",
|
|
16347
|
+
"who",
|
|
16348
|
+
"whom",
|
|
16349
|
+
"how",
|
|
16350
|
+
"when",
|
|
16351
|
+
"where",
|
|
16352
|
+
"my",
|
|
16353
|
+
"me",
|
|
16354
|
+
"i",
|
|
16355
|
+
"we",
|
|
16356
|
+
"you",
|
|
16357
|
+
"they",
|
|
16358
|
+
"them",
|
|
16359
|
+
"from",
|
|
16360
|
+
"into",
|
|
16361
|
+
"about",
|
|
16362
|
+
"would",
|
|
16363
|
+
"will",
|
|
16364
|
+
"can",
|
|
16365
|
+
"could",
|
|
16366
|
+
"should",
|
|
16367
|
+
"anything",
|
|
16368
|
+
"everything",
|
|
16369
|
+
"something",
|
|
16370
|
+
"all",
|
|
16371
|
+
"any",
|
|
16372
|
+
"some",
|
|
16373
|
+
"no",
|
|
16374
|
+
"not",
|
|
16375
|
+
"so",
|
|
16376
|
+
"up",
|
|
16377
|
+
"out",
|
|
16378
|
+
"over",
|
|
16379
|
+
"get",
|
|
16380
|
+
"got",
|
|
16381
|
+
"show",
|
|
16382
|
+
"tell",
|
|
16383
|
+
"explain",
|
|
16384
|
+
"find",
|
|
16385
|
+
"give",
|
|
16386
|
+
"happen",
|
|
16387
|
+
"happens",
|
|
16388
|
+
"happening",
|
|
16389
|
+
"here",
|
|
16390
|
+
"there",
|
|
16391
|
+
"system",
|
|
16392
|
+
"code",
|
|
16393
|
+
"service",
|
|
16394
|
+
"node",
|
|
16395
|
+
"graph"
|
|
16396
|
+
]);
|
|
16397
|
+
var INTENT_WORDS = /* @__PURE__ */ new Set([
|
|
16398
|
+
"why",
|
|
16399
|
+
"root",
|
|
16400
|
+
"cause",
|
|
16401
|
+
"failing",
|
|
16402
|
+
"fail",
|
|
16403
|
+
"fails",
|
|
16404
|
+
"failed",
|
|
16405
|
+
"breaking",
|
|
16406
|
+
"broke",
|
|
16407
|
+
"broken",
|
|
16408
|
+
"crash",
|
|
16409
|
+
"crashing",
|
|
16410
|
+
"crashed",
|
|
16411
|
+
"throw",
|
|
16412
|
+
"throwing",
|
|
16413
|
+
"throws",
|
|
16414
|
+
"culprit",
|
|
16415
|
+
"blast",
|
|
16416
|
+
"radius",
|
|
16417
|
+
"break",
|
|
16418
|
+
"breaks",
|
|
16419
|
+
"impact",
|
|
16420
|
+
"downstream",
|
|
16421
|
+
"dependent",
|
|
16422
|
+
"dependents",
|
|
16423
|
+
"redeploy",
|
|
16424
|
+
"affect",
|
|
16425
|
+
"affects",
|
|
16426
|
+
"affected",
|
|
16427
|
+
"diverge",
|
|
16428
|
+
"divergence",
|
|
16429
|
+
"divergences",
|
|
16430
|
+
"weird",
|
|
16431
|
+
"mismatch",
|
|
16432
|
+
"drift",
|
|
16433
|
+
"inconsistent",
|
|
16434
|
+
"disagree",
|
|
16435
|
+
"declared",
|
|
16436
|
+
"observed",
|
|
16437
|
+
"incident",
|
|
16438
|
+
"incidents",
|
|
16439
|
+
"recent",
|
|
16440
|
+
"errors",
|
|
16441
|
+
"error",
|
|
16442
|
+
"failures",
|
|
16443
|
+
"failure",
|
|
16444
|
+
"history",
|
|
16445
|
+
"runtime",
|
|
16446
|
+
"production",
|
|
16447
|
+
"prod",
|
|
16448
|
+
"traffic",
|
|
16449
|
+
"actually",
|
|
16450
|
+
"really",
|
|
16451
|
+
"depend",
|
|
16452
|
+
"depends",
|
|
16453
|
+
"dependency",
|
|
16454
|
+
"dependencies",
|
|
16455
|
+
"call",
|
|
16456
|
+
"calls",
|
|
16457
|
+
"use",
|
|
16458
|
+
"uses",
|
|
16459
|
+
"using",
|
|
16460
|
+
"import",
|
|
16461
|
+
"imports",
|
|
16462
|
+
"relies",
|
|
16463
|
+
"rely",
|
|
16464
|
+
"needs",
|
|
16465
|
+
"need",
|
|
16466
|
+
"sync"
|
|
16467
|
+
]);
|
|
16468
|
+
function tokens(input) {
|
|
16469
|
+
const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
16470
|
+
return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
|
|
16471
|
+
}
|
|
16472
|
+
function queryTokens(question) {
|
|
16473
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16474
|
+
const out = [];
|
|
16475
|
+
for (const t of tokens(question)) {
|
|
16476
|
+
if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
|
|
16477
|
+
if (seen.has(t)) continue;
|
|
16478
|
+
seen.add(t);
|
|
16479
|
+
out.push(t);
|
|
16480
|
+
}
|
|
16481
|
+
return out;
|
|
16482
|
+
}
|
|
16483
|
+
function nodeName(node) {
|
|
16484
|
+
return node.name ?? node.id;
|
|
16485
|
+
}
|
|
16486
|
+
function idBody(id) {
|
|
16487
|
+
const colon = id.indexOf(":");
|
|
16488
|
+
return colon >= 0 ? id.slice(colon + 1) : id;
|
|
16489
|
+
}
|
|
16490
|
+
var EMBED_MIN_SCORE = 0.35;
|
|
16491
|
+
async function resolveEntities(graph, question, searchIndex, maxNodes) {
|
|
16492
|
+
const qTokens = queryTokens(question);
|
|
16493
|
+
const normalized = question.toLowerCase();
|
|
16494
|
+
const best = /* @__PURE__ */ new Map();
|
|
16495
|
+
const consider = (cand) => {
|
|
16496
|
+
const cur = best.get(cand.nodeId);
|
|
16497
|
+
if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
|
|
16498
|
+
};
|
|
16499
|
+
graph.forEachNode((id, attrs) => {
|
|
16500
|
+
const node = attrs;
|
|
16501
|
+
if (node.type === import_types58.NodeType.FrontierNode) return;
|
|
16502
|
+
const name = nodeName(node);
|
|
16503
|
+
const body = idBody(id);
|
|
16504
|
+
const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
|
|
16505
|
+
if (labelTokens.size === 0) return;
|
|
16506
|
+
let matched = 0;
|
|
16507
|
+
for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
|
|
16508
|
+
const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
|
|
16509
|
+
if (matched === 0 && !named) return;
|
|
16510
|
+
const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
|
|
16511
|
+
const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
|
|
16512
|
+
const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
|
|
16513
|
+
consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
|
|
16514
|
+
});
|
|
16515
|
+
if (searchIndex) {
|
|
16516
|
+
try {
|
|
16517
|
+
const res = await searchIndex.search(question, 10);
|
|
16518
|
+
if (res.provider !== "substring") {
|
|
16519
|
+
for (const m of res.matches) {
|
|
16520
|
+
if (m.node.type === import_types58.NodeType.FrontierNode) continue;
|
|
16521
|
+
const already = best.get(m.node.id);
|
|
16522
|
+
if (!already && m.score < EMBED_MIN_SCORE) continue;
|
|
16523
|
+
consider({
|
|
16524
|
+
nodeId: m.node.id,
|
|
16525
|
+
label: nodeName(m.node),
|
|
16526
|
+
via: "embedding",
|
|
16527
|
+
score: Math.max(0, Math.min(1, m.score))
|
|
16528
|
+
});
|
|
16529
|
+
}
|
|
16530
|
+
}
|
|
16531
|
+
} catch {
|
|
16532
|
+
}
|
|
16533
|
+
}
|
|
16534
|
+
const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
|
|
16535
|
+
return [...best.values()].sort(
|
|
16536
|
+
(a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
|
|
16537
|
+
).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
|
|
16538
|
+
}
|
|
16539
|
+
function incidentsForNode(nodeId, incidents) {
|
|
16540
|
+
if (!incidents || incidents.length === 0) return [];
|
|
16541
|
+
const svc = nodeId.replace(/^service:/, "");
|
|
16542
|
+
return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
|
|
16543
|
+
}
|
|
16544
|
+
function edgeSignalNote(e) {
|
|
16545
|
+
const bits = [];
|
|
16546
|
+
if (e.signal) {
|
|
16547
|
+
bits.push(`${e.signal.spanCount} spans`);
|
|
16548
|
+
if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
|
|
16549
|
+
if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
|
|
16550
|
+
} else if (e.callCount !== void 0) {
|
|
16551
|
+
bits.push(`${e.callCount} calls`);
|
|
16552
|
+
}
|
|
16553
|
+
return bits.length ? ` (${bits.join(", ")})` : "";
|
|
16554
|
+
}
|
|
16555
|
+
function buildRootCauseSection(graph, node, incidents, now) {
|
|
16556
|
+
const result = getRootCause(graph, node, void 0, incidents, { now });
|
|
16557
|
+
if (!result) return null;
|
|
16558
|
+
const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types58.Provenance.OBSERVED;
|
|
16559
|
+
const facts = [
|
|
16560
|
+
{
|
|
16561
|
+
text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
|
|
16562
|
+
provenance: lastProv,
|
|
16563
|
+
confidence: result.confidence
|
|
16564
|
+
}
|
|
16565
|
+
];
|
|
16566
|
+
const candidates = result.candidates ?? [];
|
|
16567
|
+
for (const c of candidates.slice(0, 3)) {
|
|
16568
|
+
facts.push({
|
|
16569
|
+
text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
|
|
16570
|
+
...c.provenance ? { provenance: c.provenance } : {},
|
|
16571
|
+
confidence: c.confidence
|
|
16572
|
+
});
|
|
16573
|
+
}
|
|
16574
|
+
if (result.traversalPath.length > 1) {
|
|
16575
|
+
facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
|
|
16576
|
+
}
|
|
16577
|
+
if (result.fixRecommendation) {
|
|
16578
|
+
facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
|
|
16579
|
+
}
|
|
16580
|
+
return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
|
|
16581
|
+
}
|
|
16582
|
+
function buildDependenciesSection(graph, node) {
|
|
16583
|
+
const result = getTransitiveDependencies(graph, node, 2);
|
|
16584
|
+
if (result.total === 0) return null;
|
|
16585
|
+
const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16586
|
+
text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
|
|
16587
|
+
// Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
|
|
16588
|
+
// edges, so the tag is on the fact, not asserted for the whole section.
|
|
16589
|
+
provenance: d.provenance
|
|
16590
|
+
}));
|
|
16591
|
+
return { heading: "Dependencies", facts };
|
|
16592
|
+
}
|
|
16593
|
+
function buildObservedSection(graph, node) {
|
|
16594
|
+
const result = getObservedDependencies(graph, node);
|
|
16595
|
+
const facts = [];
|
|
16596
|
+
for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
|
|
16597
|
+
const via = e.source !== node ? ` via ${e.source}` : "";
|
|
16598
|
+
facts.push({
|
|
16599
|
+
text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
|
|
16600
|
+
provenance: e.provenance,
|
|
16601
|
+
confidence: confidenceForEdge(e)
|
|
16602
|
+
});
|
|
16603
|
+
}
|
|
16604
|
+
if (facts.length === 0) {
|
|
16605
|
+
if (result.observed && result.inboundObservedCount > 0) {
|
|
16606
|
+
facts.push({
|
|
16607
|
+
text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
|
|
16608
|
+
provenance: import_types58.Provenance.OBSERVED
|
|
16609
|
+
});
|
|
16610
|
+
} else {
|
|
16611
|
+
return null;
|
|
16612
|
+
}
|
|
16613
|
+
}
|
|
16614
|
+
return { heading: "Runtime dependencies (OBSERVED)", facts };
|
|
16615
|
+
}
|
|
16616
|
+
function buildBlastSection(graph, node) {
|
|
16617
|
+
const result = getBlastRadius(graph, node);
|
|
16618
|
+
if (result.totalAffected === 0) return null;
|
|
16619
|
+
const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
|
|
16620
|
+
text: `${n.nodeId} (distance ${n.distance})`,
|
|
16621
|
+
provenance: n.edgeProvenance,
|
|
16622
|
+
confidence: n.confidence
|
|
16623
|
+
}));
|
|
16624
|
+
return {
|
|
16625
|
+
heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
|
|
16626
|
+
facts
|
|
16627
|
+
};
|
|
16628
|
+
}
|
|
16629
|
+
function buildIncidentsSection(node, incidents) {
|
|
16630
|
+
const relevant = incidentsForNode(node, incidents);
|
|
16631
|
+
if (relevant.length === 0) return null;
|
|
16632
|
+
const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
|
|
16633
|
+
const facts = ordered.map((ev) => ({
|
|
16634
|
+
text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
|
|
16635
|
+
// ErrorEvents are observation records — OBSERVED by definition.
|
|
16636
|
+
provenance: import_types58.Provenance.OBSERVED
|
|
16637
|
+
}));
|
|
16638
|
+
return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
|
|
16639
|
+
}
|
|
16640
|
+
function divergenceLine(d) {
|
|
16641
|
+
if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
|
|
16642
|
+
return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
|
|
16643
|
+
}
|
|
16644
|
+
return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
|
|
16645
|
+
}
|
|
16646
|
+
function buildDivergenceSection(graph, node) {
|
|
16647
|
+
const result = computeDivergences(graph, { node });
|
|
16648
|
+
if (result.totalAffected === 0) return null;
|
|
16649
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16650
|
+
text: divergenceLine(d),
|
|
16651
|
+
confidence: d.confidence
|
|
16652
|
+
// A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
|
|
16653
|
+
// carries no single provenance — the fact leaves it unset.
|
|
16654
|
+
}));
|
|
16655
|
+
return {
|
|
16656
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
16657
|
+
facts
|
|
16658
|
+
};
|
|
16659
|
+
}
|
|
16660
|
+
function buildGlobalDivergenceSection(graph) {
|
|
16661
|
+
const result = computeDivergences(graph);
|
|
16662
|
+
if (result.totalAffected === 0) {
|
|
16663
|
+
return {
|
|
16664
|
+
heading: "Divergences (EXTRACTED vs OBSERVED)",
|
|
16665
|
+
facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
|
|
16666
|
+
};
|
|
16667
|
+
}
|
|
16668
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16669
|
+
text: divergenceLine(d),
|
|
16670
|
+
confidence: d.confidence
|
|
16671
|
+
// Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
|
|
16672
|
+
}));
|
|
16673
|
+
return {
|
|
16674
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
16675
|
+
facts
|
|
16676
|
+
};
|
|
16677
|
+
}
|
|
16678
|
+
function buildGlobalIncidentsSection(incidents) {
|
|
16679
|
+
if (!incidents || incidents.length === 0) {
|
|
16680
|
+
return {
|
|
16681
|
+
heading: "Incidents across the system",
|
|
16682
|
+
facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
|
|
16683
|
+
};
|
|
16684
|
+
}
|
|
16685
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
16686
|
+
for (const ev of incidents) {
|
|
16687
|
+
const key = ev.affectedNode || (0, import_types58.serviceId)(ev.service);
|
|
16688
|
+
const cur = byKey.get(key);
|
|
16689
|
+
if (!cur) {
|
|
16690
|
+
byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
|
|
16691
|
+
} else {
|
|
16692
|
+
cur.count += 1;
|
|
16693
|
+
if (ev.timestamp > cur.latest) {
|
|
16694
|
+
cur.latest = ev.timestamp;
|
|
16695
|
+
cur.sampleMsg = ev.errorMessage;
|
|
16696
|
+
}
|
|
16697
|
+
}
|
|
16698
|
+
}
|
|
16699
|
+
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);
|
|
16700
|
+
const facts = rows.map((r) => ({
|
|
16701
|
+
text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
|
|
16702
|
+
provenance: import_types58.Provenance.OBSERVED
|
|
16703
|
+
}));
|
|
16704
|
+
return {
|
|
16705
|
+
heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
|
|
16706
|
+
facts
|
|
16707
|
+
};
|
|
16708
|
+
}
|
|
16709
|
+
function buildOverviewSections(graph, incidents) {
|
|
16710
|
+
const nodeByType = /* @__PURE__ */ new Map();
|
|
16711
|
+
const services = [];
|
|
16712
|
+
graph.forEachNode((_id, attrs) => {
|
|
16713
|
+
const node = attrs;
|
|
16714
|
+
nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
|
|
16715
|
+
if (node.type === import_types58.NodeType.ServiceNode) services.push(node.id);
|
|
16716
|
+
});
|
|
16717
|
+
const edgeByProv = /* @__PURE__ */ new Map();
|
|
16718
|
+
graph.forEachEdge((_id, attrs) => {
|
|
16719
|
+
const p = attrs.provenance;
|
|
16720
|
+
edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
|
|
16721
|
+
});
|
|
16722
|
+
const sections = [];
|
|
16723
|
+
const count = (t) => nodeByType.get(t) ?? 0;
|
|
16724
|
+
const shapeFacts = [
|
|
16725
|
+
{ text: `${graph.order} nodes, ${graph.size} edges` },
|
|
16726
|
+
{
|
|
16727
|
+
text: `${count(import_types58.NodeType.ServiceNode)} services, ${count(import_types58.NodeType.FileNode)} files, ${count(import_types58.NodeType.SymbolNode)} symbols, ${count(import_types58.NodeType.DatabaseNode)} databases`
|
|
16728
|
+
}
|
|
16729
|
+
];
|
|
16730
|
+
for (const p of [import_types58.Provenance.EXTRACTED, import_types58.Provenance.OBSERVED, import_types58.Provenance.INFERRED, import_types58.Provenance.STALE]) {
|
|
16731
|
+
const n = edgeByProv.get(p) ?? 0;
|
|
16732
|
+
if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
|
|
16733
|
+
}
|
|
16734
|
+
sections.push({ heading: "System shape", facts: shapeFacts });
|
|
16735
|
+
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);
|
|
16736
|
+
if (depCounts.length > 0) {
|
|
16737
|
+
sections.push({
|
|
16738
|
+
heading: "Busiest services (by direct dependencies)",
|
|
16739
|
+
facts: depCounts.map((r) => ({
|
|
16740
|
+
text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
|
|
16741
|
+
}))
|
|
16742
|
+
});
|
|
16743
|
+
}
|
|
16744
|
+
if (incidents && incidents.length > 0) {
|
|
16745
|
+
const incCount = /* @__PURE__ */ new Map();
|
|
16746
|
+
for (const ev of incidents) {
|
|
16747
|
+
const key = ev.affectedNode || (0, import_types58.serviceId)(ev.service);
|
|
16748
|
+
incCount.set(key, (incCount.get(key) ?? 0) + 1);
|
|
16749
|
+
}
|
|
16750
|
+
const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
|
|
16751
|
+
sections.push({
|
|
16752
|
+
heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
|
|
16753
|
+
facts: top.map(([k, n]) => ({
|
|
16754
|
+
text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
|
|
16755
|
+
provenance: import_types58.Provenance.OBSERVED
|
|
16756
|
+
}))
|
|
16757
|
+
});
|
|
16758
|
+
}
|
|
16759
|
+
const div = computeDivergences(graph);
|
|
16760
|
+
sections.push({
|
|
16761
|
+
heading: "Divergences",
|
|
16762
|
+
facts: [
|
|
16763
|
+
{
|
|
16764
|
+
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.`
|
|
16765
|
+
}
|
|
16766
|
+
]
|
|
16767
|
+
});
|
|
16768
|
+
return sections;
|
|
16769
|
+
}
|
|
16770
|
+
function buildGlobalSections(intent, graph, incidents) {
|
|
16771
|
+
switch (intent) {
|
|
16772
|
+
case "divergence":
|
|
16773
|
+
return [buildGlobalDivergenceSection(graph)];
|
|
16774
|
+
case "incidents":
|
|
16775
|
+
return [buildGlobalIncidentsSection(incidents)];
|
|
16776
|
+
case "overview":
|
|
16777
|
+
return buildOverviewSections(graph, incidents);
|
|
16778
|
+
default:
|
|
16779
|
+
return null;
|
|
16780
|
+
}
|
|
16781
|
+
}
|
|
16782
|
+
var SECTION_ORDER = {
|
|
16783
|
+
"root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
|
|
16784
|
+
"blast-radius": ["blast", "dependencies", "observed", "incidents"],
|
|
16785
|
+
dependencies: ["dependencies", "observed", "blast"],
|
|
16786
|
+
observed: ["observed", "dependencies", "incidents"],
|
|
16787
|
+
incidents: ["incidents", "root-cause", "observed"],
|
|
16788
|
+
divergence: ["divergence", "observed", "dependencies", "incidents"],
|
|
16789
|
+
overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
|
|
16790
|
+
};
|
|
16791
|
+
function buildSection(kind, graph, node, incidents, now) {
|
|
16792
|
+
switch (kind) {
|
|
16793
|
+
case "root-cause":
|
|
16794
|
+
return buildRootCauseSection(graph, node, incidents, now);
|
|
16795
|
+
case "dependencies":
|
|
16796
|
+
return buildDependenciesSection(graph, node);
|
|
16797
|
+
case "observed":
|
|
16798
|
+
return buildObservedSection(graph, node);
|
|
16799
|
+
case "blast":
|
|
16800
|
+
return buildBlastSection(graph, node);
|
|
16801
|
+
case "incidents":
|
|
16802
|
+
return buildIncidentsSection(node, incidents);
|
|
16803
|
+
case "divergence":
|
|
16804
|
+
return buildDivergenceSection(graph, node);
|
|
16805
|
+
}
|
|
16806
|
+
}
|
|
16807
|
+
function summarizeGlobal(intent, sections) {
|
|
16808
|
+
const lead = sections[0];
|
|
16809
|
+
switch (intent) {
|
|
16810
|
+
case "divergence": {
|
|
16811
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
16812
|
+
return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
|
|
16813
|
+
}
|
|
16814
|
+
case "incidents": {
|
|
16815
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
16816
|
+
return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
|
|
16817
|
+
}
|
|
16818
|
+
case "overview":
|
|
16819
|
+
default:
|
|
16820
|
+
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.`;
|
|
16821
|
+
}
|
|
16822
|
+
}
|
|
16823
|
+
function summarize(question, intent, matched, primary, sections, scope) {
|
|
16824
|
+
if (scope === "global") {
|
|
16825
|
+
return summarizeGlobal(intent, sections);
|
|
16826
|
+
}
|
|
16827
|
+
if (!primary) {
|
|
16828
|
+
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.`;
|
|
16829
|
+
}
|
|
16830
|
+
const lead = sections[0];
|
|
16831
|
+
const others = matched.slice(1);
|
|
16832
|
+
const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
|
|
16833
|
+
let core;
|
|
16834
|
+
switch (intent) {
|
|
16835
|
+
case "root-cause": {
|
|
16836
|
+
const rc = sections.find((s) => s.heading === "Root cause (navigation)");
|
|
16837
|
+
if (rc) {
|
|
16838
|
+
core = rc.facts[0]?.text ?? "";
|
|
16839
|
+
} else if (lead) {
|
|
16840
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
|
|
16841
|
+
} else {
|
|
16842
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
|
|
16843
|
+
}
|
|
16844
|
+
break;
|
|
16845
|
+
}
|
|
16846
|
+
case "blast-radius":
|
|
16847
|
+
core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
|
|
16848
|
+
break;
|
|
16849
|
+
case "dependencies":
|
|
16850
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
|
|
16851
|
+
break;
|
|
16852
|
+
case "observed":
|
|
16853
|
+
core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
|
|
16854
|
+
break;
|
|
16855
|
+
case "incidents":
|
|
16856
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
|
|
16857
|
+
break;
|
|
16858
|
+
case "divergence":
|
|
16859
|
+
core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
|
|
16860
|
+
break;
|
|
16861
|
+
default:
|
|
16862
|
+
core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
|
|
16863
|
+
}
|
|
16864
|
+
return `${core}${alsoNote}`;
|
|
16865
|
+
}
|
|
16866
|
+
async function askGraph(graph, question, opts = {}) {
|
|
16867
|
+
const now = opts.now ?? Date.now();
|
|
16868
|
+
const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
|
|
16869
|
+
const intent = classifyIntent(question);
|
|
16870
|
+
const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
|
|
16871
|
+
const primary = matched[0]?.nodeId;
|
|
16872
|
+
const sections = [];
|
|
16873
|
+
let scope;
|
|
16874
|
+
if (primary) {
|
|
16875
|
+
scope = "node";
|
|
16876
|
+
for (const kind of SECTION_ORDER[intent]) {
|
|
16877
|
+
const s = buildSection(kind, graph, primary, opts.incidents, now);
|
|
16878
|
+
if (s && s.facts.length > 0) sections.push(s);
|
|
16879
|
+
}
|
|
16880
|
+
} else {
|
|
16881
|
+
const global = buildGlobalSections(intent, graph, opts.incidents);
|
|
16882
|
+
if (global) {
|
|
16883
|
+
scope = "global";
|
|
16884
|
+
for (const s of global) if (s.facts.length > 0) sections.push(s);
|
|
16885
|
+
}
|
|
16886
|
+
}
|
|
16887
|
+
const answer = summarize(question, intent, matched, primary, sections, scope);
|
|
16888
|
+
const provSet = /* @__PURE__ */ new Set();
|
|
16889
|
+
for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
|
|
16890
|
+
const confidence = sections[0]?.facts[0]?.confidence;
|
|
16891
|
+
return import_types58.AskResultSchema.parse({
|
|
16892
|
+
question,
|
|
16893
|
+
intent,
|
|
16894
|
+
matched,
|
|
16895
|
+
...primary ? { primaryNode: primary } : {},
|
|
16896
|
+
...scope ? { scope } : {},
|
|
16897
|
+
sections,
|
|
16898
|
+
answer,
|
|
16899
|
+
...confidence !== void 0 ? { confidence } : {},
|
|
16900
|
+
provenance: [...provSet]
|
|
16901
|
+
});
|
|
16902
|
+
}
|
|
16903
|
+
|
|
16271
16904
|
// src/diff.ts
|
|
16272
16905
|
init_cjs_shims();
|
|
16273
16906
|
var import_node_fs35 = require("fs");
|
|
@@ -16349,7 +16982,7 @@ function canonicalJson(value) {
|
|
|
16349
16982
|
init_cjs_shims();
|
|
16350
16983
|
var import_node_fs36 = require("fs");
|
|
16351
16984
|
var import_node_path70 = __toESM(require("path"), 1);
|
|
16352
|
-
var
|
|
16985
|
+
var import_types59 = require("@neat.is/types");
|
|
16353
16986
|
var SCHEMA_VERSION = 7;
|
|
16354
16987
|
function migrateV1ToV2(payload) {
|
|
16355
16988
|
const nodes = payload.graph.nodes;
|
|
@@ -16373,7 +17006,7 @@ function migrateV5ToV6(payload) {
|
|
|
16373
17006
|
if (Array.isArray(nodes)) {
|
|
16374
17007
|
for (const node of nodes) {
|
|
16375
17008
|
const attrs = node.attributes;
|
|
16376
|
-
if (!attrs || attrs.type !==
|
|
17009
|
+
if (!attrs || attrs.type !== import_types59.NodeType.InfraNode) continue;
|
|
16377
17010
|
if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
|
|
16378
17011
|
if (!Array.isArray(attrs.columns)) attrs.columns = [];
|
|
16379
17012
|
}
|
|
@@ -16389,12 +17022,12 @@ function migrateV2ToV3(payload) {
|
|
|
16389
17022
|
for (const edge of edges) {
|
|
16390
17023
|
const attrs = edge.attributes;
|
|
16391
17024
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
16392
|
-
attrs.provenance =
|
|
17025
|
+
attrs.provenance = import_types59.Provenance.OBSERVED;
|
|
16393
17026
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
16394
17027
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
16395
17028
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
16396
17029
|
if (type && source && target) {
|
|
16397
|
-
const newId = (0,
|
|
17030
|
+
const newId = (0, import_types59.observedEdgeId)(source, target, type);
|
|
16398
17031
|
attrs.id = newId;
|
|
16399
17032
|
if (edge.key) edge.key = newId;
|
|
16400
17033
|
}
|
|
@@ -16552,7 +17185,7 @@ init_cjs_shims();
|
|
|
16552
17185
|
var import_node_fs37 = require("fs");
|
|
16553
17186
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
16554
17187
|
var import_node_path72 = __toESM(require("path"), 1);
|
|
16555
|
-
var
|
|
17188
|
+
var import_types60 = require("@neat.is/types");
|
|
16556
17189
|
function neatHome() {
|
|
16557
17190
|
const override = process.env.NEAT_HOME;
|
|
16558
17191
|
if (override && override.length > 0) return import_node_path72.default.resolve(override);
|
|
@@ -16642,7 +17275,7 @@ async function readRegistry() {
|
|
|
16642
17275
|
throw err;
|
|
16643
17276
|
}
|
|
16644
17277
|
const parsed = JSON.parse(raw);
|
|
16645
|
-
return
|
|
17278
|
+
return import_types60.RegistryFileSchema.parse(parsed);
|
|
16646
17279
|
}
|
|
16647
17280
|
async function getProject(name) {
|
|
16648
17281
|
const reg = await readRegistry();
|
|
@@ -16923,15 +17556,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
16923
17556
|
|
|
16924
17557
|
// src/connectors/index.ts
|
|
16925
17558
|
init_cjs_shims();
|
|
16926
|
-
var
|
|
17559
|
+
var import_types61 = require("@neat.is/types");
|
|
16927
17560
|
var NO_ENV = "unknown";
|
|
16928
17561
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
16929
17562
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
16930
17563
|
const sites = [];
|
|
16931
17564
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
16932
17565
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
16933
|
-
if (edge.provenance !==
|
|
16934
|
-
const parsed = (0,
|
|
17566
|
+
if (edge.provenance !== import_types61.Provenance.EXTRACTED) continue;
|
|
17567
|
+
const parsed = (0, import_types61.parseFileId)(edge.source);
|
|
16935
17568
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
16936
17569
|
const site = { relPath: edge.evidence.file };
|
|
16937
17570
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -16942,7 +17575,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
16942
17575
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
16943
17576
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
16944
17577
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
16945
|
-
if (attrs.type !==
|
|
17578
|
+
if (attrs.type !== import_types61.NodeType.RouteNode || !attrs.path) return void 0;
|
|
16946
17579
|
const site = { relPath: attrs.path };
|
|
16947
17580
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
16948
17581
|
return site;
|
|
@@ -17513,23 +18146,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
17513
18146
|
|
|
17514
18147
|
// src/connectors/supabase/resolve.ts
|
|
17515
18148
|
init_cjs_shims();
|
|
17516
|
-
var
|
|
18149
|
+
var import_types63 = require("@neat.is/types");
|
|
17517
18150
|
function createSupabaseResolveTarget(graph, config) {
|
|
17518
18151
|
return (signal, _ctx) => {
|
|
17519
18152
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
17520
18153
|
return null;
|
|
17521
18154
|
}
|
|
17522
|
-
const subResourceId = (0,
|
|
18155
|
+
const subResourceId = (0, import_types63.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
17523
18156
|
if (graph.hasNode(subResourceId)) {
|
|
17524
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
18157
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17525
18158
|
}
|
|
17526
|
-
const bareResourceId = (0,
|
|
18159
|
+
const bareResourceId = (0, import_types63.infraId)(signal.targetKind, signal.targetName);
|
|
17527
18160
|
if (graph.hasNode(bareResourceId)) {
|
|
17528
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
18161
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17529
18162
|
}
|
|
17530
|
-
const projectLevelId = (0,
|
|
18163
|
+
const projectLevelId = (0, import_types63.infraId)("supabase", config.nodeRef);
|
|
17531
18164
|
if (graph.hasNode(projectLevelId)) {
|
|
17532
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
18165
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17533
18166
|
}
|
|
17534
18167
|
return null;
|
|
17535
18168
|
};
|
|
@@ -17622,7 +18255,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
17622
18255
|
|
|
17623
18256
|
// src/connectors/railway/index.ts
|
|
17624
18257
|
init_cjs_shims();
|
|
17625
|
-
var
|
|
18258
|
+
var import_types67 = require("@neat.is/types");
|
|
17626
18259
|
|
|
17627
18260
|
// src/connectors/railway/client.ts
|
|
17628
18261
|
init_cjs_shims();
|
|
@@ -17773,7 +18406,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
17773
18406
|
const out = [];
|
|
17774
18407
|
graph.forEachNode((_id, attrs) => {
|
|
17775
18408
|
const node = attrs;
|
|
17776
|
-
if (node.type !==
|
|
18409
|
+
if (node.type !== import_types67.NodeType.RouteNode) return;
|
|
17777
18410
|
const route = attrs;
|
|
17778
18411
|
if (route.service !== serviceName) return;
|
|
17779
18412
|
out.push({
|
|
@@ -17877,12 +18510,12 @@ function createRailwayResolveTarget(config) {
|
|
|
17877
18510
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
17878
18511
|
if (!serviceName) return null;
|
|
17879
18512
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
17880
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
18513
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types67.EdgeType.CALLS };
|
|
17881
18514
|
}
|
|
17882
18515
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
17883
18516
|
const peerName = config.serviceNameById[signal.targetName];
|
|
17884
18517
|
if (!peerName) return null;
|
|
17885
|
-
return { targetNodeId: (0,
|
|
18518
|
+
return { targetNodeId: (0, import_types67.serviceId)(peerName), serviceName, edgeType: import_types67.EdgeType.CONNECTS_TO };
|
|
17886
18519
|
}
|
|
17887
18520
|
return null;
|
|
17888
18521
|
};
|
|
@@ -18070,7 +18703,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
18070
18703
|
|
|
18071
18704
|
// src/connectors/firebase/resolve.ts
|
|
18072
18705
|
init_cjs_shims();
|
|
18073
|
-
var
|
|
18706
|
+
var import_types68 = require("@neat.is/types");
|
|
18074
18707
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
18075
18708
|
switch (resourceType) {
|
|
18076
18709
|
case "cloud_function":
|
|
@@ -18085,7 +18718,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
18085
18718
|
const entries = [];
|
|
18086
18719
|
graph.forEachNode((_id, attrs) => {
|
|
18087
18720
|
const node = attrs;
|
|
18088
|
-
if (node.type !==
|
|
18721
|
+
if (node.type !== import_types68.NodeType.RouteNode) return;
|
|
18089
18722
|
const route = attrs;
|
|
18090
18723
|
if (route.service !== serviceName) return;
|
|
18091
18724
|
entries.push({
|
|
@@ -18117,7 +18750,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
18117
18750
|
return {
|
|
18118
18751
|
targetNodeId: match.routeNodeId,
|
|
18119
18752
|
serviceName,
|
|
18120
|
-
edgeType:
|
|
18753
|
+
edgeType: import_types68.EdgeType.CALLS
|
|
18121
18754
|
};
|
|
18122
18755
|
};
|
|
18123
18756
|
}
|
|
@@ -18144,7 +18777,7 @@ init_cjs_shims();
|
|
|
18144
18777
|
|
|
18145
18778
|
// src/connectors/cloudflare/connector.ts
|
|
18146
18779
|
init_cjs_shims();
|
|
18147
|
-
var
|
|
18780
|
+
var import_types70 = require("@neat.is/types");
|
|
18148
18781
|
|
|
18149
18782
|
// src/connectors/cloudflare/client.ts
|
|
18150
18783
|
init_cjs_shims();
|
|
@@ -18308,7 +18941,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
18308
18941
|
graph.forEachNode((id, attrs) => {
|
|
18309
18942
|
if (found) return;
|
|
18310
18943
|
const a = attrs;
|
|
18311
|
-
if (a.type ===
|
|
18944
|
+
if (a.type === import_types70.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
18312
18945
|
found = id;
|
|
18313
18946
|
}
|
|
18314
18947
|
});
|
|
@@ -18320,7 +18953,7 @@ function findMatchingRouteNode(graph, serviceName, method, path76) {
|
|
|
18320
18953
|
graph.forEachNode((id, attrs) => {
|
|
18321
18954
|
if (found) return;
|
|
18322
18955
|
const a = attrs;
|
|
18323
|
-
if (a.type !==
|
|
18956
|
+
if (a.type !== import_types70.NodeType.RouteNode || a.service !== serviceName) return;
|
|
18324
18957
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
18325
18958
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
18326
18959
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -18339,11 +18972,11 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
18339
18972
|
};
|
|
18340
18973
|
const mapping = config.workers?.[scriptName];
|
|
18341
18974
|
if (mapping) {
|
|
18342
|
-
const wholeFileId = (0,
|
|
18975
|
+
const wholeFileId = (0, import_types70.fileId)(mapping.service, mapping.entryFile);
|
|
18343
18976
|
return {
|
|
18344
18977
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
18345
18978
|
serviceName: mapping.service,
|
|
18346
|
-
edgeType:
|
|
18979
|
+
edgeType: import_types70.EdgeType.CALLS
|
|
18347
18980
|
};
|
|
18348
18981
|
}
|
|
18349
18982
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -18352,13 +18985,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
18352
18985
|
return {
|
|
18353
18986
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
18354
18987
|
serviceName: fileNode.service,
|
|
18355
|
-
edgeType:
|
|
18988
|
+
edgeType: import_types70.EdgeType.CALLS
|
|
18356
18989
|
};
|
|
18357
18990
|
}
|
|
18358
18991
|
return {
|
|
18359
|
-
targetNodeId: (0,
|
|
18992
|
+
targetNodeId: (0, import_types70.infraId)("cloudflare-worker", scriptName),
|
|
18360
18993
|
serviceName: scriptName,
|
|
18361
|
-
edgeType:
|
|
18994
|
+
edgeType: import_types70.EdgeType.CALLS,
|
|
18362
18995
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
18363
18996
|
};
|
|
18364
18997
|
};
|
|
@@ -18554,14 +19187,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
18554
19187
|
|
|
18555
19188
|
// src/connectors/neon/resolve.ts
|
|
18556
19189
|
init_cjs_shims();
|
|
18557
|
-
var
|
|
19190
|
+
var import_types74 = require("@neat.is/types");
|
|
18558
19191
|
function createNeonResolveTarget(config) {
|
|
18559
19192
|
return (signal) => {
|
|
18560
19193
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
18561
19194
|
return {
|
|
18562
|
-
targetNodeId: (0,
|
|
19195
|
+
targetNodeId: (0, import_types74.infraId)("sql-table", signal.targetName),
|
|
18563
19196
|
serviceName: config.serviceName,
|
|
18564
|
-
edgeType:
|
|
19197
|
+
edgeType: import_types74.EdgeType.CALLS,
|
|
18565
19198
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
18566
19199
|
};
|
|
18567
19200
|
};
|
|
@@ -18742,14 +19375,14 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
18742
19375
|
|
|
18743
19376
|
// src/connectors/cloud-run/resolve.ts
|
|
18744
19377
|
init_cjs_shims();
|
|
18745
|
-
var
|
|
19378
|
+
var import_types78 = require("@neat.is/types");
|
|
18746
19379
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
18747
19380
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
18748
19381
|
let found = null;
|
|
18749
19382
|
graph.forEachNode((_id, attrs) => {
|
|
18750
19383
|
if (found) return;
|
|
18751
19384
|
const node = attrs;
|
|
18752
|
-
if (node.type !==
|
|
19385
|
+
if (node.type !== import_types78.NodeType.RouteNode) return;
|
|
18753
19386
|
const route = attrs;
|
|
18754
19387
|
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
18755
19388
|
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
@@ -18774,13 +19407,13 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
18774
19407
|
normalizePathTemplate(path76)
|
|
18775
19408
|
);
|
|
18776
19409
|
if (routeNodeId) {
|
|
18777
|
-
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType:
|
|
19410
|
+
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types78.EdgeType.CALLS };
|
|
18778
19411
|
}
|
|
18779
19412
|
}
|
|
18780
19413
|
return {
|
|
18781
|
-
targetNodeId: (0,
|
|
19414
|
+
targetNodeId: (0, import_types78.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
18782
19415
|
serviceName: mappedService ?? gcpServiceName,
|
|
18783
|
-
edgeType:
|
|
19416
|
+
edgeType: import_types78.EdgeType.CALLS,
|
|
18784
19417
|
ensureInfraNode: {
|
|
18785
19418
|
kind: CLOUD_RUN_SERVICE_INFRA_KIND,
|
|
18786
19419
|
name: gcpServiceName,
|
|
@@ -18821,7 +19454,7 @@ function createCloudRunConnector(graph, config = {}) {
|
|
|
18821
19454
|
|
|
18822
19455
|
// src/connectors/render/index.ts
|
|
18823
19456
|
init_cjs_shims();
|
|
18824
|
-
var
|
|
19457
|
+
var import_types81 = require("@neat.is/types");
|
|
18825
19458
|
|
|
18826
19459
|
// src/connectors/render/types.ts
|
|
18827
19460
|
init_cjs_shims();
|
|
@@ -18899,7 +19532,7 @@ function buildRenderRouteIndex(graph, serviceName) {
|
|
|
18899
19532
|
const out = [];
|
|
18900
19533
|
graph.forEachNode((_id, attrs) => {
|
|
18901
19534
|
const node = attrs;
|
|
18902
|
-
if (node.type !==
|
|
19535
|
+
if (node.type !== import_types81.NodeType.RouteNode) return;
|
|
18903
19536
|
const route = attrs;
|
|
18904
19537
|
if (route.service !== serviceName) return;
|
|
18905
19538
|
out.push({
|
|
@@ -18984,7 +19617,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
|
|
|
18984
19617
|
function createRenderResolveTarget(config) {
|
|
18985
19618
|
return (signal) => {
|
|
18986
19619
|
if (signal.targetKind === ROUTE_TARGET_KIND2) {
|
|
18987
|
-
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType:
|
|
19620
|
+
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types81.EdgeType.CALLS };
|
|
18988
19621
|
}
|
|
18989
19622
|
return null;
|
|
18990
19623
|
};
|
|
@@ -19122,21 +19755,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
|
|
|
19122
19755
|
|
|
19123
19756
|
// src/connectors/planetscale/resolve.ts
|
|
19124
19757
|
init_cjs_shims();
|
|
19125
|
-
var
|
|
19758
|
+
var import_types85 = require("@neat.is/types");
|
|
19126
19759
|
var PLANETSCALE_DATABASE_KIND = "planetscale-database";
|
|
19127
19760
|
function createPlanetscaleResolveTarget(graph, config) {
|
|
19128
19761
|
const databaseName = `${config.organization}/${config.database}`;
|
|
19129
19762
|
return (signal, _ctx) => {
|
|
19130
19763
|
if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
19131
|
-
const tableId = (0,
|
|
19764
|
+
const tableId = (0, import_types85.infraId)("sql-table", signal.targetName);
|
|
19132
19765
|
if (graph.hasNode(tableId)) {
|
|
19133
|
-
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType:
|
|
19766
|
+
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types85.EdgeType.CALLS };
|
|
19134
19767
|
}
|
|
19135
|
-
const providerId = (0,
|
|
19768
|
+
const providerId = (0, import_types85.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
|
|
19136
19769
|
return {
|
|
19137
19770
|
targetNodeId: providerId,
|
|
19138
19771
|
serviceName: config.serviceName,
|
|
19139
|
-
edgeType:
|
|
19772
|
+
edgeType: import_types85.EdgeType.CALLS,
|
|
19140
19773
|
ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
|
|
19141
19774
|
};
|
|
19142
19775
|
};
|
|
@@ -19401,7 +20034,7 @@ function mapBuildsToSignals(builds, serviceName) {
|
|
|
19401
20034
|
|
|
19402
20035
|
// src/connectors/eas/resolve.ts
|
|
19403
20036
|
init_cjs_shims();
|
|
19404
|
-
var
|
|
20037
|
+
var import_types90 = require("@neat.is/types");
|
|
19405
20038
|
var NO_ENV2 = "unknown";
|
|
19406
20039
|
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
19407
20040
|
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
@@ -19417,8 +20050,8 @@ function configBasenamesForPhase(phase) {
|
|
|
19417
20050
|
function configNodeService(graph, configNodeId) {
|
|
19418
20051
|
for (const edgeId of graph.inboundEdges(configNodeId)) {
|
|
19419
20052
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
19420
|
-
if (edge.type !==
|
|
19421
|
-
const parsed = (0,
|
|
20053
|
+
if (edge.type !== import_types90.EdgeType.CONFIGURED_BY) continue;
|
|
20054
|
+
const parsed = (0, import_types90.parseFileId)(edge.source);
|
|
19422
20055
|
if (parsed) return parsed.service;
|
|
19423
20056
|
}
|
|
19424
20057
|
return null;
|
|
@@ -19429,7 +20062,7 @@ function findConfigNode(graph, basenames, serviceName) {
|
|
|
19429
20062
|
graph.forEachNode((id, attrs) => {
|
|
19430
20063
|
if (scoped) return;
|
|
19431
20064
|
const node = attrs;
|
|
19432
|
-
if (node.type !==
|
|
20065
|
+
if (node.type !== import_types90.NodeType.ConfigNode) return;
|
|
19433
20066
|
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
19434
20067
|
if (anyMatch === null) anyMatch = id;
|
|
19435
20068
|
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
@@ -19446,13 +20079,13 @@ function createEasResolveTarget(graph) {
|
|
|
19446
20079
|
if (basenames.length > 0) {
|
|
19447
20080
|
const configNodeId = findConfigNode(graph, basenames, serviceName);
|
|
19448
20081
|
if (configNodeId) {
|
|
19449
|
-
return { targetNodeId: configNodeId, serviceName, edgeType:
|
|
20082
|
+
return { targetNodeId: configNodeId, serviceName, edgeType: import_types90.EdgeType.CALLS };
|
|
19450
20083
|
}
|
|
19451
20084
|
}
|
|
19452
20085
|
return {
|
|
19453
20086
|
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
|
|
19454
20087
|
serviceName,
|
|
19455
|
-
edgeType:
|
|
20088
|
+
edgeType: import_types90.EdgeType.CALLS
|
|
19456
20089
|
};
|
|
19457
20090
|
};
|
|
19458
20091
|
}
|
|
@@ -20131,11 +20764,11 @@ function registerRoutes(scope, ctx) {
|
|
|
20131
20764
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
20132
20765
|
const parsed = [];
|
|
20133
20766
|
for (const c of candidates) {
|
|
20134
|
-
const r =
|
|
20767
|
+
const r = import_types93.DivergenceTypeSchema.safeParse(c);
|
|
20135
20768
|
if (!r.success) {
|
|
20136
20769
|
return reply.code(400).send({
|
|
20137
20770
|
error: `unknown divergence type "${c}"`,
|
|
20138
|
-
allowed:
|
|
20771
|
+
allowed: import_types93.DivergenceTypeSchema.options
|
|
20139
20772
|
});
|
|
20140
20773
|
}
|
|
20141
20774
|
parsed.push(r.data);
|
|
@@ -20377,6 +21010,18 @@ function registerRoutes(scope, ctx) {
|
|
|
20377
21010
|
matches: matches.slice(0, safeLimit)
|
|
20378
21011
|
};
|
|
20379
21012
|
});
|
|
21013
|
+
scope.get("/graph/ask", async (req, reply) => {
|
|
21014
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
21015
|
+
if (!proj) return;
|
|
21016
|
+
const question = (req.query.q ?? "").trim();
|
|
21017
|
+
if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
|
|
21018
|
+
const epath = errorsPathFor(proj);
|
|
21019
|
+
const incidents = epath ? await readErrorEvents(epath) : [];
|
|
21020
|
+
return askGraph(proj.graph, question, {
|
|
21021
|
+
...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
|
|
21022
|
+
incidents
|
|
21023
|
+
});
|
|
21024
|
+
});
|
|
20380
21025
|
scope.get(
|
|
20381
21026
|
"/graph/diff",
|
|
20382
21027
|
async (req, reply) => {
|
|
@@ -20477,7 +21122,7 @@ function registerRoutes(scope, ctx) {
|
|
|
20477
21122
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
20478
21123
|
let violations = await log.readAll();
|
|
20479
21124
|
if (req.query.severity) {
|
|
20480
|
-
const sev =
|
|
21125
|
+
const sev = import_types93.PolicySeveritySchema.safeParse(req.query.severity);
|
|
20481
21126
|
if (!sev.success) {
|
|
20482
21127
|
return reply.code(400).send({
|
|
20483
21128
|
error: "invalid severity",
|
|
@@ -20516,7 +21161,7 @@ function registerRoutes(scope, ctx) {
|
|
|
20516
21161
|
scope.post("/policies/check", async (req, reply) => {
|
|
20517
21162
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
20518
21163
|
if (!proj) return;
|
|
20519
|
-
const parsed =
|
|
21164
|
+
const parsed = import_types93.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
20520
21165
|
if (!parsed.success) {
|
|
20521
21166
|
return reply.code(400).send({
|
|
20522
21167
|
error: "invalid /policies/check body",
|