@neat.is/core 0.9.12 → 0.9.13
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-BKSM2YLV.js} +35 -4
- package/dist/{chunk-WE3AFQYL.js.map → chunk-BKSM2YLV.js.map} +1 -1
- package/dist/{chunk-RBZNXA5L.js → chunk-RL6H3VVD.js} +56 -2
- package/dist/chunk-RL6H3VVD.js.map +1 -0
- package/dist/cli.cjs +538 -29
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +18 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +87 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +87 -3
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +55 -1
- 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/index.cjs
CHANGED
|
@@ -1945,6 +1945,11 @@ function isSaturated(ctx) {
|
|
|
1945
1945
|
function isBoundaryTimeoutSymptom(ctx) {
|
|
1946
1946
|
return ctx.errorsEmittedHere > 0 && ctx.boundaryTimeout === true && ctx.errorsFromCallers === 0 && ctx.hasOutboundDeps === true && ctx.observedErroringDownstream !== true;
|
|
1947
1947
|
}
|
|
1948
|
+
var UNREACHABLE_INBOUND_ERROR_RATE = 0.5;
|
|
1949
|
+
var UNREACHABLE_MIN_INBOUND = 3;
|
|
1950
|
+
function isUnreachableSeed(ctx) {
|
|
1951
|
+
return ctx.callCount >= UNREACHABLE_MIN_INBOUND && ctx.errorsFromCallers > 0 && ctx.errorsFromCallers >= UNREACHABLE_INBOUND_ERROR_RATE * ctx.callCount && ctx.errorsEmittedHere === 0 && ctx.outboundVolume === 0;
|
|
1952
|
+
}
|
|
1948
1953
|
function classifyNode(ctx) {
|
|
1949
1954
|
if (ctx.errorsEmittedHere > 0) {
|
|
1950
1955
|
if ((ctx.stale || isSaturated(ctx)) && ctx.errorsFromCallers >= ctx.errorsEmittedHere) {
|
|
@@ -1953,6 +1958,7 @@ function classifyNode(ctx) {
|
|
|
1953
1958
|
if (isBoundaryTimeoutSymptom(ctx)) return "symptom-only";
|
|
1954
1959
|
return "primary-failure";
|
|
1955
1960
|
}
|
|
1961
|
+
if (isUnreachableSeed(ctx)) return "unreachable";
|
|
1956
1962
|
if (ctx.errorsFromCallers > 0) return "symptom-only";
|
|
1957
1963
|
return "unrelated";
|
|
1958
1964
|
}
|
|
@@ -2239,6 +2245,16 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
2239
2245
|
confidence: Math.min(legacy.confidence, 0.4),
|
|
2240
2246
|
...lastProv ? { provenance: lastProv } : {}
|
|
2241
2247
|
});
|
|
2248
|
+
} else if (seedCtx && isUnreachableSeed(seedCtx)) {
|
|
2249
|
+
const name = displayNameOf(seedNode);
|
|
2250
|
+
candidates.push({
|
|
2251
|
+
node: seedNode,
|
|
2252
|
+
classification: "unreachable",
|
|
2253
|
+
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.`,
|
|
2254
|
+
context: seedCtx,
|
|
2255
|
+
confidence: legacy.confidence,
|
|
2256
|
+
provenance: import_types.Provenance.OBSERVED
|
|
2257
|
+
});
|
|
2242
2258
|
} else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
|
|
2243
2259
|
const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
|
|
2244
2260
|
const seedName = displayNameOf(seedNode);
|
|
@@ -6286,6 +6302,14 @@ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
|
6286
6302
|
function mergeObservedColumns(graph, tableNodeId, columns) {
|
|
6287
6303
|
mergeColumnsAt(graph, tableNodeId, columns, import_types8.Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
|
|
6288
6304
|
}
|
|
6305
|
+
function mergeObservedDeployState(graph, serviceNodeId, state) {
|
|
6306
|
+
if (!graph.hasNode(serviceNodeId)) return;
|
|
6307
|
+
const attrs = {};
|
|
6308
|
+
if (typeof state.image === "string" && state.image.length > 0) attrs["observedImage"] = state.image;
|
|
6309
|
+
if (typeof state.readyReplicas === "number") attrs["observedReadyReplicas"] = state.readyReplicas;
|
|
6310
|
+
if (Object.keys(attrs).length === 0) return;
|
|
6311
|
+
graph.mergeNodeAttributes(serviceNodeId, attrs);
|
|
6312
|
+
}
|
|
6289
6313
|
function ensureDatabaseNode(graph, host, engine) {
|
|
6290
6314
|
const id = (0, import_types8.databaseId)(host);
|
|
6291
6315
|
if (graph.hasNode(id)) return id;
|
|
@@ -16956,6 +16980,25 @@ function detectColumnDrift(node) {
|
|
|
16956
16980
|
}
|
|
16957
16981
|
return out;
|
|
16958
16982
|
}
|
|
16983
|
+
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.";
|
|
16984
|
+
var DEPLOY_DIVERGENCE_CONFIDENCE = 0.9;
|
|
16985
|
+
function detectDeployDivergence(svc) {
|
|
16986
|
+
const out = [];
|
|
16987
|
+
if (typeof svc.declaredImage === "string" && typeof svc.observedImage === "string" && svc.declaredImage !== svc.observedImage) {
|
|
16988
|
+
out.push({
|
|
16989
|
+
type: "deploy-mismatch",
|
|
16990
|
+
kind: "image",
|
|
16991
|
+
source: svc.id,
|
|
16992
|
+
target: svc.id,
|
|
16993
|
+
declaredImage: svc.declaredImage,
|
|
16994
|
+
observedImage: svc.observedImage,
|
|
16995
|
+
confidence: DEPLOY_DIVERGENCE_CONFIDENCE,
|
|
16996
|
+
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.`,
|
|
16997
|
+
recommendation: RECOMMENDATION_DEPLOY_IMAGE_MISMATCH
|
|
16998
|
+
});
|
|
16999
|
+
}
|
|
17000
|
+
return out;
|
|
17001
|
+
}
|
|
16959
17002
|
var SYMBOL_MISMATCH_PATTERNS = [
|
|
16960
17003
|
{
|
|
16961
17004
|
// "'ListProductsResponse' object has no attribute 'products_list'" and kin.
|
|
@@ -17283,6 +17326,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
17283
17326
|
const svc = n;
|
|
17284
17327
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
17285
17328
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
17329
|
+
for (const d of detectDeployDivergence(svc)) all.push(d);
|
|
17286
17330
|
return;
|
|
17287
17331
|
}
|
|
17288
17332
|
if (n.type === import_types60.NodeType.InfraNode && n.kind === "sql-table") {
|
|
@@ -17323,7 +17367,12 @@ function computeDivergences(graph, opts = {}) {
|
|
|
17323
17367
|
// orders last so at equal confidence the structural and symbol divergences
|
|
17324
17368
|
// lead, per the contract ("rank below the definitive structural and symbol
|
|
17325
17369
|
// divergences").
|
|
17326
|
-
"observed-failing": 6
|
|
17370
|
+
"observed-failing": 6,
|
|
17371
|
+
// Deploy mismatch (ADR-225) is a definitive structural declared-vs-observed
|
|
17372
|
+
// divergence carrying high confidence (0.9), so the confidence sort already
|
|
17373
|
+
// places it among the structural leaders; this slot only breaks an exact
|
|
17374
|
+
// confidence tie and sits last as a stable tiebreaker.
|
|
17375
|
+
"deploy-mismatch": 7
|
|
17327
17376
|
};
|
|
17328
17377
|
filtered.sort((a, b) => {
|
|
17329
17378
|
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
@@ -18973,6 +19022,11 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
18973
19022
|
unresolved++;
|
|
18974
19023
|
continue;
|
|
18975
19024
|
}
|
|
19025
|
+
if (signal.deployState) {
|
|
19026
|
+
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
19027
|
+
mergeObservedDeployState(graph, resolved.targetNodeId, signal.deployState);
|
|
19028
|
+
continue;
|
|
19029
|
+
}
|
|
18976
19030
|
if (signal.incident) {
|
|
18977
19031
|
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
18978
19032
|
if (!ctx.errorsPath || !graph.hasNode(resolved.targetNodeId)) {
|
|
@@ -23432,6 +23486,7 @@ var IMAGE_PULL_REASONS = /* @__PURE__ */ new Set(["ImagePullBackOff", "ErrImageP
|
|
|
23432
23486
|
var CRASH_LOOP_REASON = "CrashLoopBackOff";
|
|
23433
23487
|
var FIELD_SEP5 = "\0";
|
|
23434
23488
|
var K8S_TARGET_KIND = "k8s-workload";
|
|
23489
|
+
var K8S_DEPLOY_STATE = "deploy-state";
|
|
23435
23490
|
function packK8sTargetName(identity) {
|
|
23436
23491
|
return [identity.serviceName, identity.fault].join(FIELD_SEP5);
|
|
23437
23492
|
}
|
|
@@ -23563,11 +23618,40 @@ function mapDeploymentToSignal(deployment, pods, config) {
|
|
|
23563
23618
|
}
|
|
23564
23619
|
};
|
|
23565
23620
|
}
|
|
23621
|
+
function observedDeployState(deployment, pods) {
|
|
23622
|
+
const readyReplicas = typeof deployment.status?.readyReplicas === "number" ? deployment.status.readyReplicas : 0;
|
|
23623
|
+
let image;
|
|
23624
|
+
for (const pod of podsForDeployment(deployment, pods)) {
|
|
23625
|
+
for (const cs of pod.status?.containerStatuses ?? []) {
|
|
23626
|
+
if (cs.state?.running && typeof cs.image === "string" && cs.image.length > 0) {
|
|
23627
|
+
image = cs.image;
|
|
23628
|
+
break;
|
|
23629
|
+
}
|
|
23630
|
+
}
|
|
23631
|
+
if (image) break;
|
|
23632
|
+
}
|
|
23633
|
+
return image !== void 0 ? { image, readyReplicas } : { readyReplicas };
|
|
23634
|
+
}
|
|
23635
|
+
function deployStateSignal(deployment, pods, config) {
|
|
23636
|
+
const name = deployment.metadata?.name;
|
|
23637
|
+
if (typeof name !== "string" || name.length === 0) return null;
|
|
23638
|
+
const serviceName = serviceNameFor(deployment, config);
|
|
23639
|
+
return {
|
|
23640
|
+
targetKind: K8S_TARGET_KIND,
|
|
23641
|
+
targetName: packK8sTargetName({ serviceName, fault: K8S_DEPLOY_STATE }),
|
|
23642
|
+
callCount: 0,
|
|
23643
|
+
errorCount: 0,
|
|
23644
|
+
lastObservedIso: nowIso2(),
|
|
23645
|
+
deployState: observedDeployState(deployment, pods)
|
|
23646
|
+
};
|
|
23647
|
+
}
|
|
23566
23648
|
function mapWorkloadsToSignals(deployments, pods, config) {
|
|
23567
23649
|
const out = [];
|
|
23568
23650
|
for (const deployment of deployments) {
|
|
23569
|
-
const
|
|
23570
|
-
if (
|
|
23651
|
+
const deployState = deployStateSignal(deployment, pods, config);
|
|
23652
|
+
if (deployState) out.push(deployState);
|
|
23653
|
+
const incident = mapDeploymentToSignal(deployment, pods, config);
|
|
23654
|
+
if (incident) out.push(incident);
|
|
23571
23655
|
}
|
|
23572
23656
|
return out;
|
|
23573
23657
|
}
|