@neat.is/core 0.9.13-dev.20260901 → 0.9.14
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-WE3AFQYL.js → chunk-NV4WWJSU.js} +78 -7
- package/dist/chunk-NV4WWJSU.js.map +1 -0
- package/dist/{chunk-RBZNXA5L.js → chunk-SSLPBQWY.js} +162 -38
- package/dist/chunk-SSLPBQWY.js.map +1 -0
- package/dist/cli.cjs +691 -84
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +23 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +225 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +225 -44
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +128 -37
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-RBZNXA5L.js.map +0 -1
- package/dist/chunk-WE3AFQYL.js.map +0 -1
|
@@ -935,6 +935,7 @@ import {
|
|
|
935
935
|
inferredEdgeId,
|
|
936
936
|
infraId,
|
|
937
937
|
observedEdgeId,
|
|
938
|
+
frontierEdgeId,
|
|
938
939
|
serviceId,
|
|
939
940
|
symbolId,
|
|
940
941
|
websocketChannelId
|
|
@@ -4413,6 +4414,14 @@ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
|
4413
4414
|
function mergeObservedColumns(graph, tableNodeId, columns) {
|
|
4414
4415
|
mergeColumnsAt(graph, tableNodeId, columns, Provenance5.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
|
|
4415
4416
|
}
|
|
4417
|
+
function mergeObservedDeployState(graph, serviceNodeId, state) {
|
|
4418
|
+
if (!graph.hasNode(serviceNodeId)) return;
|
|
4419
|
+
const attrs = {};
|
|
4420
|
+
if (typeof state.image === "string" && state.image.length > 0) attrs["observedImage"] = state.image;
|
|
4421
|
+
if (typeof state.readyReplicas === "number") attrs["observedReadyReplicas"] = state.readyReplicas;
|
|
4422
|
+
if (Object.keys(attrs).length === 0) return;
|
|
4423
|
+
graph.mergeNodeAttributes(serviceNodeId, attrs);
|
|
4424
|
+
}
|
|
4416
4425
|
function ensureDatabaseNode(graph, host, engine) {
|
|
4417
4426
|
const id = databaseId(host);
|
|
4418
4427
|
if (graph.hasNode(id)) return id;
|
|
@@ -5109,6 +5118,32 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
5109
5118
|
}
|
|
5110
5119
|
return promoted;
|
|
5111
5120
|
}
|
|
5121
|
+
function stageFrontierEdge(graph, source, target, type) {
|
|
5122
|
+
if (!graph.hasNode(source) || !graph.hasNode(target)) return false;
|
|
5123
|
+
if (graph.hasEdge(observedEdgeId(source, target, type))) return false;
|
|
5124
|
+
const key = frontierEdgeId(source, target, type);
|
|
5125
|
+
if (graph.hasEdge(key)) return false;
|
|
5126
|
+
graph.addEdgeWithKey(key, source, target, {
|
|
5127
|
+
id: key,
|
|
5128
|
+
source,
|
|
5129
|
+
target,
|
|
5130
|
+
type,
|
|
5131
|
+
provenance: Provenance5.FRONTIER
|
|
5132
|
+
});
|
|
5133
|
+
return true;
|
|
5134
|
+
}
|
|
5135
|
+
function promoteFrontierEdges(graph) {
|
|
5136
|
+
const graduated = [];
|
|
5137
|
+
graph.forEachEdge((edgeId, attrs) => {
|
|
5138
|
+
const e = attrs;
|
|
5139
|
+
if (e.provenance !== Provenance5.FRONTIER) return;
|
|
5140
|
+
if (graph.hasEdge(observedEdgeId(e.source, e.target, e.type))) {
|
|
5141
|
+
graduated.push(edgeId);
|
|
5142
|
+
}
|
|
5143
|
+
});
|
|
5144
|
+
for (const edgeId of graduated) graph.dropEdge(edgeId);
|
|
5145
|
+
return graduated.length;
|
|
5146
|
+
}
|
|
5112
5147
|
function rewireFrontierEdges(graph, frontierId2, serviceId17) {
|
|
5113
5148
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
5114
5149
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
@@ -5217,6 +5252,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
5217
5252
|
project: options.project
|
|
5218
5253
|
});
|
|
5219
5254
|
if (options.onPolicyTrigger) await options.onPolicyTrigger(graph);
|
|
5255
|
+
if (options.onReconcile) await options.onReconcile(graph);
|
|
5220
5256
|
} catch (err) {
|
|
5221
5257
|
console.error("staleness tick failed", err);
|
|
5222
5258
|
}
|
|
@@ -5360,6 +5396,7 @@ import {
|
|
|
5360
5396
|
RootCauseResultSchema,
|
|
5361
5397
|
TransitiveDependenciesResultSchema,
|
|
5362
5398
|
fileId as fileId4,
|
|
5399
|
+
frontierEdgeId as frontierEdgeId2,
|
|
5363
5400
|
parseSymbolId
|
|
5364
5401
|
} from "@neat.is/types";
|
|
5365
5402
|
var ROOT_CAUSE_MAX_DEPTH = 5;
|
|
@@ -5390,13 +5427,19 @@ function resolveOwningService(graph, nodeId) {
|
|
|
5390
5427
|
}
|
|
5391
5428
|
return null;
|
|
5392
5429
|
}
|
|
5430
|
+
function isFrontierEdge(e) {
|
|
5431
|
+
return e.provenance === Provenance6.FRONTIER;
|
|
5432
|
+
}
|
|
5433
|
+
function rankOf(p) {
|
|
5434
|
+
return p === Provenance6.FRONTIER ? -1 : PROV_RANK[p];
|
|
5435
|
+
}
|
|
5393
5436
|
function bestEdgeBySource(graph, edgeIds) {
|
|
5394
5437
|
const best = /* @__PURE__ */ new Map();
|
|
5395
5438
|
for (const id of edgeIds) {
|
|
5396
5439
|
const e = graph.getEdgeAttributes(id);
|
|
5397
|
-
if (isFrontierNode(graph, e.source)) continue;
|
|
5440
|
+
if (isFrontierNode(graph, e.source) || isFrontierEdge(e)) continue;
|
|
5398
5441
|
const cur = best.get(e.source);
|
|
5399
|
-
if (!cur ||
|
|
5442
|
+
if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
|
|
5400
5443
|
best.set(e.source, e);
|
|
5401
5444
|
}
|
|
5402
5445
|
}
|
|
@@ -5406,9 +5449,9 @@ function bestEdgeByTarget(graph, edgeIds) {
|
|
|
5406
5449
|
const best = /* @__PURE__ */ new Map();
|
|
5407
5450
|
for (const id of edgeIds) {
|
|
5408
5451
|
const e = graph.getEdgeAttributes(id);
|
|
5409
|
-
if (isFrontierNode(graph, e.target)) continue;
|
|
5452
|
+
if (isFrontierNode(graph, e.target) || isFrontierEdge(e)) continue;
|
|
5410
5453
|
const cur = best.get(e.target);
|
|
5411
|
-
if (!cur ||
|
|
5454
|
+
if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
|
|
5412
5455
|
best.set(e.target, e);
|
|
5413
5456
|
}
|
|
5414
5457
|
}
|
|
@@ -5418,7 +5461,10 @@ var PROVENANCE_CEILING = {
|
|
|
5418
5461
|
OBSERVED: 1,
|
|
5419
5462
|
INFERRED: 0.7,
|
|
5420
5463
|
EXTRACTED: 0.5,
|
|
5421
|
-
STALE: 0.3
|
|
5464
|
+
STALE: 0.3,
|
|
5465
|
+
// A FRONTIER surface (ADR-226) is a proposal about a cause NEAT could not observe
|
|
5466
|
+
// — the least-trusted claim it makes, below STALE (which was at least once seen).
|
|
5467
|
+
FRONTIER: 0.2
|
|
5422
5468
|
};
|
|
5423
5469
|
function volumeWeight(spanCount) {
|
|
5424
5470
|
if (!spanCount || spanCount <= 0) return 0.5;
|
|
@@ -5653,7 +5699,7 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
5653
5699
|
});
|
|
5654
5700
|
}
|
|
5655
5701
|
function isFailingCallEdge(e) {
|
|
5656
|
-
return e.type === EdgeType6.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
5702
|
+
return e.type === EdgeType6.CALLS && !isFrontierEdge(e) && (e.signal?.errorCount ?? 0) > 0;
|
|
5657
5703
|
}
|
|
5658
5704
|
function callSourcesForService(graph, serviceId17) {
|
|
5659
5705
|
const ids = [serviceId17];
|
|
@@ -5676,8 +5722,8 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
5676
5722
|
const ec = e.signal?.errorCount ?? 0;
|
|
5677
5723
|
const cc = curEdge.signal?.errorCount ?? 0;
|
|
5678
5724
|
if (ec !== cc) return ec > cc;
|
|
5679
|
-
if (
|
|
5680
|
-
return
|
|
5725
|
+
if (rankOf(e.provenance) !== rankOf(curEdge.provenance)) {
|
|
5726
|
+
return rankOf(e.provenance) > rankOf(curEdge.provenance);
|
|
5681
5727
|
}
|
|
5682
5728
|
return id < curId;
|
|
5683
5729
|
}
|
|
@@ -5728,11 +5774,11 @@ function dominantStaleCall(graph, serviceId17, visited) {
|
|
|
5728
5774
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
5729
5775
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5730
5776
|
if (e.type !== EdgeType6.CALLS) continue;
|
|
5731
|
-
if (isFrontierNode(graph, e.target)) continue;
|
|
5777
|
+
if (isFrontierNode(graph, e.target) || isFrontierEdge(e)) continue;
|
|
5732
5778
|
const owner = resolveOwningService(graph, e.target);
|
|
5733
5779
|
if (!owner || visited.has(owner.id)) continue;
|
|
5734
5780
|
const cur = bestByCallee.get(owner.id);
|
|
5735
|
-
if (!cur ||
|
|
5781
|
+
if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
|
|
5736
5782
|
bestByCallee.set(owner.id, e);
|
|
5737
5783
|
}
|
|
5738
5784
|
}
|
|
@@ -6005,6 +6051,7 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
6005
6051
|
for (const edgeId of graph.inboundEdges(n)) {
|
|
6006
6052
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6007
6053
|
if (e.type === EdgeType6.CONTAINS) continue;
|
|
6054
|
+
if (isFrontierEdge(e)) continue;
|
|
6008
6055
|
errorsFromCallers += e.signal?.errorCount ?? 0;
|
|
6009
6056
|
inboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
|
|
6010
6057
|
if (e.provenance === Provenance6.STALE) stale = true;
|
|
@@ -6018,6 +6065,7 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
6018
6065
|
for (const edgeId of graph.outboundEdges(n)) {
|
|
6019
6066
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6020
6067
|
if (e.type === EdgeType6.CONTAINS) continue;
|
|
6068
|
+
if (isFrontierEdge(e)) continue;
|
|
6021
6069
|
outboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
|
|
6022
6070
|
if (e.type === EdgeType6.CALLS) outboundErrors += e.signal?.errorCount ?? 0;
|
|
6023
6071
|
if (e.provenance === Provenance6.STALE) stale = true;
|
|
@@ -6051,6 +6099,11 @@ function isSaturated(ctx) {
|
|
|
6051
6099
|
function isBoundaryTimeoutSymptom(ctx) {
|
|
6052
6100
|
return ctx.errorsEmittedHere > 0 && ctx.boundaryTimeout === true && ctx.errorsFromCallers === 0 && ctx.hasOutboundDeps === true && ctx.observedErroringDownstream !== true;
|
|
6053
6101
|
}
|
|
6102
|
+
var UNREACHABLE_INBOUND_ERROR_RATE = 0.5;
|
|
6103
|
+
var UNREACHABLE_MIN_INBOUND = 3;
|
|
6104
|
+
function isUnreachableSeed(ctx) {
|
|
6105
|
+
return ctx.callCount >= UNREACHABLE_MIN_INBOUND && ctx.errorsFromCallers > 0 && ctx.errorsFromCallers >= UNREACHABLE_INBOUND_ERROR_RATE * ctx.callCount && ctx.errorsEmittedHere === 0 && ctx.outboundVolume === 0;
|
|
6106
|
+
}
|
|
6054
6107
|
function classifyNode(ctx) {
|
|
6055
6108
|
if (ctx.errorsEmittedHere > 0) {
|
|
6056
6109
|
if ((ctx.stale || isSaturated(ctx)) && ctx.errorsFromCallers >= ctx.errorsEmittedHere) {
|
|
@@ -6059,6 +6112,7 @@ function classifyNode(ctx) {
|
|
|
6059
6112
|
if (isBoundaryTimeoutSymptom(ctx)) return "symptom-only";
|
|
6060
6113
|
return "primary-failure";
|
|
6061
6114
|
}
|
|
6115
|
+
if (isUnreachableSeed(ctx)) return "unreachable";
|
|
6062
6116
|
if (ctx.errorsFromCallers > 0) return "symptom-only";
|
|
6063
6117
|
return "unrelated";
|
|
6064
6118
|
}
|
|
@@ -6295,22 +6349,44 @@ function routeMatches(declared, incident) {
|
|
|
6295
6349
|
const b = normalizeRoute(incident);
|
|
6296
6350
|
return a === b || a.startsWith(b) || b.startsWith(a);
|
|
6297
6351
|
}
|
|
6298
|
-
function
|
|
6299
|
-
const
|
|
6300
|
-
const callees = declaredOutboundCallees(graph, boundaryNode);
|
|
6352
|
+
function firstDeclaredCallee(graph, node, route) {
|
|
6353
|
+
const callees = declaredOutboundCallees(graph, node);
|
|
6301
6354
|
if (callees.length === 0) return null;
|
|
6302
6355
|
const narrowed = route !== void 0 ? callees.filter((c) => c.route !== void 0 && routeMatches(c.route, route)) : callees;
|
|
6303
6356
|
const chosen = narrowed.length === 1 ? narrowed[0] : callees.length === 1 ? callees[0] : null;
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
if (
|
|
6310
|
-
|
|
6311
|
-
|
|
6357
|
+
return chosen ? chosen.target : null;
|
|
6358
|
+
}
|
|
6359
|
+
function observedOutboundNextServices(graph, nodeId) {
|
|
6360
|
+
const targets = /* @__PURE__ */ new Set();
|
|
6361
|
+
for (const n of nodeScope(graph, nodeId)) {
|
|
6362
|
+
if (!graph.hasNode(n)) continue;
|
|
6363
|
+
for (const edgeId of graph.outboundEdges(n)) {
|
|
6364
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
6365
|
+
if (!DEP_EDGE_TYPES.has(e.type)) continue;
|
|
6366
|
+
if (e.provenance !== Provenance6.OBSERVED) continue;
|
|
6367
|
+
if (e.target !== nodeId) targets.add(e.target);
|
|
6368
|
+
}
|
|
6369
|
+
}
|
|
6370
|
+
return [...targets];
|
|
6371
|
+
}
|
|
6372
|
+
function resolveHangHop(graph, boundaryNode, incidents) {
|
|
6373
|
+
const route = boundaryIncidentRoute(boundaryNode, incidents);
|
|
6374
|
+
const own = firstDeclaredCallee(graph, boundaryNode, route);
|
|
6375
|
+
if (own) {
|
|
6376
|
+
return route !== void 0 ? { source: boundaryNode, target: own, route } : { source: boundaryNode, target: own };
|
|
6377
|
+
}
|
|
6378
|
+
const resolved = observedOutboundNextServices(graph, boundaryNode).map((n) => ({ source: n, target: firstDeclaredCallee(graph, n, route) })).filter((r) => r.target !== null);
|
|
6379
|
+
if (resolved.length === 1) {
|
|
6380
|
+
const hop = resolved[0];
|
|
6381
|
+
return route !== void 0 ? { ...hop, route } : hop;
|
|
6312
6382
|
}
|
|
6313
|
-
return
|
|
6383
|
+
return null;
|
|
6384
|
+
}
|
|
6385
|
+
function stagedHangProposal(graph, boundaryNode, incidents) {
|
|
6386
|
+
const hop = resolveHangHop(graph, boundaryNode, incidents);
|
|
6387
|
+
if (!hop) return null;
|
|
6388
|
+
if (!graph.hasEdge(frontierEdgeId2(hop.source, hop.target, EdgeType6.CALLS))) return null;
|
|
6389
|
+
return hop.route !== void 0 ? { node: hop.target, route: hop.route } : { node: hop.target };
|
|
6314
6390
|
}
|
|
6315
6391
|
function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
6316
6392
|
const legacy = tagged.result;
|
|
@@ -6345,29 +6421,39 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
6345
6421
|
confidence: Math.min(legacy.confidence, 0.4),
|
|
6346
6422
|
...lastProv ? { provenance: lastProv } : {}
|
|
6347
6423
|
});
|
|
6424
|
+
} else if (seedCtx && isUnreachableSeed(seedCtx)) {
|
|
6425
|
+
const name = displayNameOf(seedNode);
|
|
6426
|
+
candidates.push({
|
|
6427
|
+
node: seedNode,
|
|
6428
|
+
classification: "unreachable",
|
|
6429
|
+
reason: `${name} is unreachable: its callers' requests fail (${seedCtx.errorsFromCallers} erroring inbound calls) and it produced no telemetry of its own \u2014 no server spans, no outbound calls \u2014 so it never served. The failure is observed, but its cause is not in the trace: a startup failure, a crash before the first span, or an unschedulable / unhealthy pod. Inspect ${name}'s deploy state and logs \u2014 there is no code fault to find in the graph here.`,
|
|
6430
|
+
context: seedCtx,
|
|
6431
|
+
confidence: legacy.confidence,
|
|
6432
|
+
provenance: Provenance6.OBSERVED
|
|
6433
|
+
});
|
|
6348
6434
|
} else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
|
|
6349
|
-
const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
|
|
6350
6435
|
const seedName = displayNameOf(seedNode);
|
|
6351
|
-
const
|
|
6352
|
-
|
|
6353
|
-
const causeName = displayNameOf(pointer.node);
|
|
6354
|
-
candidates.push({
|
|
6355
|
-
node: pointer.node,
|
|
6356
|
-
classification: "primary-failure",
|
|
6357
|
-
reason: `${causeName} is the likely root cause (structural, INFERRED \u2014 not observed): the boundary ${seedName} only timed out waiting, and the actual culprit hung without exporting a span, so this is walked from the declared call graph${routeNote}, not from runtime signal. Restore instrumentation / inspect ${causeName} to confirm.`,
|
|
6358
|
-
context: nodeContext(graph, pointer.node, incidents, now),
|
|
6359
|
-
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.EXTRACTED),
|
|
6360
|
-
provenance: Provenance6.INFERRED
|
|
6361
|
-
});
|
|
6362
|
-
}
|
|
6436
|
+
const proposal = stagedHangProposal(graph, seedNode, incidents);
|
|
6437
|
+
const routeNote = proposal?.route ? ` serving ${proposal.route}` : "";
|
|
6363
6438
|
candidates.push({
|
|
6364
6439
|
node: seedNode,
|
|
6365
6440
|
classification: "symptom-only",
|
|
6366
|
-
reason:
|
|
6441
|
+
reason: proposal ? `${seedName} timed out waiting on a downstream that hung and exported no span \u2014 a boundary reporting an upstream fault, not the fault itself. The cause is unobservable; NEAT proposes the staged FRONTIER surface below, a hypothesis to confirm, not an observed cause.` : `${seedName} timed out but nothing it can see downstream is erroring \u2014 the cause is downstream and unobserved (the culprit hung and exported no span). No FRONTIER surface${routeNote} is staged, so no cause is proposed; inspect ${seedName}'s declared downstream and restore instrumentation.`,
|
|
6367
6442
|
context: seedCtx,
|
|
6368
6443
|
confidence: Math.min(legacy.confidence, 0.3),
|
|
6369
6444
|
...lastProv ? { provenance: lastProv } : {}
|
|
6370
6445
|
});
|
|
6446
|
+
if (proposal) {
|
|
6447
|
+
const causeName = displayNameOf(proposal.node);
|
|
6448
|
+
candidates.push({
|
|
6449
|
+
node: proposal.node,
|
|
6450
|
+
classification: "primary-failure",
|
|
6451
|
+
reason: `${causeName} is the proposed hang cause (FRONTIER \u2014 a hypothesis, not observed): the boundary ${seedName} only timed out waiting and the culprit hung without exporting a span, so NEAT staged a FRONTIER surface on the unobservable hop${routeNote}. It graduates to OBSERVED when ${causeName} comes back; until then, inspect it / restore instrumentation to confirm, or it is culled.`,
|
|
6452
|
+
context: nodeContext(graph, proposal.node, incidents, now),
|
|
6453
|
+
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.FRONTIER),
|
|
6454
|
+
provenance: Provenance6.FRONTIER
|
|
6455
|
+
});
|
|
6456
|
+
}
|
|
6371
6457
|
} else if (staleChain) {
|
|
6372
6458
|
const culprit = staleChain.culprit;
|
|
6373
6459
|
const culpritName = displayNameOf(culprit);
|
|
@@ -6431,6 +6517,9 @@ function fixRecommendationForTop(top, seedNode, legacy) {
|
|
|
6431
6517
|
return legacy.fixRecommendation;
|
|
6432
6518
|
}
|
|
6433
6519
|
const name = top.node.replace(/^service:/, "");
|
|
6520
|
+
if (top.classification === "symptom-only" && top.context.boundaryTimeout === true) {
|
|
6521
|
+
return `${name} timed out waiting on a downstream that hung and exported no span \u2014 the cause is unobservable from traces. Inspect the FRONTIER surface NEAT staged on the unobservable hop (see candidates); it graduates to OBSERVED when that service recovers. Restore instrumentation on that path to confirm, rather than treating ${name} as the fault.`;
|
|
6522
|
+
}
|
|
6434
6523
|
if (top.provenance === Provenance6.STALE) {
|
|
6435
6524
|
return `Live telemetry for this path has gone quiet; the last-observed topology traces the failure downstream to ${name}. Restore instrumentation (or re-run with live traces) to confirm, then inspect ${name}.`;
|
|
6436
6525
|
}
|
|
@@ -15708,6 +15797,25 @@ function detectColumnDrift(node) {
|
|
|
15708
15797
|
}
|
|
15709
15798
|
return out;
|
|
15710
15799
|
}
|
|
15800
|
+
var RECOMMENDATION_DEPLOY_IMAGE_MISMATCH = "The running image differs from the manifest-declared image \u2014 the rollout has not taken (the old ReplicaSet is still serving). Re-apply the deployment or investigate why the new image failed to roll out.";
|
|
15801
|
+
var DEPLOY_DIVERGENCE_CONFIDENCE = 0.9;
|
|
15802
|
+
function detectDeployDivergence(svc) {
|
|
15803
|
+
const out = [];
|
|
15804
|
+
if (typeof svc.declaredImage === "string" && typeof svc.observedImage === "string" && svc.declaredImage !== svc.observedImage) {
|
|
15805
|
+
out.push({
|
|
15806
|
+
type: "deploy-mismatch",
|
|
15807
|
+
kind: "image",
|
|
15808
|
+
source: svc.id,
|
|
15809
|
+
target: svc.id,
|
|
15810
|
+
declaredImage: svc.declaredImage,
|
|
15811
|
+
observedImage: svc.observedImage,
|
|
15812
|
+
confidence: DEPLOY_DIVERGENCE_CONFIDENCE,
|
|
15813
|
+
reason: `Service ${svc.name} declares image ${svc.declaredImage} but its running pods report ${svc.observedImage} \u2014 the deploy has not taken (the old version is still serving), so no incident fires.`,
|
|
15814
|
+
recommendation: RECOMMENDATION_DEPLOY_IMAGE_MISMATCH
|
|
15815
|
+
});
|
|
15816
|
+
}
|
|
15817
|
+
return out;
|
|
15818
|
+
}
|
|
15711
15819
|
var SYMBOL_MISMATCH_PATTERNS = [
|
|
15712
15820
|
{
|
|
15713
15821
|
// "'ListProductsResponse' object has no attribute 'products_list'" and kin.
|
|
@@ -16035,6 +16143,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
16035
16143
|
const svc = n;
|
|
16036
16144
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
16037
16145
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
16146
|
+
for (const d of detectDeployDivergence(svc)) all.push(d);
|
|
16038
16147
|
return;
|
|
16039
16148
|
}
|
|
16040
16149
|
if (n.type === NodeType30.InfraNode && n.kind === "sql-table") {
|
|
@@ -16075,7 +16184,12 @@ function computeDivergences(graph, opts = {}) {
|
|
|
16075
16184
|
// orders last so at equal confidence the structural and symbol divergences
|
|
16076
16185
|
// lead, per the contract ("rank below the definitive structural and symbol
|
|
16077
16186
|
// divergences").
|
|
16078
|
-
"observed-failing": 6
|
|
16187
|
+
"observed-failing": 6,
|
|
16188
|
+
// Deploy mismatch (ADR-225) is a definitive structural declared-vs-observed
|
|
16189
|
+
// divergence carrying high confidence (0.9), so the confidence sort already
|
|
16190
|
+
// places it among the structural leaders; this slot only breaks an exact
|
|
16191
|
+
// confidence tie and sits last as a stable tiebreaker.
|
|
16192
|
+
"deploy-mismatch": 7
|
|
16079
16193
|
};
|
|
16080
16194
|
filtered.sort((a, b) => {
|
|
16081
16195
|
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
@@ -18378,6 +18492,11 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
18378
18492
|
unresolved++;
|
|
18379
18493
|
continue;
|
|
18380
18494
|
}
|
|
18495
|
+
if (signal.deployState) {
|
|
18496
|
+
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
18497
|
+
mergeObservedDeployState(graph, resolved.targetNodeId, signal.deployState);
|
|
18498
|
+
continue;
|
|
18499
|
+
}
|
|
18381
18500
|
if (signal.incident) {
|
|
18382
18501
|
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
18383
18502
|
if (!ctx.errorsPath || !graph.hasNode(resolved.targetNodeId)) {
|
|
@@ -22654,7 +22773,10 @@ export {
|
|
|
22654
22773
|
attachGraphToEventBus,
|
|
22655
22774
|
confidenceForEdge,
|
|
22656
22775
|
getBlastRadius,
|
|
22776
|
+
nodeContext,
|
|
22777
|
+
isBoundaryTimeoutSymptom,
|
|
22657
22778
|
getRootCause,
|
|
22779
|
+
resolveHangHop,
|
|
22658
22780
|
evaluateAllPolicies,
|
|
22659
22781
|
loadPolicyFile,
|
|
22660
22782
|
PolicyViolationsLog,
|
|
@@ -22669,6 +22791,8 @@ export {
|
|
|
22669
22791
|
makeErrorSpanWriter,
|
|
22670
22792
|
handleSpan,
|
|
22671
22793
|
promoteFrontierNodes,
|
|
22794
|
+
stageFrontierEdge,
|
|
22795
|
+
promoteFrontierEdges,
|
|
22672
22796
|
makeSpanHandler,
|
|
22673
22797
|
markStaleEdges,
|
|
22674
22798
|
readStaleEvents,
|
|
@@ -22733,4 +22857,4 @@ export {
|
|
|
22733
22857
|
deprovisionConnector,
|
|
22734
22858
|
buildApi
|
|
22735
22859
|
};
|
|
22736
|
-
//# sourceMappingURL=chunk-
|
|
22860
|
+
//# sourceMappingURL=chunk-SSLPBQWY.js.map
|