@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
package/dist/neatd.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
reconcileDaemonRecordSync,
|
|
4
4
|
startDaemon
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NV4WWJSU.js";
|
|
6
6
|
import {
|
|
7
7
|
listProjects,
|
|
8
8
|
registryPath
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-SSLPBQWY.js";
|
|
10
10
|
import {
|
|
11
11
|
BindAuthorityError,
|
|
12
12
|
__require
|
package/dist/server.cjs
CHANGED
|
@@ -5428,6 +5428,14 @@ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
|
5428
5428
|
function mergeObservedColumns(graph, tableNodeId, columns) {
|
|
5429
5429
|
mergeColumnsAt(graph, tableNodeId, columns, import_types7.Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
|
|
5430
5430
|
}
|
|
5431
|
+
function mergeObservedDeployState(graph, serviceNodeId, state) {
|
|
5432
|
+
if (!graph.hasNode(serviceNodeId)) return;
|
|
5433
|
+
const attrs = {};
|
|
5434
|
+
if (typeof state.image === "string" && state.image.length > 0) attrs["observedImage"] = state.image;
|
|
5435
|
+
if (typeof state.readyReplicas === "number") attrs["observedReadyReplicas"] = state.readyReplicas;
|
|
5436
|
+
if (Object.keys(attrs).length === 0) return;
|
|
5437
|
+
graph.mergeNodeAttributes(serviceNodeId, attrs);
|
|
5438
|
+
}
|
|
5431
5439
|
function ensureDatabaseNode(graph, host, engine) {
|
|
5432
5440
|
const id = (0, import_types7.databaseId)(host);
|
|
5433
5441
|
if (graph.hasNode(id)) return id;
|
|
@@ -6223,6 +6231,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
6223
6231
|
project: options.project
|
|
6224
6232
|
});
|
|
6225
6233
|
if (options.onPolicyTrigger) await options.onPolicyTrigger(graph);
|
|
6234
|
+
if (options.onReconcile) await options.onReconcile(graph);
|
|
6226
6235
|
} catch (err) {
|
|
6227
6236
|
console.error("staleness tick failed", err);
|
|
6228
6237
|
}
|
|
@@ -6383,13 +6392,19 @@ function resolveOwningService(graph, nodeId) {
|
|
|
6383
6392
|
}
|
|
6384
6393
|
return null;
|
|
6385
6394
|
}
|
|
6395
|
+
function isFrontierEdge(e) {
|
|
6396
|
+
return e.provenance === import_types8.Provenance.FRONTIER;
|
|
6397
|
+
}
|
|
6398
|
+
function rankOf(p) {
|
|
6399
|
+
return p === import_types8.Provenance.FRONTIER ? -1 : import_types8.PROV_RANK[p];
|
|
6400
|
+
}
|
|
6386
6401
|
function bestEdgeBySource(graph, edgeIds) {
|
|
6387
6402
|
const best = /* @__PURE__ */ new Map();
|
|
6388
6403
|
for (const id of edgeIds) {
|
|
6389
6404
|
const e = graph.getEdgeAttributes(id);
|
|
6390
|
-
if (isFrontierNode(graph, e.source)) continue;
|
|
6405
|
+
if (isFrontierNode(graph, e.source) || isFrontierEdge(e)) continue;
|
|
6391
6406
|
const cur = best.get(e.source);
|
|
6392
|
-
if (!cur ||
|
|
6407
|
+
if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
|
|
6393
6408
|
best.set(e.source, e);
|
|
6394
6409
|
}
|
|
6395
6410
|
}
|
|
@@ -6399,9 +6414,9 @@ function bestEdgeByTarget(graph, edgeIds) {
|
|
|
6399
6414
|
const best = /* @__PURE__ */ new Map();
|
|
6400
6415
|
for (const id of edgeIds) {
|
|
6401
6416
|
const e = graph.getEdgeAttributes(id);
|
|
6402
|
-
if (isFrontierNode(graph, e.target)) continue;
|
|
6417
|
+
if (isFrontierNode(graph, e.target) || isFrontierEdge(e)) continue;
|
|
6403
6418
|
const cur = best.get(e.target);
|
|
6404
|
-
if (!cur ||
|
|
6419
|
+
if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
|
|
6405
6420
|
best.set(e.target, e);
|
|
6406
6421
|
}
|
|
6407
6422
|
}
|
|
@@ -6411,7 +6426,10 @@ var PROVENANCE_CEILING = {
|
|
|
6411
6426
|
OBSERVED: 1,
|
|
6412
6427
|
INFERRED: 0.7,
|
|
6413
6428
|
EXTRACTED: 0.5,
|
|
6414
|
-
STALE: 0.3
|
|
6429
|
+
STALE: 0.3,
|
|
6430
|
+
// A FRONTIER surface (ADR-226) is a proposal about a cause NEAT could not observe
|
|
6431
|
+
// — the least-trusted claim it makes, below STALE (which was at least once seen).
|
|
6432
|
+
FRONTIER: 0.2
|
|
6415
6433
|
};
|
|
6416
6434
|
function volumeWeight(spanCount) {
|
|
6417
6435
|
if (!spanCount || spanCount <= 0) return 0.5;
|
|
@@ -6646,7 +6664,7 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
6646
6664
|
});
|
|
6647
6665
|
}
|
|
6648
6666
|
function isFailingCallEdge(e) {
|
|
6649
|
-
return e.type === import_types8.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
6667
|
+
return e.type === import_types8.EdgeType.CALLS && !isFrontierEdge(e) && (e.signal?.errorCount ?? 0) > 0;
|
|
6650
6668
|
}
|
|
6651
6669
|
function callSourcesForService(graph, serviceId17) {
|
|
6652
6670
|
const ids = [serviceId17];
|
|
@@ -6669,8 +6687,8 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
6669
6687
|
const ec = e.signal?.errorCount ?? 0;
|
|
6670
6688
|
const cc = curEdge.signal?.errorCount ?? 0;
|
|
6671
6689
|
if (ec !== cc) return ec > cc;
|
|
6672
|
-
if (
|
|
6673
|
-
return
|
|
6690
|
+
if (rankOf(e.provenance) !== rankOf(curEdge.provenance)) {
|
|
6691
|
+
return rankOf(e.provenance) > rankOf(curEdge.provenance);
|
|
6674
6692
|
}
|
|
6675
6693
|
return id < curId;
|
|
6676
6694
|
}
|
|
@@ -6721,11 +6739,11 @@ function dominantStaleCall(graph, serviceId17, visited) {
|
|
|
6721
6739
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
6722
6740
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6723
6741
|
if (e.type !== import_types8.EdgeType.CALLS) continue;
|
|
6724
|
-
if (isFrontierNode(graph, e.target)) continue;
|
|
6742
|
+
if (isFrontierNode(graph, e.target) || isFrontierEdge(e)) continue;
|
|
6725
6743
|
const owner = resolveOwningService(graph, e.target);
|
|
6726
6744
|
if (!owner || visited.has(owner.id)) continue;
|
|
6727
6745
|
const cur = bestByCallee.get(owner.id);
|
|
6728
|
-
if (!cur ||
|
|
6746
|
+
if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
|
|
6729
6747
|
bestByCallee.set(owner.id, e);
|
|
6730
6748
|
}
|
|
6731
6749
|
}
|
|
@@ -6998,6 +7016,7 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
6998
7016
|
for (const edgeId of graph.inboundEdges(n)) {
|
|
6999
7017
|
const e = graph.getEdgeAttributes(edgeId);
|
|
7000
7018
|
if (e.type === import_types8.EdgeType.CONTAINS) continue;
|
|
7019
|
+
if (isFrontierEdge(e)) continue;
|
|
7001
7020
|
errorsFromCallers += e.signal?.errorCount ?? 0;
|
|
7002
7021
|
inboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
|
|
7003
7022
|
if (e.provenance === import_types8.Provenance.STALE) stale = true;
|
|
@@ -7011,6 +7030,7 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
7011
7030
|
for (const edgeId of graph.outboundEdges(n)) {
|
|
7012
7031
|
const e = graph.getEdgeAttributes(edgeId);
|
|
7013
7032
|
if (e.type === import_types8.EdgeType.CONTAINS) continue;
|
|
7033
|
+
if (isFrontierEdge(e)) continue;
|
|
7014
7034
|
outboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
|
|
7015
7035
|
if (e.type === import_types8.EdgeType.CALLS) outboundErrors += e.signal?.errorCount ?? 0;
|
|
7016
7036
|
if (e.provenance === import_types8.Provenance.STALE) stale = true;
|
|
@@ -7044,6 +7064,11 @@ function isSaturated(ctx) {
|
|
|
7044
7064
|
function isBoundaryTimeoutSymptom(ctx) {
|
|
7045
7065
|
return ctx.errorsEmittedHere > 0 && ctx.boundaryTimeout === true && ctx.errorsFromCallers === 0 && ctx.hasOutboundDeps === true && ctx.observedErroringDownstream !== true;
|
|
7046
7066
|
}
|
|
7067
|
+
var UNREACHABLE_INBOUND_ERROR_RATE = 0.5;
|
|
7068
|
+
var UNREACHABLE_MIN_INBOUND = 3;
|
|
7069
|
+
function isUnreachableSeed(ctx) {
|
|
7070
|
+
return ctx.callCount >= UNREACHABLE_MIN_INBOUND && ctx.errorsFromCallers > 0 && ctx.errorsFromCallers >= UNREACHABLE_INBOUND_ERROR_RATE * ctx.callCount && ctx.errorsEmittedHere === 0 && ctx.outboundVolume === 0;
|
|
7071
|
+
}
|
|
7047
7072
|
function classifyNode(ctx) {
|
|
7048
7073
|
if (ctx.errorsEmittedHere > 0) {
|
|
7049
7074
|
if ((ctx.stale || isSaturated(ctx)) && ctx.errorsFromCallers >= ctx.errorsEmittedHere) {
|
|
@@ -7052,6 +7077,7 @@ function classifyNode(ctx) {
|
|
|
7052
7077
|
if (isBoundaryTimeoutSymptom(ctx)) return "symptom-only";
|
|
7053
7078
|
return "primary-failure";
|
|
7054
7079
|
}
|
|
7080
|
+
if (isUnreachableSeed(ctx)) return "unreachable";
|
|
7055
7081
|
if (ctx.errorsFromCallers > 0) return "symptom-only";
|
|
7056
7082
|
return "unrelated";
|
|
7057
7083
|
}
|
|
@@ -7288,22 +7314,44 @@ function routeMatches(declared, incident) {
|
|
|
7288
7314
|
const b = normalizeRoute(incident);
|
|
7289
7315
|
return a === b || a.startsWith(b) || b.startsWith(a);
|
|
7290
7316
|
}
|
|
7291
|
-
function
|
|
7292
|
-
const
|
|
7293
|
-
const callees = declaredOutboundCallees(graph, boundaryNode);
|
|
7317
|
+
function firstDeclaredCallee(graph, node, route) {
|
|
7318
|
+
const callees = declaredOutboundCallees(graph, node);
|
|
7294
7319
|
if (callees.length === 0) return null;
|
|
7295
7320
|
const narrowed = route !== void 0 ? callees.filter((c) => c.route !== void 0 && routeMatches(c.route, route)) : callees;
|
|
7296
7321
|
const chosen = narrowed.length === 1 ? narrowed[0] : callees.length === 1 ? callees[0] : null;
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
if (
|
|
7303
|
-
|
|
7304
|
-
|
|
7322
|
+
return chosen ? chosen.target : null;
|
|
7323
|
+
}
|
|
7324
|
+
function observedOutboundNextServices(graph, nodeId) {
|
|
7325
|
+
const targets = /* @__PURE__ */ new Set();
|
|
7326
|
+
for (const n of nodeScope(graph, nodeId)) {
|
|
7327
|
+
if (!graph.hasNode(n)) continue;
|
|
7328
|
+
for (const edgeId of graph.outboundEdges(n)) {
|
|
7329
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
7330
|
+
if (!DEP_EDGE_TYPES.has(e.type)) continue;
|
|
7331
|
+
if (e.provenance !== import_types8.Provenance.OBSERVED) continue;
|
|
7332
|
+
if (e.target !== nodeId) targets.add(e.target);
|
|
7333
|
+
}
|
|
7305
7334
|
}
|
|
7306
|
-
return
|
|
7335
|
+
return [...targets];
|
|
7336
|
+
}
|
|
7337
|
+
function resolveHangHop(graph, boundaryNode, incidents) {
|
|
7338
|
+
const route = boundaryIncidentRoute(boundaryNode, incidents);
|
|
7339
|
+
const own = firstDeclaredCallee(graph, boundaryNode, route);
|
|
7340
|
+
if (own) {
|
|
7341
|
+
return route !== void 0 ? { source: boundaryNode, target: own, route } : { source: boundaryNode, target: own };
|
|
7342
|
+
}
|
|
7343
|
+
const resolved = observedOutboundNextServices(graph, boundaryNode).map((n) => ({ source: n, target: firstDeclaredCallee(graph, n, route) })).filter((r) => r.target !== null);
|
|
7344
|
+
if (resolved.length === 1) {
|
|
7345
|
+
const hop = resolved[0];
|
|
7346
|
+
return route !== void 0 ? { ...hop, route } : hop;
|
|
7347
|
+
}
|
|
7348
|
+
return null;
|
|
7349
|
+
}
|
|
7350
|
+
function stagedHangProposal(graph, boundaryNode, incidents) {
|
|
7351
|
+
const hop = resolveHangHop(graph, boundaryNode, incidents);
|
|
7352
|
+
if (!hop) return null;
|
|
7353
|
+
if (!graph.hasEdge((0, import_types8.frontierEdgeId)(hop.source, hop.target, import_types8.EdgeType.CALLS))) return null;
|
|
7354
|
+
return hop.route !== void 0 ? { node: hop.target, route: hop.route } : { node: hop.target };
|
|
7307
7355
|
}
|
|
7308
7356
|
function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
7309
7357
|
const legacy = tagged.result;
|
|
@@ -7338,29 +7386,39 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
7338
7386
|
confidence: Math.min(legacy.confidence, 0.4),
|
|
7339
7387
|
...lastProv ? { provenance: lastProv } : {}
|
|
7340
7388
|
});
|
|
7389
|
+
} else if (seedCtx && isUnreachableSeed(seedCtx)) {
|
|
7390
|
+
const name = displayNameOf(seedNode);
|
|
7391
|
+
candidates.push({
|
|
7392
|
+
node: seedNode,
|
|
7393
|
+
classification: "unreachable",
|
|
7394
|
+
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.`,
|
|
7395
|
+
context: seedCtx,
|
|
7396
|
+
confidence: legacy.confidence,
|
|
7397
|
+
provenance: import_types8.Provenance.OBSERVED
|
|
7398
|
+
});
|
|
7341
7399
|
} else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
|
|
7342
|
-
const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
|
|
7343
7400
|
const seedName = displayNameOf(seedNode);
|
|
7344
|
-
const
|
|
7345
|
-
|
|
7346
|
-
const causeName = displayNameOf(pointer.node);
|
|
7347
|
-
candidates.push({
|
|
7348
|
-
node: pointer.node,
|
|
7349
|
-
classification: "primary-failure",
|
|
7350
|
-
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.`,
|
|
7351
|
-
context: nodeContext(graph, pointer.node, incidents, now),
|
|
7352
|
-
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.EXTRACTED),
|
|
7353
|
-
provenance: import_types8.Provenance.INFERRED
|
|
7354
|
-
});
|
|
7355
|
-
}
|
|
7401
|
+
const proposal = stagedHangProposal(graph, seedNode, incidents);
|
|
7402
|
+
const routeNote = proposal?.route ? ` serving ${proposal.route}` : "";
|
|
7356
7403
|
candidates.push({
|
|
7357
7404
|
node: seedNode,
|
|
7358
7405
|
classification: "symptom-only",
|
|
7359
|
-
reason:
|
|
7406
|
+
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.`,
|
|
7360
7407
|
context: seedCtx,
|
|
7361
7408
|
confidence: Math.min(legacy.confidence, 0.3),
|
|
7362
7409
|
...lastProv ? { provenance: lastProv } : {}
|
|
7363
7410
|
});
|
|
7411
|
+
if (proposal) {
|
|
7412
|
+
const causeName = displayNameOf(proposal.node);
|
|
7413
|
+
candidates.push({
|
|
7414
|
+
node: proposal.node,
|
|
7415
|
+
classification: "primary-failure",
|
|
7416
|
+
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.`,
|
|
7417
|
+
context: nodeContext(graph, proposal.node, incidents, now),
|
|
7418
|
+
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.FRONTIER),
|
|
7419
|
+
provenance: import_types8.Provenance.FRONTIER
|
|
7420
|
+
});
|
|
7421
|
+
}
|
|
7364
7422
|
} else if (staleChain) {
|
|
7365
7423
|
const culprit = staleChain.culprit;
|
|
7366
7424
|
const culpritName = displayNameOf(culprit);
|
|
@@ -7424,6 +7482,9 @@ function fixRecommendationForTop(top, seedNode, legacy) {
|
|
|
7424
7482
|
return legacy.fixRecommendation;
|
|
7425
7483
|
}
|
|
7426
7484
|
const name = top.node.replace(/^service:/, "");
|
|
7485
|
+
if (top.classification === "symptom-only" && top.context.boundaryTimeout === true) {
|
|
7486
|
+
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.`;
|
|
7487
|
+
}
|
|
7427
7488
|
if (top.provenance === import_types8.Provenance.STALE) {
|
|
7428
7489
|
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}.`;
|
|
7429
7490
|
}
|
|
@@ -7687,6 +7748,25 @@ function detectColumnDrift(node) {
|
|
|
7687
7748
|
}
|
|
7688
7749
|
return out;
|
|
7689
7750
|
}
|
|
7751
|
+
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.";
|
|
7752
|
+
var DEPLOY_DIVERGENCE_CONFIDENCE = 0.9;
|
|
7753
|
+
function detectDeployDivergence(svc) {
|
|
7754
|
+
const out = [];
|
|
7755
|
+
if (typeof svc.declaredImage === "string" && typeof svc.observedImage === "string" && svc.declaredImage !== svc.observedImage) {
|
|
7756
|
+
out.push({
|
|
7757
|
+
type: "deploy-mismatch",
|
|
7758
|
+
kind: "image",
|
|
7759
|
+
source: svc.id,
|
|
7760
|
+
target: svc.id,
|
|
7761
|
+
declaredImage: svc.declaredImage,
|
|
7762
|
+
observedImage: svc.observedImage,
|
|
7763
|
+
confidence: DEPLOY_DIVERGENCE_CONFIDENCE,
|
|
7764
|
+
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.`,
|
|
7765
|
+
recommendation: RECOMMENDATION_DEPLOY_IMAGE_MISMATCH
|
|
7766
|
+
});
|
|
7767
|
+
}
|
|
7768
|
+
return out;
|
|
7769
|
+
}
|
|
7690
7770
|
var SYMBOL_MISMATCH_PATTERNS = [
|
|
7691
7771
|
{
|
|
7692
7772
|
// "'ListProductsResponse' object has no attribute 'products_list'" and kin.
|
|
@@ -8014,6 +8094,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
8014
8094
|
const svc = n;
|
|
8015
8095
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
8016
8096
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
8097
|
+
for (const d of detectDeployDivergence(svc)) all.push(d);
|
|
8017
8098
|
return;
|
|
8018
8099
|
}
|
|
8019
8100
|
if (n.type === import_types9.NodeType.InfraNode && n.kind === "sql-table") {
|
|
@@ -8054,7 +8135,12 @@ function computeDivergences(graph, opts = {}) {
|
|
|
8054
8135
|
// orders last so at equal confidence the structural and symbol divergences
|
|
8055
8136
|
// lead, per the contract ("rank below the definitive structural and symbol
|
|
8056
8137
|
// divergences").
|
|
8057
|
-
"observed-failing": 6
|
|
8138
|
+
"observed-failing": 6,
|
|
8139
|
+
// Deploy mismatch (ADR-225) is a definitive structural declared-vs-observed
|
|
8140
|
+
// divergence carrying high confidence (0.9), so the confidence sort already
|
|
8141
|
+
// places it among the structural leaders; this slot only breaks an exact
|
|
8142
|
+
// confidence tie and sits last as a stable tiebreaker.
|
|
8143
|
+
"deploy-mismatch": 7
|
|
8058
8144
|
};
|
|
8059
8145
|
filtered.sort((a, b) => {
|
|
8060
8146
|
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
@@ -18602,6 +18688,11 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
18602
18688
|
unresolved++;
|
|
18603
18689
|
continue;
|
|
18604
18690
|
}
|
|
18691
|
+
if (signal.deployState) {
|
|
18692
|
+
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
18693
|
+
mergeObservedDeployState(graph, resolved.targetNodeId, signal.deployState);
|
|
18694
|
+
continue;
|
|
18695
|
+
}
|
|
18605
18696
|
if (signal.incident) {
|
|
18606
18697
|
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
18607
18698
|
if (!ctx.errorsPath || !graph.hasNode(resolved.targetNodeId)) {
|