@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/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;
|
|
@@ -7399,6 +7399,53 @@ function detectColumnDrift(node) {
|
|
|
7399
7399
|
function involvesNode(d, nodeId) {
|
|
7400
7400
|
return d.source === nodeId || d.target === nodeId;
|
|
7401
7401
|
}
|
|
7402
|
+
function datastoreEverObserved(graph, nodeId) {
|
|
7403
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
7404
|
+
const n = graph.getNodeAttributes(nodeId);
|
|
7405
|
+
if (n.type === import_types9.NodeType.DatabaseNode) {
|
|
7406
|
+
const via = n.discoveredVia;
|
|
7407
|
+
if (via === "otel" || via === "merged") return true;
|
|
7408
|
+
}
|
|
7409
|
+
for (const edgeId of graph.inboundEdges(nodeId)) {
|
|
7410
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
7411
|
+
if (e.provenance === import_types9.Provenance.OBSERVED || e.provenance === import_types9.Provenance.STALE) return true;
|
|
7412
|
+
}
|
|
7413
|
+
return false;
|
|
7414
|
+
}
|
|
7415
|
+
function serviceHasObservedSameEngineStore(graph, buckets2, serviceId16, engine, excludeTarget) {
|
|
7416
|
+
for (const bucket of buckets2.values()) {
|
|
7417
|
+
if (bucket.type !== import_types9.EdgeType.CONNECTS_TO) continue;
|
|
7418
|
+
if (bucket.source !== serviceId16) continue;
|
|
7419
|
+
if (bucket.target === excludeTarget) continue;
|
|
7420
|
+
if (!graph.hasNode(bucket.target)) continue;
|
|
7421
|
+
const target = graph.getNodeAttributes(bucket.target);
|
|
7422
|
+
if (target.type !== import_types9.NodeType.DatabaseNode) continue;
|
|
7423
|
+
if (target.engine !== engine) continue;
|
|
7424
|
+
if (bucket.observed || datastoreEverObserved(graph, bucket.target)) return true;
|
|
7425
|
+
}
|
|
7426
|
+
return false;
|
|
7427
|
+
}
|
|
7428
|
+
var DEAD_CODE_PROBE_CONFIDENCE = 0.1;
|
|
7429
|
+
function dampenDeadCodeProbes(graph, buckets2, all) {
|
|
7430
|
+
return all.map((d) => {
|
|
7431
|
+
if (d.type !== "missing-observed") return d;
|
|
7432
|
+
if (!d.extracted || d.edgeType !== import_types9.EdgeType.CONNECTS_TO) return d;
|
|
7433
|
+
if (!graph.hasNode(d.target)) return d;
|
|
7434
|
+
const target = graph.getNodeAttributes(d.target);
|
|
7435
|
+
if (target.type !== import_types9.NodeType.DatabaseNode) return d;
|
|
7436
|
+
if (d.extracted.evidence?.hostSource !== "literal") return d;
|
|
7437
|
+
if (datastoreEverObserved(graph, d.target)) return d;
|
|
7438
|
+
const engine = target.engine;
|
|
7439
|
+
if (!serviceHasObservedSameEngineStore(graph, buckets2, d.source, engine, d.target)) return d;
|
|
7440
|
+
const host = target.host ?? target.name;
|
|
7441
|
+
return {
|
|
7442
|
+
...d,
|
|
7443
|
+
confidence: Math.min(d.confidence, DEAD_CODE_PROBE_CONFIDENCE),
|
|
7444
|
+
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.`,
|
|
7445
|
+
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."
|
|
7446
|
+
};
|
|
7447
|
+
});
|
|
7448
|
+
}
|
|
7402
7449
|
function suppressHostMismatchHalves(all) {
|
|
7403
7450
|
const observedHalf = /* @__PURE__ */ new Set();
|
|
7404
7451
|
const declaredHalf = /* @__PURE__ */ new Set();
|
|
@@ -7435,7 +7482,8 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7435
7482
|
}
|
|
7436
7483
|
});
|
|
7437
7484
|
const reconciled = suppressHostMismatchHalves(all);
|
|
7438
|
-
|
|
7485
|
+
const dampened = dampenDeadCodeProbes(graph, buckets2, reconciled);
|
|
7486
|
+
let filtered = dampened;
|
|
7439
7487
|
if (opts.type) {
|
|
7440
7488
|
const allowed = opts.type;
|
|
7441
7489
|
filtered = filtered.filter((d) => allowed.has(d.type));
|
|
@@ -8431,9 +8479,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
8431
8479
|
"StatefulSet",
|
|
8432
8480
|
"DaemonSet"
|
|
8433
8481
|
]);
|
|
8434
|
-
function addAliases(graph,
|
|
8435
|
-
if (!graph.hasNode(
|
|
8436
|
-
const node = graph.getNodeAttributes(
|
|
8482
|
+
function addAliases(graph, serviceId16, candidates) {
|
|
8483
|
+
if (!graph.hasNode(serviceId16)) return;
|
|
8484
|
+
const node = graph.getNodeAttributes(serviceId16);
|
|
8437
8485
|
if (node.type !== import_types20.NodeType.ServiceNode) return;
|
|
8438
8486
|
const set = new Set(node.aliases ?? []);
|
|
8439
8487
|
for (const c of candidates) {
|
|
@@ -8443,7 +8491,7 @@ function addAliases(graph, serviceId15, candidates) {
|
|
|
8443
8491
|
}
|
|
8444
8492
|
if (set.size === 0) return;
|
|
8445
8493
|
const updated = { ...node, aliases: [...set].sort() };
|
|
8446
|
-
graph.replaceNodeAttributes(
|
|
8494
|
+
graph.replaceNodeAttributes(serviceId16, updated);
|
|
8447
8495
|
}
|
|
8448
8496
|
function indexServicesByName(services) {
|
|
8449
8497
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -8476,12 +8524,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
8476
8524
|
}
|
|
8477
8525
|
if (!compose?.services) return;
|
|
8478
8526
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
8479
|
-
const
|
|
8480
|
-
if (!
|
|
8527
|
+
const serviceId16 = serviceIndex.get(composeName);
|
|
8528
|
+
if (!serviceId16) continue;
|
|
8481
8529
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
8482
8530
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
8483
8531
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
8484
|
-
addAliases(graph,
|
|
8532
|
+
addAliases(graph, serviceId16, aliases);
|
|
8485
8533
|
}
|
|
8486
8534
|
}
|
|
8487
8535
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -10471,12 +10519,12 @@ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
|
|
|
10471
10519
|
const value = await interpolateEnvRefs(raw, serviceDir);
|
|
10472
10520
|
if (!looksLike(value)) continue;
|
|
10473
10521
|
const parsed = parse11(value);
|
|
10474
|
-
if (parsed) out.push(parsed);
|
|
10522
|
+
if (parsed) out.push({ ...parsed, hostSource: "config" });
|
|
10475
10523
|
}
|
|
10476
10524
|
for (const lit of literals) {
|
|
10477
10525
|
if (!looksLike(lit)) continue;
|
|
10478
10526
|
const parsed = parse11(lit);
|
|
10479
|
-
if (parsed) out.push(parsed);
|
|
10527
|
+
if (parsed) out.push({ ...parsed, hostSource: "literal" });
|
|
10480
10528
|
}
|
|
10481
10529
|
return out;
|
|
10482
10530
|
}
|
|
@@ -10745,7 +10793,10 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
10745
10793
|
type: import_types24.EdgeType.CONNECTS_TO,
|
|
10746
10794
|
provenance: import_types24.Provenance.EXTRACTED,
|
|
10747
10795
|
confidence: (0, import_types24.confidenceForExtracted)("structural"),
|
|
10748
|
-
|
|
10796
|
+
// Carry how the host was recovered (ADR-213) so the divergence ranker
|
|
10797
|
+
// can tell a real declared store from a hardcoded fault-injection /
|
|
10798
|
+
// flag-gated probe. Only set when the parser distinguished the two.
|
|
10799
|
+
evidence: config.hostSource ? { file: evidenceFile, hostSource: config.hostSource } : { file: evidenceFile }
|
|
10749
10800
|
};
|
|
10750
10801
|
if (!graph.hasEdge(edge.id)) {
|
|
10751
10802
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -16268,6 +16319,639 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
16268
16319
|
return result;
|
|
16269
16320
|
}
|
|
16270
16321
|
|
|
16322
|
+
// src/ask.ts
|
|
16323
|
+
init_cjs_shims();
|
|
16324
|
+
var import_types58 = require("@neat.is/types");
|
|
16325
|
+
var DEFAULT_MAX_NODES = 3;
|
|
16326
|
+
var MAX_FACTS_PER_SECTION = 6;
|
|
16327
|
+
var INTENT_RULES = [
|
|
16328
|
+
{
|
|
16329
|
+
intent: "root-cause",
|
|
16330
|
+
test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
|
|
16331
|
+
},
|
|
16332
|
+
{
|
|
16333
|
+
intent: "blast-radius",
|
|
16334
|
+
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/
|
|
16335
|
+
},
|
|
16336
|
+
{
|
|
16337
|
+
intent: "divergence",
|
|
16338
|
+
test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
|
|
16339
|
+
},
|
|
16340
|
+
{
|
|
16341
|
+
intent: "incidents",
|
|
16342
|
+
test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
|
|
16343
|
+
},
|
|
16344
|
+
{
|
|
16345
|
+
intent: "observed",
|
|
16346
|
+
test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
|
|
16347
|
+
},
|
|
16348
|
+
{
|
|
16349
|
+
intent: "dependencies",
|
|
16350
|
+
test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
|
|
16351
|
+
}
|
|
16352
|
+
];
|
|
16353
|
+
function classifyIntent(question) {
|
|
16354
|
+
const q = question.toLowerCase();
|
|
16355
|
+
for (const rule of INTENT_RULES) {
|
|
16356
|
+
if (rule.test.test(q)) return rule.intent;
|
|
16357
|
+
}
|
|
16358
|
+
return "overview";
|
|
16359
|
+
}
|
|
16360
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
16361
|
+
"the",
|
|
16362
|
+
"a",
|
|
16363
|
+
"an",
|
|
16364
|
+
"is",
|
|
16365
|
+
"are",
|
|
16366
|
+
"was",
|
|
16367
|
+
"were",
|
|
16368
|
+
"be",
|
|
16369
|
+
"been",
|
|
16370
|
+
"being",
|
|
16371
|
+
"of",
|
|
16372
|
+
"to",
|
|
16373
|
+
"in",
|
|
16374
|
+
"on",
|
|
16375
|
+
"at",
|
|
16376
|
+
"by",
|
|
16377
|
+
"for",
|
|
16378
|
+
"with",
|
|
16379
|
+
"and",
|
|
16380
|
+
"or",
|
|
16381
|
+
"but",
|
|
16382
|
+
"if",
|
|
16383
|
+
"as",
|
|
16384
|
+
"this",
|
|
16385
|
+
"that",
|
|
16386
|
+
"these",
|
|
16387
|
+
"those",
|
|
16388
|
+
"it",
|
|
16389
|
+
"its",
|
|
16390
|
+
"do",
|
|
16391
|
+
"does",
|
|
16392
|
+
"did",
|
|
16393
|
+
"has",
|
|
16394
|
+
"have",
|
|
16395
|
+
"had",
|
|
16396
|
+
"what",
|
|
16397
|
+
"which",
|
|
16398
|
+
"who",
|
|
16399
|
+
"whom",
|
|
16400
|
+
"how",
|
|
16401
|
+
"when",
|
|
16402
|
+
"where",
|
|
16403
|
+
"my",
|
|
16404
|
+
"me",
|
|
16405
|
+
"i",
|
|
16406
|
+
"we",
|
|
16407
|
+
"you",
|
|
16408
|
+
"they",
|
|
16409
|
+
"them",
|
|
16410
|
+
"from",
|
|
16411
|
+
"into",
|
|
16412
|
+
"about",
|
|
16413
|
+
"would",
|
|
16414
|
+
"will",
|
|
16415
|
+
"can",
|
|
16416
|
+
"could",
|
|
16417
|
+
"should",
|
|
16418
|
+
"anything",
|
|
16419
|
+
"everything",
|
|
16420
|
+
"something",
|
|
16421
|
+
"all",
|
|
16422
|
+
"any",
|
|
16423
|
+
"some",
|
|
16424
|
+
"no",
|
|
16425
|
+
"not",
|
|
16426
|
+
"so",
|
|
16427
|
+
"up",
|
|
16428
|
+
"out",
|
|
16429
|
+
"over",
|
|
16430
|
+
"get",
|
|
16431
|
+
"got",
|
|
16432
|
+
"show",
|
|
16433
|
+
"tell",
|
|
16434
|
+
"explain",
|
|
16435
|
+
"find",
|
|
16436
|
+
"give",
|
|
16437
|
+
"happen",
|
|
16438
|
+
"happens",
|
|
16439
|
+
"happening",
|
|
16440
|
+
"here",
|
|
16441
|
+
"there",
|
|
16442
|
+
"system",
|
|
16443
|
+
"code",
|
|
16444
|
+
"service",
|
|
16445
|
+
"node",
|
|
16446
|
+
"graph"
|
|
16447
|
+
]);
|
|
16448
|
+
var INTENT_WORDS = /* @__PURE__ */ new Set([
|
|
16449
|
+
"why",
|
|
16450
|
+
"root",
|
|
16451
|
+
"cause",
|
|
16452
|
+
"failing",
|
|
16453
|
+
"fail",
|
|
16454
|
+
"fails",
|
|
16455
|
+
"failed",
|
|
16456
|
+
"breaking",
|
|
16457
|
+
"broke",
|
|
16458
|
+
"broken",
|
|
16459
|
+
"crash",
|
|
16460
|
+
"crashing",
|
|
16461
|
+
"crashed",
|
|
16462
|
+
"throw",
|
|
16463
|
+
"throwing",
|
|
16464
|
+
"throws",
|
|
16465
|
+
"culprit",
|
|
16466
|
+
"blast",
|
|
16467
|
+
"radius",
|
|
16468
|
+
"break",
|
|
16469
|
+
"breaks",
|
|
16470
|
+
"impact",
|
|
16471
|
+
"downstream",
|
|
16472
|
+
"dependent",
|
|
16473
|
+
"dependents",
|
|
16474
|
+
"redeploy",
|
|
16475
|
+
"affect",
|
|
16476
|
+
"affects",
|
|
16477
|
+
"affected",
|
|
16478
|
+
"diverge",
|
|
16479
|
+
"divergence",
|
|
16480
|
+
"divergences",
|
|
16481
|
+
"weird",
|
|
16482
|
+
"mismatch",
|
|
16483
|
+
"drift",
|
|
16484
|
+
"inconsistent",
|
|
16485
|
+
"disagree",
|
|
16486
|
+
"declared",
|
|
16487
|
+
"observed",
|
|
16488
|
+
"incident",
|
|
16489
|
+
"incidents",
|
|
16490
|
+
"recent",
|
|
16491
|
+
"errors",
|
|
16492
|
+
"error",
|
|
16493
|
+
"failures",
|
|
16494
|
+
"failure",
|
|
16495
|
+
"history",
|
|
16496
|
+
"runtime",
|
|
16497
|
+
"production",
|
|
16498
|
+
"prod",
|
|
16499
|
+
"traffic",
|
|
16500
|
+
"actually",
|
|
16501
|
+
"really",
|
|
16502
|
+
"depend",
|
|
16503
|
+
"depends",
|
|
16504
|
+
"dependency",
|
|
16505
|
+
"dependencies",
|
|
16506
|
+
"call",
|
|
16507
|
+
"calls",
|
|
16508
|
+
"use",
|
|
16509
|
+
"uses",
|
|
16510
|
+
"using",
|
|
16511
|
+
"import",
|
|
16512
|
+
"imports",
|
|
16513
|
+
"relies",
|
|
16514
|
+
"rely",
|
|
16515
|
+
"needs",
|
|
16516
|
+
"need",
|
|
16517
|
+
"sync"
|
|
16518
|
+
]);
|
|
16519
|
+
function tokens(input) {
|
|
16520
|
+
const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
16521
|
+
return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
|
|
16522
|
+
}
|
|
16523
|
+
function queryTokens(question) {
|
|
16524
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16525
|
+
const out = [];
|
|
16526
|
+
for (const t of tokens(question)) {
|
|
16527
|
+
if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
|
|
16528
|
+
if (seen.has(t)) continue;
|
|
16529
|
+
seen.add(t);
|
|
16530
|
+
out.push(t);
|
|
16531
|
+
}
|
|
16532
|
+
return out;
|
|
16533
|
+
}
|
|
16534
|
+
function nodeName(node) {
|
|
16535
|
+
return node.name ?? node.id;
|
|
16536
|
+
}
|
|
16537
|
+
function idBody(id) {
|
|
16538
|
+
const colon = id.indexOf(":");
|
|
16539
|
+
return colon >= 0 ? id.slice(colon + 1) : id;
|
|
16540
|
+
}
|
|
16541
|
+
var EMBED_MIN_SCORE = 0.35;
|
|
16542
|
+
async function resolveEntities(graph, question, searchIndex, maxNodes) {
|
|
16543
|
+
const qTokens = queryTokens(question);
|
|
16544
|
+
const normalized = question.toLowerCase();
|
|
16545
|
+
const best = /* @__PURE__ */ new Map();
|
|
16546
|
+
const consider = (cand) => {
|
|
16547
|
+
const cur = best.get(cand.nodeId);
|
|
16548
|
+
if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
|
|
16549
|
+
};
|
|
16550
|
+
graph.forEachNode((id, attrs) => {
|
|
16551
|
+
const node = attrs;
|
|
16552
|
+
if (node.type === import_types58.NodeType.FrontierNode) return;
|
|
16553
|
+
const name = nodeName(node);
|
|
16554
|
+
const body = idBody(id);
|
|
16555
|
+
const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
|
|
16556
|
+
if (labelTokens.size === 0) return;
|
|
16557
|
+
let matched = 0;
|
|
16558
|
+
for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
|
|
16559
|
+
const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
|
|
16560
|
+
if (matched === 0 && !named) return;
|
|
16561
|
+
const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
|
|
16562
|
+
const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
|
|
16563
|
+
const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
|
|
16564
|
+
consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
|
|
16565
|
+
});
|
|
16566
|
+
if (searchIndex) {
|
|
16567
|
+
try {
|
|
16568
|
+
const res = await searchIndex.search(question, 10);
|
|
16569
|
+
if (res.provider !== "substring") {
|
|
16570
|
+
for (const m of res.matches) {
|
|
16571
|
+
if (m.node.type === import_types58.NodeType.FrontierNode) continue;
|
|
16572
|
+
const already = best.get(m.node.id);
|
|
16573
|
+
if (!already && m.score < EMBED_MIN_SCORE) continue;
|
|
16574
|
+
consider({
|
|
16575
|
+
nodeId: m.node.id,
|
|
16576
|
+
label: nodeName(m.node),
|
|
16577
|
+
via: "embedding",
|
|
16578
|
+
score: Math.max(0, Math.min(1, m.score))
|
|
16579
|
+
});
|
|
16580
|
+
}
|
|
16581
|
+
}
|
|
16582
|
+
} catch {
|
|
16583
|
+
}
|
|
16584
|
+
}
|
|
16585
|
+
const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
|
|
16586
|
+
return [...best.values()].sort(
|
|
16587
|
+
(a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
|
|
16588
|
+
).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
|
|
16589
|
+
}
|
|
16590
|
+
function incidentsForNode(nodeId, incidents) {
|
|
16591
|
+
if (!incidents || incidents.length === 0) return [];
|
|
16592
|
+
const svc = nodeId.replace(/^service:/, "");
|
|
16593
|
+
return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
|
|
16594
|
+
}
|
|
16595
|
+
function edgeSignalNote(e) {
|
|
16596
|
+
const bits = [];
|
|
16597
|
+
if (e.signal) {
|
|
16598
|
+
bits.push(`${e.signal.spanCount} spans`);
|
|
16599
|
+
if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
|
|
16600
|
+
if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
|
|
16601
|
+
} else if (e.callCount !== void 0) {
|
|
16602
|
+
bits.push(`${e.callCount} calls`);
|
|
16603
|
+
}
|
|
16604
|
+
return bits.length ? ` (${bits.join(", ")})` : "";
|
|
16605
|
+
}
|
|
16606
|
+
function buildRootCauseSection(graph, node, incidents, now) {
|
|
16607
|
+
const result = getRootCause(graph, node, void 0, incidents, { now });
|
|
16608
|
+
if (!result) return null;
|
|
16609
|
+
const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types58.Provenance.OBSERVED;
|
|
16610
|
+
const facts = [
|
|
16611
|
+
{
|
|
16612
|
+
text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
|
|
16613
|
+
provenance: lastProv,
|
|
16614
|
+
confidence: result.confidence
|
|
16615
|
+
}
|
|
16616
|
+
];
|
|
16617
|
+
const candidates = result.candidates ?? [];
|
|
16618
|
+
for (const c of candidates.slice(0, 3)) {
|
|
16619
|
+
facts.push({
|
|
16620
|
+
text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
|
|
16621
|
+
...c.provenance ? { provenance: c.provenance } : {},
|
|
16622
|
+
confidence: c.confidence
|
|
16623
|
+
});
|
|
16624
|
+
}
|
|
16625
|
+
if (result.traversalPath.length > 1) {
|
|
16626
|
+
facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
|
|
16627
|
+
}
|
|
16628
|
+
if (result.fixRecommendation) {
|
|
16629
|
+
facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
|
|
16630
|
+
}
|
|
16631
|
+
return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
|
|
16632
|
+
}
|
|
16633
|
+
function buildDependenciesSection(graph, node) {
|
|
16634
|
+
const result = getTransitiveDependencies(graph, node, 2);
|
|
16635
|
+
if (result.total === 0) return null;
|
|
16636
|
+
const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16637
|
+
text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
|
|
16638
|
+
// Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
|
|
16639
|
+
// edges, so the tag is on the fact, not asserted for the whole section.
|
|
16640
|
+
provenance: d.provenance
|
|
16641
|
+
}));
|
|
16642
|
+
return { heading: "Dependencies", facts };
|
|
16643
|
+
}
|
|
16644
|
+
function buildObservedSection(graph, node) {
|
|
16645
|
+
const result = getObservedDependencies(graph, node);
|
|
16646
|
+
const facts = [];
|
|
16647
|
+
for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
|
|
16648
|
+
const via = e.source !== node ? ` via ${e.source}` : "";
|
|
16649
|
+
facts.push({
|
|
16650
|
+
text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
|
|
16651
|
+
provenance: e.provenance,
|
|
16652
|
+
confidence: confidenceForEdge(e)
|
|
16653
|
+
});
|
|
16654
|
+
}
|
|
16655
|
+
if (facts.length === 0) {
|
|
16656
|
+
if (result.observed && result.inboundObservedCount > 0) {
|
|
16657
|
+
facts.push({
|
|
16658
|
+
text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
|
|
16659
|
+
provenance: import_types58.Provenance.OBSERVED
|
|
16660
|
+
});
|
|
16661
|
+
} else {
|
|
16662
|
+
return null;
|
|
16663
|
+
}
|
|
16664
|
+
}
|
|
16665
|
+
return { heading: "Runtime dependencies (OBSERVED)", facts };
|
|
16666
|
+
}
|
|
16667
|
+
function buildBlastSection(graph, node) {
|
|
16668
|
+
const result = getBlastRadius(graph, node);
|
|
16669
|
+
if (result.totalAffected === 0) return null;
|
|
16670
|
+
const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
|
|
16671
|
+
text: `${n.nodeId} (distance ${n.distance})`,
|
|
16672
|
+
provenance: n.edgeProvenance,
|
|
16673
|
+
confidence: n.confidence
|
|
16674
|
+
}));
|
|
16675
|
+
return {
|
|
16676
|
+
heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
|
|
16677
|
+
facts
|
|
16678
|
+
};
|
|
16679
|
+
}
|
|
16680
|
+
function buildIncidentsSection(node, incidents) {
|
|
16681
|
+
const relevant = incidentsForNode(node, incidents);
|
|
16682
|
+
if (relevant.length === 0) return null;
|
|
16683
|
+
const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
|
|
16684
|
+
const facts = ordered.map((ev) => ({
|
|
16685
|
+
text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
|
|
16686
|
+
// ErrorEvents are observation records — OBSERVED by definition.
|
|
16687
|
+
provenance: import_types58.Provenance.OBSERVED
|
|
16688
|
+
}));
|
|
16689
|
+
return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
|
|
16690
|
+
}
|
|
16691
|
+
function divergenceLine(d) {
|
|
16692
|
+
if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
|
|
16693
|
+
return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
|
|
16694
|
+
}
|
|
16695
|
+
return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
|
|
16696
|
+
}
|
|
16697
|
+
function buildDivergenceSection(graph, node) {
|
|
16698
|
+
const result = computeDivergences(graph, { node });
|
|
16699
|
+
if (result.totalAffected === 0) return null;
|
|
16700
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16701
|
+
text: divergenceLine(d),
|
|
16702
|
+
confidence: d.confidence
|
|
16703
|
+
// A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
|
|
16704
|
+
// carries no single provenance — the fact leaves it unset.
|
|
16705
|
+
}));
|
|
16706
|
+
return {
|
|
16707
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
16708
|
+
facts
|
|
16709
|
+
};
|
|
16710
|
+
}
|
|
16711
|
+
function buildGlobalDivergenceSection(graph) {
|
|
16712
|
+
const result = computeDivergences(graph);
|
|
16713
|
+
if (result.totalAffected === 0) {
|
|
16714
|
+
return {
|
|
16715
|
+
heading: "Divergences (EXTRACTED vs OBSERVED)",
|
|
16716
|
+
facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
|
|
16717
|
+
};
|
|
16718
|
+
}
|
|
16719
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16720
|
+
text: divergenceLine(d),
|
|
16721
|
+
confidence: d.confidence
|
|
16722
|
+
// Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
|
|
16723
|
+
}));
|
|
16724
|
+
return {
|
|
16725
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
16726
|
+
facts
|
|
16727
|
+
};
|
|
16728
|
+
}
|
|
16729
|
+
function buildGlobalIncidentsSection(incidents) {
|
|
16730
|
+
if (!incidents || incidents.length === 0) {
|
|
16731
|
+
return {
|
|
16732
|
+
heading: "Incidents across the system",
|
|
16733
|
+
facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
|
|
16734
|
+
};
|
|
16735
|
+
}
|
|
16736
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
16737
|
+
for (const ev of incidents) {
|
|
16738
|
+
const key = ev.affectedNode || (0, import_types58.serviceId)(ev.service);
|
|
16739
|
+
const cur = byKey.get(key);
|
|
16740
|
+
if (!cur) {
|
|
16741
|
+
byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
|
|
16742
|
+
} else {
|
|
16743
|
+
cur.count += 1;
|
|
16744
|
+
if (ev.timestamp > cur.latest) {
|
|
16745
|
+
cur.latest = ev.timestamp;
|
|
16746
|
+
cur.sampleMsg = ev.errorMessage;
|
|
16747
|
+
}
|
|
16748
|
+
}
|
|
16749
|
+
}
|
|
16750
|
+
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);
|
|
16751
|
+
const facts = rows.map((r) => ({
|
|
16752
|
+
text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
|
|
16753
|
+
provenance: import_types58.Provenance.OBSERVED
|
|
16754
|
+
}));
|
|
16755
|
+
return {
|
|
16756
|
+
heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
|
|
16757
|
+
facts
|
|
16758
|
+
};
|
|
16759
|
+
}
|
|
16760
|
+
function buildOverviewSections(graph, incidents) {
|
|
16761
|
+
const nodeByType = /* @__PURE__ */ new Map();
|
|
16762
|
+
const services = [];
|
|
16763
|
+
graph.forEachNode((_id, attrs) => {
|
|
16764
|
+
const node = attrs;
|
|
16765
|
+
nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
|
|
16766
|
+
if (node.type === import_types58.NodeType.ServiceNode) services.push(node.id);
|
|
16767
|
+
});
|
|
16768
|
+
const edgeByProv = /* @__PURE__ */ new Map();
|
|
16769
|
+
graph.forEachEdge((_id, attrs) => {
|
|
16770
|
+
const p = attrs.provenance;
|
|
16771
|
+
edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
|
|
16772
|
+
});
|
|
16773
|
+
const sections = [];
|
|
16774
|
+
const count = (t) => nodeByType.get(t) ?? 0;
|
|
16775
|
+
const shapeFacts = [
|
|
16776
|
+
{ text: `${graph.order} nodes, ${graph.size} edges` },
|
|
16777
|
+
{
|
|
16778
|
+
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`
|
|
16779
|
+
}
|
|
16780
|
+
];
|
|
16781
|
+
for (const p of [import_types58.Provenance.EXTRACTED, import_types58.Provenance.OBSERVED, import_types58.Provenance.INFERRED, import_types58.Provenance.STALE]) {
|
|
16782
|
+
const n = edgeByProv.get(p) ?? 0;
|
|
16783
|
+
if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
|
|
16784
|
+
}
|
|
16785
|
+
sections.push({ heading: "System shape", facts: shapeFacts });
|
|
16786
|
+
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);
|
|
16787
|
+
if (depCounts.length > 0) {
|
|
16788
|
+
sections.push({
|
|
16789
|
+
heading: "Busiest services (by direct dependencies)",
|
|
16790
|
+
facts: depCounts.map((r) => ({
|
|
16791
|
+
text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
|
|
16792
|
+
}))
|
|
16793
|
+
});
|
|
16794
|
+
}
|
|
16795
|
+
if (incidents && incidents.length > 0) {
|
|
16796
|
+
const incCount = /* @__PURE__ */ new Map();
|
|
16797
|
+
for (const ev of incidents) {
|
|
16798
|
+
const key = ev.affectedNode || (0, import_types58.serviceId)(ev.service);
|
|
16799
|
+
incCount.set(key, (incCount.get(key) ?? 0) + 1);
|
|
16800
|
+
}
|
|
16801
|
+
const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
|
|
16802
|
+
sections.push({
|
|
16803
|
+
heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
|
|
16804
|
+
facts: top.map(([k, n]) => ({
|
|
16805
|
+
text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
|
|
16806
|
+
provenance: import_types58.Provenance.OBSERVED
|
|
16807
|
+
}))
|
|
16808
|
+
});
|
|
16809
|
+
}
|
|
16810
|
+
const div = computeDivergences(graph);
|
|
16811
|
+
sections.push({
|
|
16812
|
+
heading: "Divergences",
|
|
16813
|
+
facts: [
|
|
16814
|
+
{
|
|
16815
|
+
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.`
|
|
16816
|
+
}
|
|
16817
|
+
]
|
|
16818
|
+
});
|
|
16819
|
+
return sections;
|
|
16820
|
+
}
|
|
16821
|
+
function buildGlobalSections(intent, graph, incidents) {
|
|
16822
|
+
switch (intent) {
|
|
16823
|
+
case "divergence":
|
|
16824
|
+
return [buildGlobalDivergenceSection(graph)];
|
|
16825
|
+
case "incidents":
|
|
16826
|
+
return [buildGlobalIncidentsSection(incidents)];
|
|
16827
|
+
case "overview":
|
|
16828
|
+
return buildOverviewSections(graph, incidents);
|
|
16829
|
+
default:
|
|
16830
|
+
return null;
|
|
16831
|
+
}
|
|
16832
|
+
}
|
|
16833
|
+
var SECTION_ORDER = {
|
|
16834
|
+
"root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
|
|
16835
|
+
"blast-radius": ["blast", "dependencies", "observed", "incidents"],
|
|
16836
|
+
dependencies: ["dependencies", "observed", "blast"],
|
|
16837
|
+
observed: ["observed", "dependencies", "incidents"],
|
|
16838
|
+
incidents: ["incidents", "root-cause", "observed"],
|
|
16839
|
+
divergence: ["divergence", "observed", "dependencies", "incidents"],
|
|
16840
|
+
overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
|
|
16841
|
+
};
|
|
16842
|
+
function buildSection(kind, graph, node, incidents, now) {
|
|
16843
|
+
switch (kind) {
|
|
16844
|
+
case "root-cause":
|
|
16845
|
+
return buildRootCauseSection(graph, node, incidents, now);
|
|
16846
|
+
case "dependencies":
|
|
16847
|
+
return buildDependenciesSection(graph, node);
|
|
16848
|
+
case "observed":
|
|
16849
|
+
return buildObservedSection(graph, node);
|
|
16850
|
+
case "blast":
|
|
16851
|
+
return buildBlastSection(graph, node);
|
|
16852
|
+
case "incidents":
|
|
16853
|
+
return buildIncidentsSection(node, incidents);
|
|
16854
|
+
case "divergence":
|
|
16855
|
+
return buildDivergenceSection(graph, node);
|
|
16856
|
+
}
|
|
16857
|
+
}
|
|
16858
|
+
function summarizeGlobal(intent, sections) {
|
|
16859
|
+
const lead = sections[0];
|
|
16860
|
+
switch (intent) {
|
|
16861
|
+
case "divergence": {
|
|
16862
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
16863
|
+
return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
|
|
16864
|
+
}
|
|
16865
|
+
case "incidents": {
|
|
16866
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
16867
|
+
return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
|
|
16868
|
+
}
|
|
16869
|
+
case "overview":
|
|
16870
|
+
default:
|
|
16871
|
+
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.`;
|
|
16872
|
+
}
|
|
16873
|
+
}
|
|
16874
|
+
function summarize(question, intent, matched, primary, sections, scope) {
|
|
16875
|
+
if (scope === "global") {
|
|
16876
|
+
return summarizeGlobal(intent, sections);
|
|
16877
|
+
}
|
|
16878
|
+
if (!primary) {
|
|
16879
|
+
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.`;
|
|
16880
|
+
}
|
|
16881
|
+
const lead = sections[0];
|
|
16882
|
+
const others = matched.slice(1);
|
|
16883
|
+
const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
|
|
16884
|
+
let core;
|
|
16885
|
+
switch (intent) {
|
|
16886
|
+
case "root-cause": {
|
|
16887
|
+
const rc = sections.find((s) => s.heading === "Root cause (navigation)");
|
|
16888
|
+
if (rc) {
|
|
16889
|
+
core = rc.facts[0]?.text ?? "";
|
|
16890
|
+
} else if (lead) {
|
|
16891
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
|
|
16892
|
+
} else {
|
|
16893
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
|
|
16894
|
+
}
|
|
16895
|
+
break;
|
|
16896
|
+
}
|
|
16897
|
+
case "blast-radius":
|
|
16898
|
+
core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
|
|
16899
|
+
break;
|
|
16900
|
+
case "dependencies":
|
|
16901
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
|
|
16902
|
+
break;
|
|
16903
|
+
case "observed":
|
|
16904
|
+
core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
|
|
16905
|
+
break;
|
|
16906
|
+
case "incidents":
|
|
16907
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
|
|
16908
|
+
break;
|
|
16909
|
+
case "divergence":
|
|
16910
|
+
core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
|
|
16911
|
+
break;
|
|
16912
|
+
default:
|
|
16913
|
+
core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
|
|
16914
|
+
}
|
|
16915
|
+
return `${core}${alsoNote}`;
|
|
16916
|
+
}
|
|
16917
|
+
async function askGraph(graph, question, opts = {}) {
|
|
16918
|
+
const now = opts.now ?? Date.now();
|
|
16919
|
+
const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
|
|
16920
|
+
const intent = classifyIntent(question);
|
|
16921
|
+
const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
|
|
16922
|
+
const primary = matched[0]?.nodeId;
|
|
16923
|
+
const sections = [];
|
|
16924
|
+
let scope;
|
|
16925
|
+
if (primary) {
|
|
16926
|
+
scope = "node";
|
|
16927
|
+
for (const kind of SECTION_ORDER[intent]) {
|
|
16928
|
+
const s = buildSection(kind, graph, primary, opts.incidents, now);
|
|
16929
|
+
if (s && s.facts.length > 0) sections.push(s);
|
|
16930
|
+
}
|
|
16931
|
+
} else {
|
|
16932
|
+
const global = buildGlobalSections(intent, graph, opts.incidents);
|
|
16933
|
+
if (global) {
|
|
16934
|
+
scope = "global";
|
|
16935
|
+
for (const s of global) if (s.facts.length > 0) sections.push(s);
|
|
16936
|
+
}
|
|
16937
|
+
}
|
|
16938
|
+
const answer = summarize(question, intent, matched, primary, sections, scope);
|
|
16939
|
+
const provSet = /* @__PURE__ */ new Set();
|
|
16940
|
+
for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
|
|
16941
|
+
const confidence = sections[0]?.facts[0]?.confidence;
|
|
16942
|
+
return import_types58.AskResultSchema.parse({
|
|
16943
|
+
question,
|
|
16944
|
+
intent,
|
|
16945
|
+
matched,
|
|
16946
|
+
...primary ? { primaryNode: primary } : {},
|
|
16947
|
+
...scope ? { scope } : {},
|
|
16948
|
+
sections,
|
|
16949
|
+
answer,
|
|
16950
|
+
...confidence !== void 0 ? { confidence } : {},
|
|
16951
|
+
provenance: [...provSet]
|
|
16952
|
+
});
|
|
16953
|
+
}
|
|
16954
|
+
|
|
16271
16955
|
// src/diff.ts
|
|
16272
16956
|
init_cjs_shims();
|
|
16273
16957
|
var import_node_fs35 = require("fs");
|
|
@@ -16349,7 +17033,7 @@ function canonicalJson(value) {
|
|
|
16349
17033
|
init_cjs_shims();
|
|
16350
17034
|
var import_node_fs36 = require("fs");
|
|
16351
17035
|
var import_node_path70 = __toESM(require("path"), 1);
|
|
16352
|
-
var
|
|
17036
|
+
var import_types59 = require("@neat.is/types");
|
|
16353
17037
|
var SCHEMA_VERSION = 7;
|
|
16354
17038
|
function migrateV1ToV2(payload) {
|
|
16355
17039
|
const nodes = payload.graph.nodes;
|
|
@@ -16373,7 +17057,7 @@ function migrateV5ToV6(payload) {
|
|
|
16373
17057
|
if (Array.isArray(nodes)) {
|
|
16374
17058
|
for (const node of nodes) {
|
|
16375
17059
|
const attrs = node.attributes;
|
|
16376
|
-
if (!attrs || attrs.type !==
|
|
17060
|
+
if (!attrs || attrs.type !== import_types59.NodeType.InfraNode) continue;
|
|
16377
17061
|
if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
|
|
16378
17062
|
if (!Array.isArray(attrs.columns)) attrs.columns = [];
|
|
16379
17063
|
}
|
|
@@ -16389,12 +17073,12 @@ function migrateV2ToV3(payload) {
|
|
|
16389
17073
|
for (const edge of edges) {
|
|
16390
17074
|
const attrs = edge.attributes;
|
|
16391
17075
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
16392
|
-
attrs.provenance =
|
|
17076
|
+
attrs.provenance = import_types59.Provenance.OBSERVED;
|
|
16393
17077
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
16394
17078
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
16395
17079
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
16396
17080
|
if (type && source && target) {
|
|
16397
|
-
const newId = (0,
|
|
17081
|
+
const newId = (0, import_types59.observedEdgeId)(source, target, type);
|
|
16398
17082
|
attrs.id = newId;
|
|
16399
17083
|
if (edge.key) edge.key = newId;
|
|
16400
17084
|
}
|
|
@@ -16552,7 +17236,7 @@ init_cjs_shims();
|
|
|
16552
17236
|
var import_node_fs37 = require("fs");
|
|
16553
17237
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
16554
17238
|
var import_node_path72 = __toESM(require("path"), 1);
|
|
16555
|
-
var
|
|
17239
|
+
var import_types60 = require("@neat.is/types");
|
|
16556
17240
|
function neatHome() {
|
|
16557
17241
|
const override = process.env.NEAT_HOME;
|
|
16558
17242
|
if (override && override.length > 0) return import_node_path72.default.resolve(override);
|
|
@@ -16642,7 +17326,7 @@ async function readRegistry() {
|
|
|
16642
17326
|
throw err;
|
|
16643
17327
|
}
|
|
16644
17328
|
const parsed = JSON.parse(raw);
|
|
16645
|
-
return
|
|
17329
|
+
return import_types60.RegistryFileSchema.parse(parsed);
|
|
16646
17330
|
}
|
|
16647
17331
|
async function getProject(name) {
|
|
16648
17332
|
const reg = await readRegistry();
|
|
@@ -16923,15 +17607,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
16923
17607
|
|
|
16924
17608
|
// src/connectors/index.ts
|
|
16925
17609
|
init_cjs_shims();
|
|
16926
|
-
var
|
|
17610
|
+
var import_types61 = require("@neat.is/types");
|
|
16927
17611
|
var NO_ENV = "unknown";
|
|
16928
17612
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
16929
17613
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
16930
17614
|
const sites = [];
|
|
16931
17615
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
16932
17616
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
16933
|
-
if (edge.provenance !==
|
|
16934
|
-
const parsed = (0,
|
|
17617
|
+
if (edge.provenance !== import_types61.Provenance.EXTRACTED) continue;
|
|
17618
|
+
const parsed = (0, import_types61.parseFileId)(edge.source);
|
|
16935
17619
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
16936
17620
|
const site = { relPath: edge.evidence.file };
|
|
16937
17621
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -16942,7 +17626,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
16942
17626
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
16943
17627
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
16944
17628
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
16945
|
-
if (attrs.type !==
|
|
17629
|
+
if (attrs.type !== import_types61.NodeType.RouteNode || !attrs.path) return void 0;
|
|
16946
17630
|
const site = { relPath: attrs.path };
|
|
16947
17631
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
16948
17632
|
return site;
|
|
@@ -17513,23 +18197,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
17513
18197
|
|
|
17514
18198
|
// src/connectors/supabase/resolve.ts
|
|
17515
18199
|
init_cjs_shims();
|
|
17516
|
-
var
|
|
18200
|
+
var import_types63 = require("@neat.is/types");
|
|
17517
18201
|
function createSupabaseResolveTarget(graph, config) {
|
|
17518
18202
|
return (signal, _ctx) => {
|
|
17519
18203
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
17520
18204
|
return null;
|
|
17521
18205
|
}
|
|
17522
|
-
const subResourceId = (0,
|
|
18206
|
+
const subResourceId = (0, import_types63.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
17523
18207
|
if (graph.hasNode(subResourceId)) {
|
|
17524
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
18208
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17525
18209
|
}
|
|
17526
|
-
const bareResourceId = (0,
|
|
18210
|
+
const bareResourceId = (0, import_types63.infraId)(signal.targetKind, signal.targetName);
|
|
17527
18211
|
if (graph.hasNode(bareResourceId)) {
|
|
17528
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
18212
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17529
18213
|
}
|
|
17530
|
-
const projectLevelId = (0,
|
|
18214
|
+
const projectLevelId = (0, import_types63.infraId)("supabase", config.nodeRef);
|
|
17531
18215
|
if (graph.hasNode(projectLevelId)) {
|
|
17532
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
18216
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
|
|
17533
18217
|
}
|
|
17534
18218
|
return null;
|
|
17535
18219
|
};
|
|
@@ -17622,7 +18306,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
17622
18306
|
|
|
17623
18307
|
// src/connectors/railway/index.ts
|
|
17624
18308
|
init_cjs_shims();
|
|
17625
|
-
var
|
|
18309
|
+
var import_types67 = require("@neat.is/types");
|
|
17626
18310
|
|
|
17627
18311
|
// src/connectors/railway/client.ts
|
|
17628
18312
|
init_cjs_shims();
|
|
@@ -17773,7 +18457,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
17773
18457
|
const out = [];
|
|
17774
18458
|
graph.forEachNode((_id, attrs) => {
|
|
17775
18459
|
const node = attrs;
|
|
17776
|
-
if (node.type !==
|
|
18460
|
+
if (node.type !== import_types67.NodeType.RouteNode) return;
|
|
17777
18461
|
const route = attrs;
|
|
17778
18462
|
if (route.service !== serviceName) return;
|
|
17779
18463
|
out.push({
|
|
@@ -17877,12 +18561,12 @@ function createRailwayResolveTarget(config) {
|
|
|
17877
18561
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
17878
18562
|
if (!serviceName) return null;
|
|
17879
18563
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
17880
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
18564
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types67.EdgeType.CALLS };
|
|
17881
18565
|
}
|
|
17882
18566
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
17883
18567
|
const peerName = config.serviceNameById[signal.targetName];
|
|
17884
18568
|
if (!peerName) return null;
|
|
17885
|
-
return { targetNodeId: (0,
|
|
18569
|
+
return { targetNodeId: (0, import_types67.serviceId)(peerName), serviceName, edgeType: import_types67.EdgeType.CONNECTS_TO };
|
|
17886
18570
|
}
|
|
17887
18571
|
return null;
|
|
17888
18572
|
};
|
|
@@ -18070,7 +18754,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
18070
18754
|
|
|
18071
18755
|
// src/connectors/firebase/resolve.ts
|
|
18072
18756
|
init_cjs_shims();
|
|
18073
|
-
var
|
|
18757
|
+
var import_types68 = require("@neat.is/types");
|
|
18074
18758
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
18075
18759
|
switch (resourceType) {
|
|
18076
18760
|
case "cloud_function":
|
|
@@ -18085,7 +18769,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
18085
18769
|
const entries = [];
|
|
18086
18770
|
graph.forEachNode((_id, attrs) => {
|
|
18087
18771
|
const node = attrs;
|
|
18088
|
-
if (node.type !==
|
|
18772
|
+
if (node.type !== import_types68.NodeType.RouteNode) return;
|
|
18089
18773
|
const route = attrs;
|
|
18090
18774
|
if (route.service !== serviceName) return;
|
|
18091
18775
|
entries.push({
|
|
@@ -18117,7 +18801,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
18117
18801
|
return {
|
|
18118
18802
|
targetNodeId: match.routeNodeId,
|
|
18119
18803
|
serviceName,
|
|
18120
|
-
edgeType:
|
|
18804
|
+
edgeType: import_types68.EdgeType.CALLS
|
|
18121
18805
|
};
|
|
18122
18806
|
};
|
|
18123
18807
|
}
|
|
@@ -18144,7 +18828,7 @@ init_cjs_shims();
|
|
|
18144
18828
|
|
|
18145
18829
|
// src/connectors/cloudflare/connector.ts
|
|
18146
18830
|
init_cjs_shims();
|
|
18147
|
-
var
|
|
18831
|
+
var import_types70 = require("@neat.is/types");
|
|
18148
18832
|
|
|
18149
18833
|
// src/connectors/cloudflare/client.ts
|
|
18150
18834
|
init_cjs_shims();
|
|
@@ -18308,7 +18992,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
18308
18992
|
graph.forEachNode((id, attrs) => {
|
|
18309
18993
|
if (found) return;
|
|
18310
18994
|
const a = attrs;
|
|
18311
|
-
if (a.type ===
|
|
18995
|
+
if (a.type === import_types70.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
18312
18996
|
found = id;
|
|
18313
18997
|
}
|
|
18314
18998
|
});
|
|
@@ -18320,7 +19004,7 @@ function findMatchingRouteNode(graph, serviceName, method, path76) {
|
|
|
18320
19004
|
graph.forEachNode((id, attrs) => {
|
|
18321
19005
|
if (found) return;
|
|
18322
19006
|
const a = attrs;
|
|
18323
|
-
if (a.type !==
|
|
19007
|
+
if (a.type !== import_types70.NodeType.RouteNode || a.service !== serviceName) return;
|
|
18324
19008
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
18325
19009
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
18326
19010
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -18339,11 +19023,11 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
18339
19023
|
};
|
|
18340
19024
|
const mapping = config.workers?.[scriptName];
|
|
18341
19025
|
if (mapping) {
|
|
18342
|
-
const wholeFileId = (0,
|
|
19026
|
+
const wholeFileId = (0, import_types70.fileId)(mapping.service, mapping.entryFile);
|
|
18343
19027
|
return {
|
|
18344
19028
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
18345
19029
|
serviceName: mapping.service,
|
|
18346
|
-
edgeType:
|
|
19030
|
+
edgeType: import_types70.EdgeType.CALLS
|
|
18347
19031
|
};
|
|
18348
19032
|
}
|
|
18349
19033
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -18352,13 +19036,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
18352
19036
|
return {
|
|
18353
19037
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
18354
19038
|
serviceName: fileNode.service,
|
|
18355
|
-
edgeType:
|
|
19039
|
+
edgeType: import_types70.EdgeType.CALLS
|
|
18356
19040
|
};
|
|
18357
19041
|
}
|
|
18358
19042
|
return {
|
|
18359
|
-
targetNodeId: (0,
|
|
19043
|
+
targetNodeId: (0, import_types70.infraId)("cloudflare-worker", scriptName),
|
|
18360
19044
|
serviceName: scriptName,
|
|
18361
|
-
edgeType:
|
|
19045
|
+
edgeType: import_types70.EdgeType.CALLS,
|
|
18362
19046
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
18363
19047
|
};
|
|
18364
19048
|
};
|
|
@@ -18554,14 +19238,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
18554
19238
|
|
|
18555
19239
|
// src/connectors/neon/resolve.ts
|
|
18556
19240
|
init_cjs_shims();
|
|
18557
|
-
var
|
|
19241
|
+
var import_types74 = require("@neat.is/types");
|
|
18558
19242
|
function createNeonResolveTarget(config) {
|
|
18559
19243
|
return (signal) => {
|
|
18560
19244
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
18561
19245
|
return {
|
|
18562
|
-
targetNodeId: (0,
|
|
19246
|
+
targetNodeId: (0, import_types74.infraId)("sql-table", signal.targetName),
|
|
18563
19247
|
serviceName: config.serviceName,
|
|
18564
|
-
edgeType:
|
|
19248
|
+
edgeType: import_types74.EdgeType.CALLS,
|
|
18565
19249
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
18566
19250
|
};
|
|
18567
19251
|
};
|
|
@@ -18742,14 +19426,14 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
18742
19426
|
|
|
18743
19427
|
// src/connectors/cloud-run/resolve.ts
|
|
18744
19428
|
init_cjs_shims();
|
|
18745
|
-
var
|
|
19429
|
+
var import_types78 = require("@neat.is/types");
|
|
18746
19430
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
18747
19431
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
18748
19432
|
let found = null;
|
|
18749
19433
|
graph.forEachNode((_id, attrs) => {
|
|
18750
19434
|
if (found) return;
|
|
18751
19435
|
const node = attrs;
|
|
18752
|
-
if (node.type !==
|
|
19436
|
+
if (node.type !== import_types78.NodeType.RouteNode) return;
|
|
18753
19437
|
const route = attrs;
|
|
18754
19438
|
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
18755
19439
|
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
@@ -18774,13 +19458,13 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
18774
19458
|
normalizePathTemplate(path76)
|
|
18775
19459
|
);
|
|
18776
19460
|
if (routeNodeId) {
|
|
18777
|
-
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType:
|
|
19461
|
+
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types78.EdgeType.CALLS };
|
|
18778
19462
|
}
|
|
18779
19463
|
}
|
|
18780
19464
|
return {
|
|
18781
|
-
targetNodeId: (0,
|
|
19465
|
+
targetNodeId: (0, import_types78.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
18782
19466
|
serviceName: mappedService ?? gcpServiceName,
|
|
18783
|
-
edgeType:
|
|
19467
|
+
edgeType: import_types78.EdgeType.CALLS,
|
|
18784
19468
|
ensureInfraNode: {
|
|
18785
19469
|
kind: CLOUD_RUN_SERVICE_INFRA_KIND,
|
|
18786
19470
|
name: gcpServiceName,
|
|
@@ -18821,7 +19505,7 @@ function createCloudRunConnector(graph, config = {}) {
|
|
|
18821
19505
|
|
|
18822
19506
|
// src/connectors/render/index.ts
|
|
18823
19507
|
init_cjs_shims();
|
|
18824
|
-
var
|
|
19508
|
+
var import_types81 = require("@neat.is/types");
|
|
18825
19509
|
|
|
18826
19510
|
// src/connectors/render/types.ts
|
|
18827
19511
|
init_cjs_shims();
|
|
@@ -18899,7 +19583,7 @@ function buildRenderRouteIndex(graph, serviceName) {
|
|
|
18899
19583
|
const out = [];
|
|
18900
19584
|
graph.forEachNode((_id, attrs) => {
|
|
18901
19585
|
const node = attrs;
|
|
18902
|
-
if (node.type !==
|
|
19586
|
+
if (node.type !== import_types81.NodeType.RouteNode) return;
|
|
18903
19587
|
const route = attrs;
|
|
18904
19588
|
if (route.service !== serviceName) return;
|
|
18905
19589
|
out.push({
|
|
@@ -18984,7 +19668,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
|
|
|
18984
19668
|
function createRenderResolveTarget(config) {
|
|
18985
19669
|
return (signal) => {
|
|
18986
19670
|
if (signal.targetKind === ROUTE_TARGET_KIND2) {
|
|
18987
|
-
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType:
|
|
19671
|
+
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types81.EdgeType.CALLS };
|
|
18988
19672
|
}
|
|
18989
19673
|
return null;
|
|
18990
19674
|
};
|
|
@@ -19122,21 +19806,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
|
|
|
19122
19806
|
|
|
19123
19807
|
// src/connectors/planetscale/resolve.ts
|
|
19124
19808
|
init_cjs_shims();
|
|
19125
|
-
var
|
|
19809
|
+
var import_types85 = require("@neat.is/types");
|
|
19126
19810
|
var PLANETSCALE_DATABASE_KIND = "planetscale-database";
|
|
19127
19811
|
function createPlanetscaleResolveTarget(graph, config) {
|
|
19128
19812
|
const databaseName = `${config.organization}/${config.database}`;
|
|
19129
19813
|
return (signal, _ctx) => {
|
|
19130
19814
|
if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
19131
|
-
const tableId = (0,
|
|
19815
|
+
const tableId = (0, import_types85.infraId)("sql-table", signal.targetName);
|
|
19132
19816
|
if (graph.hasNode(tableId)) {
|
|
19133
|
-
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType:
|
|
19817
|
+
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types85.EdgeType.CALLS };
|
|
19134
19818
|
}
|
|
19135
|
-
const providerId = (0,
|
|
19819
|
+
const providerId = (0, import_types85.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
|
|
19136
19820
|
return {
|
|
19137
19821
|
targetNodeId: providerId,
|
|
19138
19822
|
serviceName: config.serviceName,
|
|
19139
|
-
edgeType:
|
|
19823
|
+
edgeType: import_types85.EdgeType.CALLS,
|
|
19140
19824
|
ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
|
|
19141
19825
|
};
|
|
19142
19826
|
};
|
|
@@ -19401,7 +20085,7 @@ function mapBuildsToSignals(builds, serviceName) {
|
|
|
19401
20085
|
|
|
19402
20086
|
// src/connectors/eas/resolve.ts
|
|
19403
20087
|
init_cjs_shims();
|
|
19404
|
-
var
|
|
20088
|
+
var import_types90 = require("@neat.is/types");
|
|
19405
20089
|
var NO_ENV2 = "unknown";
|
|
19406
20090
|
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
19407
20091
|
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
@@ -19417,8 +20101,8 @@ function configBasenamesForPhase(phase) {
|
|
|
19417
20101
|
function configNodeService(graph, configNodeId) {
|
|
19418
20102
|
for (const edgeId of graph.inboundEdges(configNodeId)) {
|
|
19419
20103
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
19420
|
-
if (edge.type !==
|
|
19421
|
-
const parsed = (0,
|
|
20104
|
+
if (edge.type !== import_types90.EdgeType.CONFIGURED_BY) continue;
|
|
20105
|
+
const parsed = (0, import_types90.parseFileId)(edge.source);
|
|
19422
20106
|
if (parsed) return parsed.service;
|
|
19423
20107
|
}
|
|
19424
20108
|
return null;
|
|
@@ -19429,7 +20113,7 @@ function findConfigNode(graph, basenames, serviceName) {
|
|
|
19429
20113
|
graph.forEachNode((id, attrs) => {
|
|
19430
20114
|
if (scoped) return;
|
|
19431
20115
|
const node = attrs;
|
|
19432
|
-
if (node.type !==
|
|
20116
|
+
if (node.type !== import_types90.NodeType.ConfigNode) return;
|
|
19433
20117
|
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
19434
20118
|
if (anyMatch === null) anyMatch = id;
|
|
19435
20119
|
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
@@ -19446,13 +20130,13 @@ function createEasResolveTarget(graph) {
|
|
|
19446
20130
|
if (basenames.length > 0) {
|
|
19447
20131
|
const configNodeId = findConfigNode(graph, basenames, serviceName);
|
|
19448
20132
|
if (configNodeId) {
|
|
19449
|
-
return { targetNodeId: configNodeId, serviceName, edgeType:
|
|
20133
|
+
return { targetNodeId: configNodeId, serviceName, edgeType: import_types90.EdgeType.CALLS };
|
|
19450
20134
|
}
|
|
19451
20135
|
}
|
|
19452
20136
|
return {
|
|
19453
20137
|
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
|
|
19454
20138
|
serviceName,
|
|
19455
|
-
edgeType:
|
|
20139
|
+
edgeType: import_types90.EdgeType.CALLS
|
|
19456
20140
|
};
|
|
19457
20141
|
};
|
|
19458
20142
|
}
|
|
@@ -20131,11 +20815,11 @@ function registerRoutes(scope, ctx) {
|
|
|
20131
20815
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
20132
20816
|
const parsed = [];
|
|
20133
20817
|
for (const c of candidates) {
|
|
20134
|
-
const r =
|
|
20818
|
+
const r = import_types93.DivergenceTypeSchema.safeParse(c);
|
|
20135
20819
|
if (!r.success) {
|
|
20136
20820
|
return reply.code(400).send({
|
|
20137
20821
|
error: `unknown divergence type "${c}"`,
|
|
20138
|
-
allowed:
|
|
20822
|
+
allowed: import_types93.DivergenceTypeSchema.options
|
|
20139
20823
|
});
|
|
20140
20824
|
}
|
|
20141
20825
|
parsed.push(r.data);
|
|
@@ -20377,6 +21061,18 @@ function registerRoutes(scope, ctx) {
|
|
|
20377
21061
|
matches: matches.slice(0, safeLimit)
|
|
20378
21062
|
};
|
|
20379
21063
|
});
|
|
21064
|
+
scope.get("/graph/ask", async (req, reply) => {
|
|
21065
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
21066
|
+
if (!proj) return;
|
|
21067
|
+
const question = (req.query.q ?? "").trim();
|
|
21068
|
+
if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
|
|
21069
|
+
const epath = errorsPathFor(proj);
|
|
21070
|
+
const incidents = epath ? await readErrorEvents(epath) : [];
|
|
21071
|
+
return askGraph(proj.graph, question, {
|
|
21072
|
+
...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
|
|
21073
|
+
incidents
|
|
21074
|
+
});
|
|
21075
|
+
});
|
|
20380
21076
|
scope.get(
|
|
20381
21077
|
"/graph/diff",
|
|
20382
21078
|
async (req, reply) => {
|
|
@@ -20477,7 +21173,7 @@ function registerRoutes(scope, ctx) {
|
|
|
20477
21173
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
20478
21174
|
let violations = await log.readAll();
|
|
20479
21175
|
if (req.query.severity) {
|
|
20480
|
-
const sev =
|
|
21176
|
+
const sev = import_types93.PolicySeveritySchema.safeParse(req.query.severity);
|
|
20481
21177
|
if (!sev.success) {
|
|
20482
21178
|
return reply.code(400).send({
|
|
20483
21179
|
error: "invalid severity",
|
|
@@ -20516,7 +21212,7 @@ function registerRoutes(scope, ctx) {
|
|
|
20516
21212
|
scope.post("/policies/check", async (req, reply) => {
|
|
20517
21213
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
20518
21214
|
if (!proj) return;
|
|
20519
|
-
const parsed =
|
|
21215
|
+
const parsed = import_types93.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
20520
21216
|
if (!parsed.success) {
|
|
20521
21217
|
return reply.code(400).send({
|
|
20522
21218
|
error: "invalid /policies/check body",
|