@neat.is/core 0.9.13-dev.20260901 → 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.
@@ -4413,6 +4413,14 @@ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
4413
4413
  function mergeObservedColumns(graph, tableNodeId, columns) {
4414
4414
  mergeColumnsAt(graph, tableNodeId, columns, Provenance5.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
4415
4415
  }
4416
+ function mergeObservedDeployState(graph, serviceNodeId, state) {
4417
+ if (!graph.hasNode(serviceNodeId)) return;
4418
+ const attrs = {};
4419
+ if (typeof state.image === "string" && state.image.length > 0) attrs["observedImage"] = state.image;
4420
+ if (typeof state.readyReplicas === "number") attrs["observedReadyReplicas"] = state.readyReplicas;
4421
+ if (Object.keys(attrs).length === 0) return;
4422
+ graph.mergeNodeAttributes(serviceNodeId, attrs);
4423
+ }
4416
4424
  function ensureDatabaseNode(graph, host, engine) {
4417
4425
  const id = databaseId(host);
4418
4426
  if (graph.hasNode(id)) return id;
@@ -6051,6 +6059,11 @@ function isSaturated(ctx) {
6051
6059
  function isBoundaryTimeoutSymptom(ctx) {
6052
6060
  return ctx.errorsEmittedHere > 0 && ctx.boundaryTimeout === true && ctx.errorsFromCallers === 0 && ctx.hasOutboundDeps === true && ctx.observedErroringDownstream !== true;
6053
6061
  }
6062
+ var UNREACHABLE_INBOUND_ERROR_RATE = 0.5;
6063
+ var UNREACHABLE_MIN_INBOUND = 3;
6064
+ function isUnreachableSeed(ctx) {
6065
+ return ctx.callCount >= UNREACHABLE_MIN_INBOUND && ctx.errorsFromCallers > 0 && ctx.errorsFromCallers >= UNREACHABLE_INBOUND_ERROR_RATE * ctx.callCount && ctx.errorsEmittedHere === 0 && ctx.outboundVolume === 0;
6066
+ }
6054
6067
  function classifyNode(ctx) {
6055
6068
  if (ctx.errorsEmittedHere > 0) {
6056
6069
  if ((ctx.stale || isSaturated(ctx)) && ctx.errorsFromCallers >= ctx.errorsEmittedHere) {
@@ -6059,6 +6072,7 @@ function classifyNode(ctx) {
6059
6072
  if (isBoundaryTimeoutSymptom(ctx)) return "symptom-only";
6060
6073
  return "primary-failure";
6061
6074
  }
6075
+ if (isUnreachableSeed(ctx)) return "unreachable";
6062
6076
  if (ctx.errorsFromCallers > 0) return "symptom-only";
6063
6077
  return "unrelated";
6064
6078
  }
@@ -6345,6 +6359,16 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
6345
6359
  confidence: Math.min(legacy.confidence, 0.4),
6346
6360
  ...lastProv ? { provenance: lastProv } : {}
6347
6361
  });
6362
+ } else if (seedCtx && isUnreachableSeed(seedCtx)) {
6363
+ const name = displayNameOf(seedNode);
6364
+ candidates.push({
6365
+ node: seedNode,
6366
+ classification: "unreachable",
6367
+ 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.`,
6368
+ context: seedCtx,
6369
+ confidence: legacy.confidence,
6370
+ provenance: Provenance6.OBSERVED
6371
+ });
6348
6372
  } else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
6349
6373
  const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
6350
6374
  const seedName = displayNameOf(seedNode);
@@ -15708,6 +15732,25 @@ function detectColumnDrift(node) {
15708
15732
  }
15709
15733
  return out;
15710
15734
  }
15735
+ 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.";
15736
+ var DEPLOY_DIVERGENCE_CONFIDENCE = 0.9;
15737
+ function detectDeployDivergence(svc) {
15738
+ const out = [];
15739
+ if (typeof svc.declaredImage === "string" && typeof svc.observedImage === "string" && svc.declaredImage !== svc.observedImage) {
15740
+ out.push({
15741
+ type: "deploy-mismatch",
15742
+ kind: "image",
15743
+ source: svc.id,
15744
+ target: svc.id,
15745
+ declaredImage: svc.declaredImage,
15746
+ observedImage: svc.observedImage,
15747
+ confidence: DEPLOY_DIVERGENCE_CONFIDENCE,
15748
+ 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.`,
15749
+ recommendation: RECOMMENDATION_DEPLOY_IMAGE_MISMATCH
15750
+ });
15751
+ }
15752
+ return out;
15753
+ }
15711
15754
  var SYMBOL_MISMATCH_PATTERNS = [
15712
15755
  {
15713
15756
  // "'ListProductsResponse' object has no attribute 'products_list'" and kin.
@@ -16035,6 +16078,7 @@ function computeDivergences(graph, opts = {}) {
16035
16078
  const svc = n;
16036
16079
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
16037
16080
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
16081
+ for (const d of detectDeployDivergence(svc)) all.push(d);
16038
16082
  return;
16039
16083
  }
16040
16084
  if (n.type === NodeType30.InfraNode && n.kind === "sql-table") {
@@ -16075,7 +16119,12 @@ function computeDivergences(graph, opts = {}) {
16075
16119
  // orders last so at equal confidence the structural and symbol divergences
16076
16120
  // lead, per the contract ("rank below the definitive structural and symbol
16077
16121
  // divergences").
16078
- "observed-failing": 6
16122
+ "observed-failing": 6,
16123
+ // Deploy mismatch (ADR-225) is a definitive structural declared-vs-observed
16124
+ // divergence carrying high confidence (0.9), so the confidence sort already
16125
+ // places it among the structural leaders; this slot only breaks an exact
16126
+ // confidence tie and sits last as a stable tiebreaker.
16127
+ "deploy-mismatch": 7
16079
16128
  };
16080
16129
  filtered.sort((a, b) => {
16081
16130
  if (b.confidence !== a.confidence) return b.confidence - a.confidence;
@@ -18378,6 +18427,11 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
18378
18427
  unresolved++;
18379
18428
  continue;
18380
18429
  }
18430
+ if (signal.deployState) {
18431
+ ensureServiceNode(graph, resolved.serviceName, NO_ENV);
18432
+ mergeObservedDeployState(graph, resolved.targetNodeId, signal.deployState);
18433
+ continue;
18434
+ }
18381
18435
  if (signal.incident) {
18382
18436
  ensureServiceNode(graph, resolved.serviceName, NO_ENV);
18383
18437
  if (!ctx.errorsPath || !graph.hasNode(resolved.targetNodeId)) {
@@ -22733,4 +22787,4 @@ export {
22733
22787
  deprovisionConnector,
22734
22788
  buildApi
22735
22789
  };
22736
- //# sourceMappingURL=chunk-RBZNXA5L.js.map
22790
+ //# sourceMappingURL=chunk-RL6H3VVD.js.map