@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.
- 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/cli.cjs
CHANGED
|
@@ -1953,6 +1953,11 @@ function isSaturated(ctx) {
|
|
|
1953
1953
|
function isBoundaryTimeoutSymptom(ctx) {
|
|
1954
1954
|
return ctx.errorsEmittedHere > 0 && ctx.boundaryTimeout === true && ctx.errorsFromCallers === 0 && ctx.hasOutboundDeps === true && ctx.observedErroringDownstream !== true;
|
|
1955
1955
|
}
|
|
1956
|
+
var UNREACHABLE_INBOUND_ERROR_RATE = 0.5;
|
|
1957
|
+
var UNREACHABLE_MIN_INBOUND = 3;
|
|
1958
|
+
function isUnreachableSeed(ctx) {
|
|
1959
|
+
return ctx.callCount >= UNREACHABLE_MIN_INBOUND && ctx.errorsFromCallers > 0 && ctx.errorsFromCallers >= UNREACHABLE_INBOUND_ERROR_RATE * ctx.callCount && ctx.errorsEmittedHere === 0 && ctx.outboundVolume === 0;
|
|
1960
|
+
}
|
|
1956
1961
|
function classifyNode(ctx) {
|
|
1957
1962
|
if (ctx.errorsEmittedHere > 0) {
|
|
1958
1963
|
if ((ctx.stale || isSaturated(ctx)) && ctx.errorsFromCallers >= ctx.errorsEmittedHere) {
|
|
@@ -1961,6 +1966,7 @@ function classifyNode(ctx) {
|
|
|
1961
1966
|
if (isBoundaryTimeoutSymptom(ctx)) return "symptom-only";
|
|
1962
1967
|
return "primary-failure";
|
|
1963
1968
|
}
|
|
1969
|
+
if (isUnreachableSeed(ctx)) return "unreachable";
|
|
1964
1970
|
if (ctx.errorsFromCallers > 0) return "symptom-only";
|
|
1965
1971
|
return "unrelated";
|
|
1966
1972
|
}
|
|
@@ -2247,6 +2253,16 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
2247
2253
|
confidence: Math.min(legacy.confidence, 0.4),
|
|
2248
2254
|
...lastProv ? { provenance: lastProv } : {}
|
|
2249
2255
|
});
|
|
2256
|
+
} else if (seedCtx && isUnreachableSeed(seedCtx)) {
|
|
2257
|
+
const name = displayNameOf(seedNode);
|
|
2258
|
+
candidates.push({
|
|
2259
|
+
node: seedNode,
|
|
2260
|
+
classification: "unreachable",
|
|
2261
|
+
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.`,
|
|
2262
|
+
context: seedCtx,
|
|
2263
|
+
confidence: legacy.confidence,
|
|
2264
|
+
provenance: import_types.Provenance.OBSERVED
|
|
2265
|
+
});
|
|
2250
2266
|
} else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
|
|
2251
2267
|
const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
|
|
2252
2268
|
const seedName = displayNameOf(seedNode);
|
|
@@ -6045,8 +6061,8 @@ function pickContainingSymbol(candidates, fn) {
|
|
|
6045
6061
|
return b.symbol.span.startLine - a.symbol.span.startLine;
|
|
6046
6062
|
};
|
|
6047
6063
|
if (fn) {
|
|
6048
|
-
const
|
|
6049
|
-
if (
|
|
6064
|
+
const named2 = candidates.filter((c) => terminalName(c.symbol.qualname) === fn);
|
|
6065
|
+
if (named2.length > 0) return [...named2].sort(bySpan)[0].id;
|
|
6050
6066
|
}
|
|
6051
6067
|
return [...candidates].sort(bySpan)[0].id;
|
|
6052
6068
|
}
|
|
@@ -6306,6 +6322,14 @@ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
|
6306
6322
|
function mergeObservedColumns(graph, tableNodeId, columns) {
|
|
6307
6323
|
mergeColumnsAt(graph, tableNodeId, columns, import_types8.Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
|
|
6308
6324
|
}
|
|
6325
|
+
function mergeObservedDeployState(graph, serviceNodeId, state) {
|
|
6326
|
+
if (!graph.hasNode(serviceNodeId)) return;
|
|
6327
|
+
const attrs = {};
|
|
6328
|
+
if (typeof state.image === "string" && state.image.length > 0) attrs["observedImage"] = state.image;
|
|
6329
|
+
if (typeof state.readyReplicas === "number") attrs["observedReadyReplicas"] = state.readyReplicas;
|
|
6330
|
+
if (Object.keys(attrs).length === 0) return;
|
|
6331
|
+
graph.mergeNodeAttributes(serviceNodeId, attrs);
|
|
6332
|
+
}
|
|
6309
6333
|
function ensureDatabaseNode(graph, host, engine) {
|
|
6310
6334
|
const id = (0, import_types8.databaseId)(host);
|
|
6311
6335
|
if (graph.hasNode(id)) return id;
|
|
@@ -9465,10 +9489,10 @@ function collectNamedImports(root) {
|
|
|
9465
9489
|
const clause = node.namedChild(i);
|
|
9466
9490
|
if (clause?.type !== "import_clause") continue;
|
|
9467
9491
|
for (let j = 0; j < clause.namedChildCount; j++) {
|
|
9468
|
-
const
|
|
9469
|
-
if (
|
|
9470
|
-
for (let k = 0; k <
|
|
9471
|
-
const spec =
|
|
9492
|
+
const named2 = clause.namedChild(j);
|
|
9493
|
+
if (named2?.type !== "named_imports") continue;
|
|
9494
|
+
for (let k = 0; k < named2.namedChildCount; k++) {
|
|
9495
|
+
const spec = named2.namedChild(k);
|
|
9472
9496
|
if (spec?.type !== "import_specifier") continue;
|
|
9473
9497
|
let isType = false;
|
|
9474
9498
|
for (let t = 0; t < spec.childCount; t++) {
|
|
@@ -12102,8 +12126,8 @@ function parseImportBindings(content) {
|
|
|
12102
12126
|
out.push({ local: ns[1], kind: "namespace", specifier: spec });
|
|
12103
12127
|
continue;
|
|
12104
12128
|
}
|
|
12105
|
-
const
|
|
12106
|
-
if (
|
|
12129
|
+
const named2 = /\{([^}]*)\}/.exec(clause);
|
|
12130
|
+
if (named2) parseDestructure(named2[1], spec, out);
|
|
12107
12131
|
const def = /^(\w+)\s*(?:,|$)/.exec(clause);
|
|
12108
12132
|
if (def && !clause.startsWith("{")) out.push({ local: def[1], kind: "default", specifier: spec });
|
|
12109
12133
|
}
|
|
@@ -12123,8 +12147,8 @@ function fileExportsOf(content, pluralizeOn) {
|
|
|
12123
12147
|
for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name);
|
|
12124
12148
|
const byName = new Map(byVar);
|
|
12125
12149
|
let def;
|
|
12126
|
-
const
|
|
12127
|
-
if (
|
|
12150
|
+
const named2 = /(?:module\.exports|export\s+default)\s*=\s*(\w+)\b/.exec(content);
|
|
12151
|
+
if (named2 && byVar.has(named2[1])) def = byVar.get(named2[1]);
|
|
12128
12152
|
if (!def) {
|
|
12129
12153
|
const inline = /(?:module\.exports|export\s+default)\s*=\s*(?:await\s+)?(?:\w+\s*\.\s*)?model\s*\(\s*['"`]([\w$]+)['"`]/.exec(content);
|
|
12130
12154
|
if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]) : inline[1];
|
|
@@ -16494,6 +16518,25 @@ function detectColumnDrift(node) {
|
|
|
16494
16518
|
}
|
|
16495
16519
|
return out;
|
|
16496
16520
|
}
|
|
16521
|
+
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.";
|
|
16522
|
+
var DEPLOY_DIVERGENCE_CONFIDENCE = 0.9;
|
|
16523
|
+
function detectDeployDivergence(svc) {
|
|
16524
|
+
const out = [];
|
|
16525
|
+
if (typeof svc.declaredImage === "string" && typeof svc.observedImage === "string" && svc.declaredImage !== svc.observedImage) {
|
|
16526
|
+
out.push({
|
|
16527
|
+
type: "deploy-mismatch",
|
|
16528
|
+
kind: "image",
|
|
16529
|
+
source: svc.id,
|
|
16530
|
+
target: svc.id,
|
|
16531
|
+
declaredImage: svc.declaredImage,
|
|
16532
|
+
observedImage: svc.observedImage,
|
|
16533
|
+
confidence: DEPLOY_DIVERGENCE_CONFIDENCE,
|
|
16534
|
+
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.`,
|
|
16535
|
+
recommendation: RECOMMENDATION_DEPLOY_IMAGE_MISMATCH
|
|
16536
|
+
});
|
|
16537
|
+
}
|
|
16538
|
+
return out;
|
|
16539
|
+
}
|
|
16497
16540
|
var SYMBOL_MISMATCH_PATTERNS = [
|
|
16498
16541
|
{
|
|
16499
16542
|
// "'ListProductsResponse' object has no attribute 'products_list'" and kin.
|
|
@@ -16821,6 +16864,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
16821
16864
|
const svc = n;
|
|
16822
16865
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
16823
16866
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
16867
|
+
for (const d of detectDeployDivergence(svc)) all.push(d);
|
|
16824
16868
|
return;
|
|
16825
16869
|
}
|
|
16826
16870
|
if (n.type === import_types59.NodeType.InfraNode && n.kind === "sql-table") {
|
|
@@ -16861,7 +16905,12 @@ function computeDivergences(graph, opts = {}) {
|
|
|
16861
16905
|
// orders last so at equal confidence the structural and symbol divergences
|
|
16862
16906
|
// lead, per the contract ("rank below the definitive structural and symbol
|
|
16863
16907
|
// divergences").
|
|
16864
|
-
"observed-failing": 6
|
|
16908
|
+
"observed-failing": 6,
|
|
16909
|
+
// Deploy mismatch (ADR-225) is a definitive structural declared-vs-observed
|
|
16910
|
+
// divergence carrying high confidence (0.9), so the confidence sort already
|
|
16911
|
+
// places it among the structural leaders; this slot only breaks an exact
|
|
16912
|
+
// confidence tie and sits last as a stable tiebreaker.
|
|
16913
|
+
"deploy-mismatch": 7
|
|
16865
16914
|
};
|
|
16866
16915
|
filtered.sort((a, b) => {
|
|
16867
16916
|
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
@@ -17956,11 +18005,11 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
|
|
|
17956
18005
|
if (labelTokens.size === 0) return;
|
|
17957
18006
|
let matched = 0;
|
|
17958
18007
|
for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
|
|
17959
|
-
const
|
|
17960
|
-
if (matched === 0 && !
|
|
18008
|
+
const named2 = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
|
|
18009
|
+
if (matched === 0 && !named2) return;
|
|
17961
18010
|
const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
|
|
17962
|
-
const via =
|
|
17963
|
-
const score =
|
|
18011
|
+
const via = named2 ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
|
|
18012
|
+
const score = named2 ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
|
|
17964
18013
|
consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
|
|
17965
18014
|
});
|
|
17966
18015
|
if (searchIndex) {
|
|
@@ -19300,6 +19349,11 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
19300
19349
|
unresolved++;
|
|
19301
19350
|
continue;
|
|
19302
19351
|
}
|
|
19352
|
+
if (signal.deployState) {
|
|
19353
|
+
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
19354
|
+
mergeObservedDeployState(graph, resolved.targetNodeId, signal.deployState);
|
|
19355
|
+
continue;
|
|
19356
|
+
}
|
|
19303
19357
|
if (signal.incident) {
|
|
19304
19358
|
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
19305
19359
|
if (!ctx.errorsPath || !graph.hasNode(resolved.targetNodeId)) {
|
|
@@ -19833,7 +19887,7 @@ function tableNameFromQueryText(query) {
|
|
|
19833
19887
|
if (SYSTEM_SCHEMA_PREFIXES.some((prefix) => lower.startsWith(prefix))) return null;
|
|
19834
19888
|
return name;
|
|
19835
19889
|
}
|
|
19836
|
-
function diffPgStatStatementsToSignals(rows, previous,
|
|
19890
|
+
function diffPgStatStatementsToSignals(rows, previous, nowIso3) {
|
|
19837
19891
|
const signals = [];
|
|
19838
19892
|
const seen = /* @__PURE__ */ new Set();
|
|
19839
19893
|
if (!Array.isArray(rows)) return signals;
|
|
@@ -19855,7 +19909,7 @@ function diffPgStatStatementsToSignals(rows, previous, nowIso2) {
|
|
|
19855
19909
|
targetName: table,
|
|
19856
19910
|
callCount: delta,
|
|
19857
19911
|
errorCount: 0,
|
|
19858
|
-
lastObservedIso:
|
|
19912
|
+
lastObservedIso: nowIso3,
|
|
19859
19913
|
...columns.length > 0 ? { columns } : {}
|
|
19860
19914
|
});
|
|
19861
19915
|
}
|
|
@@ -22751,11 +22805,11 @@ function serializeGraph(graph) {
|
|
|
22751
22805
|
}
|
|
22752
22806
|
function projectFromReq(req, singleProject) {
|
|
22753
22807
|
const params = req.params;
|
|
22754
|
-
const
|
|
22808
|
+
const named2 = params.project;
|
|
22755
22809
|
if (singleProject) {
|
|
22756
|
-
return
|
|
22810
|
+
return named2 === void 0 || named2 === DEFAULT_PROJECT ? singleProject : named2;
|
|
22757
22811
|
}
|
|
22758
|
-
return
|
|
22812
|
+
return named2 ?? DEFAULT_PROJECT;
|
|
22759
22813
|
}
|
|
22760
22814
|
function resolveProject(registry, req, reply, bootstrap, singleProject) {
|
|
22761
22815
|
const name = projectFromReq(req, singleProject);
|
|
@@ -23654,42 +23708,482 @@ async function buildApi(opts) {
|
|
|
23654
23708
|
init_auth();
|
|
23655
23709
|
init_otel();
|
|
23656
23710
|
|
|
23657
|
-
// src/daemon.ts
|
|
23658
|
-
init_cjs_shims();
|
|
23659
|
-
var import_node_fs43 = require("fs");
|
|
23660
|
-
var import_node_path79 = __toESM(require("path"), 1);
|
|
23661
|
-
var import_node_module = require("module");
|
|
23662
|
-
init_otel();
|
|
23663
|
-
|
|
23664
23711
|
// src/connectors/kubernetes/index.ts
|
|
23665
23712
|
init_cjs_shims();
|
|
23666
23713
|
|
|
23667
23714
|
// src/connectors/kubernetes/client.ts
|
|
23668
23715
|
init_cjs_shims();
|
|
23669
23716
|
var import_node_https = require("https");
|
|
23717
|
+
function deploymentsPath(namespace) {
|
|
23718
|
+
return `/apis/apps/v1/namespaces/${namespace}/deployments`;
|
|
23719
|
+
}
|
|
23720
|
+
function podsPath(namespace) {
|
|
23721
|
+
return `/api/v1/namespaces/${namespace}/pods`;
|
|
23722
|
+
}
|
|
23723
|
+
function makeK8sFetchImpl(transport) {
|
|
23724
|
+
const agent = new import_node_https.Agent({
|
|
23725
|
+
...transport.ca ? { ca: transport.ca } : {},
|
|
23726
|
+
...transport.clientCert ? { cert: transport.clientCert } : {},
|
|
23727
|
+
...transport.clientKey ? { key: transport.clientKey } : {},
|
|
23728
|
+
rejectUnauthorized: !transport.insecureSkipTlsVerify
|
|
23729
|
+
});
|
|
23730
|
+
return ((url, init) => new Promise((resolve, reject) => {
|
|
23731
|
+
const u = new URL(String(url));
|
|
23732
|
+
const req = (0, import_node_https.request)(
|
|
23733
|
+
u,
|
|
23734
|
+
{
|
|
23735
|
+
method: (init?.method ?? "GET").toUpperCase(),
|
|
23736
|
+
headers: init?.headers ?? {},
|
|
23737
|
+
agent
|
|
23738
|
+
},
|
|
23739
|
+
(res) => {
|
|
23740
|
+
const chunks = [];
|
|
23741
|
+
res.on("data", (c) => chunks.push(c));
|
|
23742
|
+
res.on("end", () => {
|
|
23743
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
23744
|
+
const status2 = res.statusCode ?? 0;
|
|
23745
|
+
resolve({
|
|
23746
|
+
ok: status2 >= 200 && status2 < 300,
|
|
23747
|
+
status: status2,
|
|
23748
|
+
statusText: res.statusMessage ?? "",
|
|
23749
|
+
json: async () => JSON.parse(body),
|
|
23750
|
+
text: async () => body
|
|
23751
|
+
});
|
|
23752
|
+
});
|
|
23753
|
+
}
|
|
23754
|
+
);
|
|
23755
|
+
req.on("error", reject);
|
|
23756
|
+
const signal = init?.signal;
|
|
23757
|
+
if (signal) {
|
|
23758
|
+
if (signal.aborted) req.destroy(new Error("aborted"));
|
|
23759
|
+
else signal.addEventListener("abort", () => req.destroy(new Error("aborted")), { once: true });
|
|
23760
|
+
}
|
|
23761
|
+
req.end();
|
|
23762
|
+
}));
|
|
23763
|
+
}
|
|
23764
|
+
async function listResource(transport, namespace, path96, opts = {}) {
|
|
23765
|
+
const base = opts.apiUrl ?? transport.server;
|
|
23766
|
+
const url = `${base.replace(/\/$/, "")}${path96}`;
|
|
23767
|
+
const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
|
|
23768
|
+
const res = await junctionFetch(
|
|
23769
|
+
url,
|
|
23770
|
+
{ method: "GET", headers: { ...transport.token ? bearerAuthHeader(transport.token) : {}, Accept: "application/json" } },
|
|
23771
|
+
// accountKey: the (cluster, namespace) pair — an identifier, safe to log, the
|
|
23772
|
+
// rate-limit bucket for one namespace on one cluster (ADR-131).
|
|
23773
|
+
{ provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
|
|
23774
|
+
);
|
|
23775
|
+
if (!res.ok) {
|
|
23776
|
+
throw new Error(`kubernetes ${path96} failed: ${res.status} ${res.statusText}`);
|
|
23777
|
+
}
|
|
23778
|
+
const json = await res.json();
|
|
23779
|
+
return Array.isArray(json.items) ? json.items : [];
|
|
23780
|
+
}
|
|
23781
|
+
function safeHost(server) {
|
|
23782
|
+
try {
|
|
23783
|
+
return new URL(server).host;
|
|
23784
|
+
} catch {
|
|
23785
|
+
return "cluster";
|
|
23786
|
+
}
|
|
23787
|
+
}
|
|
23788
|
+
async function fetchDeployments(transport, namespace, opts = {}) {
|
|
23789
|
+
return listResource(transport, namespace, deploymentsPath(namespace), opts);
|
|
23790
|
+
}
|
|
23791
|
+
async function fetchPods(transport, namespace, opts = {}) {
|
|
23792
|
+
return listResource(transport, namespace, podsPath(namespace), opts);
|
|
23793
|
+
}
|
|
23670
23794
|
|
|
23671
23795
|
// src/connectors/kubernetes/kubeconfig.ts
|
|
23672
23796
|
init_cjs_shims();
|
|
23673
23797
|
var import_node_fs41 = require("fs");
|
|
23674
23798
|
var import_yaml4 = require("yaml");
|
|
23799
|
+
function pemFrom(dataField, pathField) {
|
|
23800
|
+
if (typeof dataField === "string" && dataField.length > 0) {
|
|
23801
|
+
return Buffer.from(dataField, "base64").toString("utf8");
|
|
23802
|
+
}
|
|
23803
|
+
if (typeof pathField === "string" && pathField.length > 0) {
|
|
23804
|
+
return (0, import_node_fs41.readFileSync)(pathField, "utf8");
|
|
23805
|
+
}
|
|
23806
|
+
return void 0;
|
|
23807
|
+
}
|
|
23808
|
+
function named(list, name) {
|
|
23809
|
+
if (!Array.isArray(list)) return void 0;
|
|
23810
|
+
const hit = list.find((e) => e && e.name === name);
|
|
23811
|
+
return hit;
|
|
23812
|
+
}
|
|
23813
|
+
function parseKubeconfig(kubeconfig) {
|
|
23814
|
+
const looksInline = /\n/.test(kubeconfig) || /(^|\s)clusters\s*:/.test(kubeconfig);
|
|
23815
|
+
const text = looksInline ? kubeconfig : (0, import_node_fs41.readFileSync)(kubeconfig, "utf8");
|
|
23816
|
+
let doc;
|
|
23817
|
+
try {
|
|
23818
|
+
doc = (0, import_yaml4.parse)(text);
|
|
23819
|
+
} catch {
|
|
23820
|
+
throw new Error("kubernetes connector: kubeconfig is not valid YAML");
|
|
23821
|
+
}
|
|
23822
|
+
if (!doc || typeof doc !== "object") {
|
|
23823
|
+
throw new Error("kubernetes connector: kubeconfig is empty or malformed");
|
|
23824
|
+
}
|
|
23825
|
+
const currentContext = doc["current-context"];
|
|
23826
|
+
if (typeof currentContext !== "string" || currentContext.length === 0) {
|
|
23827
|
+
throw new Error("kubernetes connector: kubeconfig has no current-context");
|
|
23828
|
+
}
|
|
23829
|
+
const ctxEntry = named(doc["contexts"], currentContext);
|
|
23830
|
+
const ctx = ctxEntry?.context;
|
|
23831
|
+
if (!ctx) {
|
|
23832
|
+
throw new Error(`kubernetes connector: kubeconfig context "${currentContext}" not found`);
|
|
23833
|
+
}
|
|
23834
|
+
const clusterEntry = named(doc["clusters"], String(ctx["cluster"] ?? ""));
|
|
23835
|
+
const cluster = clusterEntry?.cluster;
|
|
23836
|
+
if (!cluster || typeof cluster["server"] !== "string") {
|
|
23837
|
+
throw new Error("kubernetes connector: kubeconfig current context has no cluster server");
|
|
23838
|
+
}
|
|
23839
|
+
const userEntry = named(doc["users"], String(ctx["user"] ?? ""));
|
|
23840
|
+
const user = userEntry?.user ?? {};
|
|
23841
|
+
const transport = {
|
|
23842
|
+
server: cluster["server"],
|
|
23843
|
+
insecureSkipTlsVerify: cluster["insecure-skip-tls-verify"] === true
|
|
23844
|
+
};
|
|
23845
|
+
const ca = pemFrom(cluster["certificate-authority-data"], cluster["certificate-authority"]);
|
|
23846
|
+
if (ca) transport.ca = ca;
|
|
23847
|
+
if (typeof user["token"] === "string" && user["token"].length > 0) transport.token = user["token"];
|
|
23848
|
+
const clientCert = pemFrom(user["client-certificate-data"], user["client-certificate"]);
|
|
23849
|
+
const clientKey = pemFrom(user["client-key-data"], user["client-key"]);
|
|
23850
|
+
if (clientCert) transport.clientCert = clientCert;
|
|
23851
|
+
if (clientKey) transport.clientKey = clientKey;
|
|
23852
|
+
return transport;
|
|
23853
|
+
}
|
|
23854
|
+
function resolveK8sTransport(creds, config) {
|
|
23855
|
+
if (creds.kubeconfig) return parseKubeconfig(creds.kubeconfig);
|
|
23856
|
+
if (!config.apiServerUrl) {
|
|
23857
|
+
throw new Error("kubernetes connector: options.apiServerUrl is required with a token credential");
|
|
23858
|
+
}
|
|
23859
|
+
const transport = {
|
|
23860
|
+
server: config.apiServerUrl,
|
|
23861
|
+
insecureSkipTlsVerify: config.insecureSkipTlsVerify === true
|
|
23862
|
+
};
|
|
23863
|
+
if (creds.token) transport.token = creds.token;
|
|
23864
|
+
if (config.caCert) transport.ca = config.caCert;
|
|
23865
|
+
return transport;
|
|
23866
|
+
}
|
|
23675
23867
|
|
|
23676
23868
|
// src/connectors/kubernetes/map.ts
|
|
23677
23869
|
init_cjs_shims();
|
|
23678
23870
|
|
|
23679
23871
|
// src/connectors/kubernetes/types.ts
|
|
23680
23872
|
init_cjs_shims();
|
|
23873
|
+
function readK8sCredentials(raw) {
|
|
23874
|
+
const token = typeof raw["token"] === "string" && raw["token"].length > 0 ? raw["token"] : void 0;
|
|
23875
|
+
const kubeconfig = typeof raw["kubeconfig"] === "string" && raw["kubeconfig"].length > 0 ? raw["kubeconfig"] : void 0;
|
|
23876
|
+
if (!token && !kubeconfig) {
|
|
23877
|
+
throw new Error("kubernetes connector: credentials must carry a token or a kubeconfig");
|
|
23878
|
+
}
|
|
23879
|
+
const out = {};
|
|
23880
|
+
if (token) out.token = token;
|
|
23881
|
+
if (kubeconfig) out.kubeconfig = kubeconfig;
|
|
23882
|
+
return out;
|
|
23883
|
+
}
|
|
23884
|
+
var IMAGE_PULL_REASONS = /* @__PURE__ */ new Set(["ImagePullBackOff", "ErrImagePull", "InvalidImageName"]);
|
|
23885
|
+
var CRASH_LOOP_REASON = "CrashLoopBackOff";
|
|
23886
|
+
var FIELD_SEP5 = "\0";
|
|
23887
|
+
var K8S_TARGET_KIND = "k8s-workload";
|
|
23888
|
+
var K8S_DEPLOY_STATE = "deploy-state";
|
|
23889
|
+
function packK8sTargetName(identity) {
|
|
23890
|
+
return [identity.serviceName, identity.fault].join(FIELD_SEP5);
|
|
23891
|
+
}
|
|
23892
|
+
function parseK8sTargetName(targetName) {
|
|
23893
|
+
const sep = targetName.indexOf(FIELD_SEP5);
|
|
23894
|
+
if (sep === -1) return null;
|
|
23895
|
+
const serviceName = targetName.slice(0, sep);
|
|
23896
|
+
const fault = targetName.slice(sep + 1);
|
|
23897
|
+
if (!serviceName || !fault) return null;
|
|
23898
|
+
return { serviceName, fault };
|
|
23899
|
+
}
|
|
23900
|
+
|
|
23901
|
+
// src/connectors/kubernetes/map.ts
|
|
23902
|
+
function serviceNameFor(deployment, config) {
|
|
23903
|
+
const name = deployment.metadata?.name ?? "";
|
|
23904
|
+
return config.serviceMap?.[name] ?? name;
|
|
23905
|
+
}
|
|
23906
|
+
function podMatchesSelector(pod, selector) {
|
|
23907
|
+
if (!selector || Object.keys(selector).length === 0) return false;
|
|
23908
|
+
const labels = pod.metadata?.labels ?? {};
|
|
23909
|
+
return Object.entries(selector).every(([k, v]) => labels[k] === v);
|
|
23910
|
+
}
|
|
23911
|
+
function podsForDeployment(deployment, pods) {
|
|
23912
|
+
const selector = deployment.spec?.selector?.matchLabels;
|
|
23913
|
+
return pods.filter((p) => podMatchesSelector(p, selector));
|
|
23914
|
+
}
|
|
23915
|
+
function nowIso2() {
|
|
23916
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
23917
|
+
}
|
|
23918
|
+
function podLevelFault(deployment, pods) {
|
|
23919
|
+
const name = deployment.metadata?.name ?? "";
|
|
23920
|
+
const owned = podsForDeployment(deployment, pods);
|
|
23921
|
+
let crash = null;
|
|
23922
|
+
for (const pod of owned) {
|
|
23923
|
+
for (const cs of pod.status?.containerStatuses ?? []) {
|
|
23924
|
+
const reason = cs.state?.waiting?.reason;
|
|
23925
|
+
if (typeof reason !== "string") continue;
|
|
23926
|
+
if (IMAGE_PULL_REASONS.has(reason)) {
|
|
23927
|
+
const image = typeof cs.image === "string" ? cs.image : "unknown image";
|
|
23928
|
+
const attrs = { "k8s.image": image, "k8s.waitingReason": reason };
|
|
23929
|
+
const wm = cs.state?.waiting?.message;
|
|
23930
|
+
if (typeof wm === "string" && wm.length > 0) attrs["k8s.waitingMessage"] = wm;
|
|
23931
|
+
return {
|
|
23932
|
+
fault: "image-pull",
|
|
23933
|
+
message: `Deployment ${name} cannot pull image ${image} (${reason})`,
|
|
23934
|
+
timestamp: pod.status?.startTime ?? nowIso2(),
|
|
23935
|
+
attributes: attrs
|
|
23936
|
+
};
|
|
23937
|
+
}
|
|
23938
|
+
if (reason === CRASH_LOOP_REASON && !crash) crash = { cs, pod };
|
|
23939
|
+
}
|
|
23940
|
+
}
|
|
23941
|
+
if (crash) {
|
|
23942
|
+
const { cs, pod } = crash;
|
|
23943
|
+
const term = cs.lastState?.terminated;
|
|
23944
|
+
const termReason = typeof term?.reason === "string" ? term.reason : void 0;
|
|
23945
|
+
const termMsg = typeof term?.message === "string" ? term.message.trim() : void 0;
|
|
23946
|
+
const restarts = typeof cs.restartCount === "number" ? cs.restartCount : 0;
|
|
23947
|
+
const detail = termReason ? `last terminated: ${termReason}${termMsg ? ` \u2014 ${termMsg}` : ""}${typeof term?.exitCode === "number" ? ` (exit ${term.exitCode})` : ""}` : "no last-termination detail reported";
|
|
23948
|
+
const attrs = { "k8s.waitingReason": CRASH_LOOP_REASON, "k8s.restartCount": restarts };
|
|
23949
|
+
if (termReason) attrs["k8s.terminatedReason"] = termReason;
|
|
23950
|
+
if (termMsg) attrs["k8s.terminatedMessage"] = termMsg;
|
|
23951
|
+
if (typeof term?.exitCode === "number") attrs["k8s.exitCode"] = term.exitCode;
|
|
23952
|
+
if (typeof cs.image === "string") attrs["k8s.image"] = cs.image;
|
|
23953
|
+
return {
|
|
23954
|
+
fault: "crash-loop",
|
|
23955
|
+
message: `Deployment ${name} is crashlooping (restarts: ${restarts}); ${detail}`,
|
|
23956
|
+
timestamp: term?.finishedAt ?? pod.status?.startTime ?? nowIso2(),
|
|
23957
|
+
attributes: attrs
|
|
23958
|
+
};
|
|
23959
|
+
}
|
|
23960
|
+
return null;
|
|
23961
|
+
}
|
|
23962
|
+
function classifyDeployment(deployment, pods, expectedZero) {
|
|
23963
|
+
const name = deployment.metadata?.name ?? "";
|
|
23964
|
+
const desired = typeof deployment.spec?.replicas === "number" ? deployment.spec.replicas : 1;
|
|
23965
|
+
const ready = typeof deployment.status?.readyReplicas === "number" ? deployment.status.readyReplicas : 0;
|
|
23966
|
+
if (desired === 0) {
|
|
23967
|
+
if (expectedZero?.has(name)) return null;
|
|
23968
|
+
return {
|
|
23969
|
+
fault: "scaled-to-zero",
|
|
23970
|
+
message: `Deployment ${name} is scaled to 0 \u2014 no running pods (desired 0)`,
|
|
23971
|
+
timestamp: nowIso2(),
|
|
23972
|
+
attributes: { "k8s.desiredReplicas": 0, "k8s.readyReplicas": ready }
|
|
23973
|
+
};
|
|
23974
|
+
}
|
|
23975
|
+
if (ready >= desired) return null;
|
|
23976
|
+
const podFault = podLevelFault(deployment, pods);
|
|
23977
|
+
if (podFault) {
|
|
23978
|
+
podFault.attributes["k8s.desiredReplicas"] = desired;
|
|
23979
|
+
podFault.attributes["k8s.readyReplicas"] = ready;
|
|
23980
|
+
return podFault;
|
|
23981
|
+
}
|
|
23982
|
+
return {
|
|
23983
|
+
fault: "no-ready-replicas",
|
|
23984
|
+
message: `Deployment ${name} has no ready replicas (desired ${desired}, ready ${ready})`,
|
|
23985
|
+
timestamp: nowIso2(),
|
|
23986
|
+
attributes: { "k8s.desiredReplicas": desired, "k8s.readyReplicas": ready }
|
|
23987
|
+
};
|
|
23988
|
+
}
|
|
23989
|
+
function mapDeploymentToSignal(deployment, pods, config) {
|
|
23990
|
+
const name = deployment.metadata?.name;
|
|
23991
|
+
if (typeof name !== "string" || name.length === 0) return null;
|
|
23992
|
+
const expectedZero = config.expectedZero ? new Set(config.expectedZero) : void 0;
|
|
23993
|
+
const finding = classifyDeployment(deployment, pods, expectedZero);
|
|
23994
|
+
if (!finding) return null;
|
|
23995
|
+
const serviceName = serviceNameFor(deployment, config);
|
|
23996
|
+
const namespace = deployment.metadata?.namespace ?? config.namespace;
|
|
23997
|
+
const attributes = {
|
|
23998
|
+
"k8s.namespace": namespace,
|
|
23999
|
+
"k8s.deployment": name,
|
|
24000
|
+
"k8s.fault": finding.fault,
|
|
24001
|
+
...finding.attributes
|
|
24002
|
+
};
|
|
24003
|
+
return {
|
|
24004
|
+
targetKind: K8S_TARGET_KIND,
|
|
24005
|
+
targetName: packK8sTargetName({ serviceName, fault: finding.fault }),
|
|
24006
|
+
// Incident-only — no edge, so no call/error count to replay.
|
|
24007
|
+
callCount: 0,
|
|
24008
|
+
errorCount: 0,
|
|
24009
|
+
lastObservedIso: finding.timestamp,
|
|
24010
|
+
incident: {
|
|
24011
|
+
id: `k8s:deploy:${namespace}:${name}:${finding.fault}`,
|
|
24012
|
+
timestamp: finding.timestamp,
|
|
24013
|
+
service: serviceName,
|
|
24014
|
+
errorType: "k8s-deploy-failure",
|
|
24015
|
+
errorMessage: finding.message,
|
|
24016
|
+
attributes
|
|
24017
|
+
}
|
|
24018
|
+
};
|
|
24019
|
+
}
|
|
24020
|
+
function observedDeployState(deployment, pods) {
|
|
24021
|
+
const readyReplicas = typeof deployment.status?.readyReplicas === "number" ? deployment.status.readyReplicas : 0;
|
|
24022
|
+
let image;
|
|
24023
|
+
for (const pod of podsForDeployment(deployment, pods)) {
|
|
24024
|
+
for (const cs of pod.status?.containerStatuses ?? []) {
|
|
24025
|
+
if (cs.state?.running && typeof cs.image === "string" && cs.image.length > 0) {
|
|
24026
|
+
image = cs.image;
|
|
24027
|
+
break;
|
|
24028
|
+
}
|
|
24029
|
+
}
|
|
24030
|
+
if (image) break;
|
|
24031
|
+
}
|
|
24032
|
+
return image !== void 0 ? { image, readyReplicas } : { readyReplicas };
|
|
24033
|
+
}
|
|
24034
|
+
function deployStateSignal(deployment, pods, config) {
|
|
24035
|
+
const name = deployment.metadata?.name;
|
|
24036
|
+
if (typeof name !== "string" || name.length === 0) return null;
|
|
24037
|
+
const serviceName = serviceNameFor(deployment, config);
|
|
24038
|
+
return {
|
|
24039
|
+
targetKind: K8S_TARGET_KIND,
|
|
24040
|
+
targetName: packK8sTargetName({ serviceName, fault: K8S_DEPLOY_STATE }),
|
|
24041
|
+
callCount: 0,
|
|
24042
|
+
errorCount: 0,
|
|
24043
|
+
lastObservedIso: nowIso2(),
|
|
24044
|
+
deployState: observedDeployState(deployment, pods)
|
|
24045
|
+
};
|
|
24046
|
+
}
|
|
24047
|
+
function mapWorkloadsToSignals(deployments, pods, config) {
|
|
24048
|
+
const out = [];
|
|
24049
|
+
for (const deployment of deployments) {
|
|
24050
|
+
const deployState = deployStateSignal(deployment, pods, config);
|
|
24051
|
+
if (deployState) out.push(deployState);
|
|
24052
|
+
const incident = mapDeploymentToSignal(deployment, pods, config);
|
|
24053
|
+
if (incident) out.push(incident);
|
|
24054
|
+
}
|
|
24055
|
+
return out;
|
|
24056
|
+
}
|
|
23681
24057
|
|
|
23682
24058
|
// src/connectors/kubernetes/resolve.ts
|
|
23683
24059
|
init_cjs_shims();
|
|
23684
24060
|
var import_types103 = require("@neat.is/types");
|
|
24061
|
+
var NO_ENV3 = "unknown";
|
|
24062
|
+
function createK8sResolveTarget(graph) {
|
|
24063
|
+
return (signal) => {
|
|
24064
|
+
if (signal.targetKind !== K8S_TARGET_KIND) return null;
|
|
24065
|
+
const identity = parseK8sTargetName(signal.targetName);
|
|
24066
|
+
if (!identity) return null;
|
|
24067
|
+
const { serviceName } = identity;
|
|
24068
|
+
return {
|
|
24069
|
+
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV3),
|
|
24070
|
+
serviceName,
|
|
24071
|
+
edgeType: import_types103.EdgeType.CALLS
|
|
24072
|
+
};
|
|
24073
|
+
};
|
|
24074
|
+
}
|
|
23685
24075
|
|
|
23686
24076
|
// src/connectors/kubernetes/substrate.ts
|
|
23687
24077
|
init_cjs_shims();
|
|
23688
24078
|
var import_promises = require("fs/promises");
|
|
23689
24079
|
var import_node_os5 = __toESM(require("os"), 1);
|
|
23690
24080
|
var import_node_path77 = __toESM(require("path"), 1);
|
|
24081
|
+
function defaultHome() {
|
|
24082
|
+
const override = process.env.NEAT_HOME;
|
|
24083
|
+
if (override && override.length > 0) return import_node_path77.default.resolve(override);
|
|
24084
|
+
return import_node_path77.default.join(import_node_os5.default.homedir(), ".neat");
|
|
24085
|
+
}
|
|
24086
|
+
function k8sSubstrateConfigPath(home = defaultHome()) {
|
|
24087
|
+
return import_node_path77.default.join(home, "k8s.json");
|
|
24088
|
+
}
|
|
24089
|
+
async function readK8sSubstrateConfig(home = defaultHome()) {
|
|
24090
|
+
let raw;
|
|
24091
|
+
try {
|
|
24092
|
+
raw = await (0, import_promises.readFile)(k8sSubstrateConfigPath(home), "utf8");
|
|
24093
|
+
} catch {
|
|
24094
|
+
return { version: 1, deployments: [] };
|
|
24095
|
+
}
|
|
24096
|
+
try {
|
|
24097
|
+
const parsed = JSON.parse(raw);
|
|
24098
|
+
if (!parsed || !Array.isArray(parsed.deployments)) return { version: 1, deployments: [] };
|
|
24099
|
+
return parsed;
|
|
24100
|
+
} catch {
|
|
24101
|
+
return { version: 1, deployments: [] };
|
|
24102
|
+
}
|
|
24103
|
+
}
|
|
24104
|
+
async function startK8sSubstratePolling(input) {
|
|
24105
|
+
const config = await readK8sSubstrateConfig(input.home);
|
|
24106
|
+
const env = input.env ?? process.env;
|
|
24107
|
+
const stops = [];
|
|
24108
|
+
for (const entry2 of config.deployments) {
|
|
24109
|
+
if (entry2.project !== void 0 && entry2.project !== input.project) continue;
|
|
24110
|
+
if (typeof entry2.namespace !== "string" || entry2.namespace.length === 0) {
|
|
24111
|
+
input.onSkip?.(entry2, "missing namespace");
|
|
24112
|
+
continue;
|
|
24113
|
+
}
|
|
24114
|
+
let credentials;
|
|
24115
|
+
try {
|
|
24116
|
+
const resolved = resolveCredential(entry2.credential, env);
|
|
24117
|
+
credentials = resolved.kind === "fields" ? { ...resolved.fields } : { token: resolved.value };
|
|
24118
|
+
} catch (err) {
|
|
24119
|
+
input.onSkip?.(entry2, err.message);
|
|
24120
|
+
continue;
|
|
24121
|
+
}
|
|
24122
|
+
const cfg = {
|
|
24123
|
+
namespace: entry2.namespace,
|
|
24124
|
+
...entry2.apiServerUrl ? { apiServerUrl: entry2.apiServerUrl } : {},
|
|
24125
|
+
...entry2.caCert ? { caCert: entry2.caCert } : {},
|
|
24126
|
+
...entry2.insecureSkipTlsVerify ? { insecureSkipTlsVerify: true } : {},
|
|
24127
|
+
...entry2.serviceMap ? { serviceMap: entry2.serviceMap } : {},
|
|
24128
|
+
...entry2.expectedZero ? { expectedZero: entry2.expectedZero } : {}
|
|
24129
|
+
};
|
|
24130
|
+
const { connector, resolveTarget } = createKubernetesConnector(input.graph, cfg, input.fetchImpl);
|
|
24131
|
+
const stop = startConnectorPollLoop(
|
|
24132
|
+
connector,
|
|
24133
|
+
{
|
|
24134
|
+
projectDir: input.projectDir,
|
|
24135
|
+
project: input.project,
|
|
24136
|
+
credentials,
|
|
24137
|
+
...input.errorsPath ? { errorsPath: input.errorsPath } : {}
|
|
24138
|
+
},
|
|
24139
|
+
input.graph,
|
|
24140
|
+
resolveTarget,
|
|
24141
|
+
{ connectorId: `k8s:${entry2.id}`, ...entry2.intervalMs ? { intervalMs: entry2.intervalMs } : {} }
|
|
24142
|
+
);
|
|
24143
|
+
stops.push(stop);
|
|
24144
|
+
}
|
|
24145
|
+
return () => {
|
|
24146
|
+
for (const stop of stops) stop();
|
|
24147
|
+
};
|
|
24148
|
+
}
|
|
24149
|
+
|
|
24150
|
+
// src/connectors/kubernetes/index.ts
|
|
24151
|
+
var KubernetesConnector = class {
|
|
24152
|
+
constructor(config, fetchImpl) {
|
|
24153
|
+
this.config = config;
|
|
24154
|
+
this.fetchImpl = fetchImpl;
|
|
24155
|
+
}
|
|
24156
|
+
config;
|
|
24157
|
+
fetchImpl;
|
|
24158
|
+
provider = "kubernetes";
|
|
24159
|
+
async poll(ctx) {
|
|
24160
|
+
const creds = readK8sCredentials(ctx.credentials);
|
|
24161
|
+
const transport = resolveK8sTransport(creds, this.config);
|
|
24162
|
+
const namespace = this.config.namespace;
|
|
24163
|
+
const opts = {
|
|
24164
|
+
...this.config.apiUrl ? { apiUrl: this.config.apiUrl } : {},
|
|
24165
|
+
...this.fetchImpl ? { fetchImpl: this.fetchImpl } : {}
|
|
24166
|
+
};
|
|
24167
|
+
const [deployments, pods] = await Promise.all([
|
|
24168
|
+
fetchDeployments(transport, namespace, opts),
|
|
24169
|
+
fetchPods(transport, namespace, opts)
|
|
24170
|
+
]);
|
|
24171
|
+
return mapWorkloadsToSignals(deployments, pods, this.config);
|
|
24172
|
+
}
|
|
24173
|
+
};
|
|
24174
|
+
function createKubernetesConnector(graph, config, fetchImpl) {
|
|
24175
|
+
return {
|
|
24176
|
+
connector: new KubernetesConnector(config, fetchImpl),
|
|
24177
|
+
resolveTarget: createK8sResolveTarget(graph)
|
|
24178
|
+
};
|
|
24179
|
+
}
|
|
23691
24180
|
|
|
23692
24181
|
// src/daemon.ts
|
|
24182
|
+
init_cjs_shims();
|
|
24183
|
+
var import_node_fs43 = require("fs");
|
|
24184
|
+
var import_node_path79 = __toESM(require("path"), 1);
|
|
24185
|
+
var import_node_module = require("module");
|
|
24186
|
+
init_otel();
|
|
23693
24187
|
init_auth();
|
|
23694
24188
|
|
|
23695
24189
|
// src/unrouted.ts
|
|
@@ -24364,6 +24858,14 @@ async function startWatch(graph, opts) {
|
|
|
24364
24858
|
`neat watch: connector "${skipped.id}" (${skipped.provider}) skipped for project "${projectName}" \u2014 ${reason}`
|
|
24365
24859
|
)
|
|
24366
24860
|
});
|
|
24861
|
+
const stopK8sSubstrate = await startK8sSubstratePolling({
|
|
24862
|
+
project: projectName,
|
|
24863
|
+
graph,
|
|
24864
|
+
projectDir: opts.scanPath,
|
|
24865
|
+
errorsPath: opts.errorsPath,
|
|
24866
|
+
...opts.neatHome ? { home: opts.neatHome } : {},
|
|
24867
|
+
onSkip: (skipped, reason) => console.warn(`neat watch: k8s substrate "${skipped.id}" skipped for project "${projectName}" \u2014 ${reason}`)
|
|
24868
|
+
});
|
|
24367
24869
|
const auth = readAuthEnv();
|
|
24368
24870
|
const host = opts.host ?? (auth.authToken ? "0.0.0.0" : "127.0.0.1");
|
|
24369
24871
|
assertBindAuthority(host, auth.authToken);
|
|
@@ -24550,6 +25052,7 @@ async function startWatch(graph, opts) {
|
|
|
24550
25052
|
}
|
|
24551
25053
|
await watcher.close();
|
|
24552
25054
|
stopConnectors();
|
|
25055
|
+
stopK8sSubstrate();
|
|
24553
25056
|
stopStaleness();
|
|
24554
25057
|
stopPersist();
|
|
24555
25058
|
detachEventBus();
|
|
@@ -28008,8 +28511,8 @@ async function waitForPeerDaemon(restPort, project, timeoutMs) {
|
|
|
28008
28511
|
async function healthIsForProject(restPort, project) {
|
|
28009
28512
|
const body = await fetchDaemonHealth(restPort);
|
|
28010
28513
|
if (body === null) return false;
|
|
28011
|
-
const
|
|
28012
|
-
if (typeof
|
|
28514
|
+
const named2 = body.project;
|
|
28515
|
+
if (typeof named2 === "string") return named2 === project;
|
|
28013
28516
|
if (Array.isArray(body.projects)) {
|
|
28014
28517
|
return body.projects.some((p) => p.name === project);
|
|
28015
28518
|
}
|
|
@@ -29178,6 +29681,12 @@ function formatDivergenceLine(d) {
|
|
|
29178
29681
|
const at = d.location ? ` at ${d.location}` : "";
|
|
29179
29682
|
return ` \u2022 [${d.type}] ${d.source}${at} (${d.failureKind}) \u2014 confidence ${d.confidence.toFixed(2)}`;
|
|
29180
29683
|
}
|
|
29684
|
+
case "deploy-mismatch": {
|
|
29685
|
+
if (d.kind === "image") {
|
|
29686
|
+
return ` \u2022 [${d.type}] ${d.source} \u2014 declared image ${d.declaredImage ?? "?"}, running ${d.observedImage ?? "?"} \u2014 confidence ${d.confidence.toFixed(2)}`;
|
|
29687
|
+
}
|
|
29688
|
+
return ` \u2022 [${d.type}] ${d.source} \u2014 declared ${d.declaredReplicas ?? "?"} replicas, ${d.observedReplicas ?? "?"} ready \u2014 confidence ${d.confidence.toFixed(2)}`;
|
|
29689
|
+
}
|
|
29181
29690
|
}
|
|
29182
29691
|
}
|
|
29183
29692
|
async function runDivergences(client, input) {
|