@neat.is/core 0.10.6-dev.20261008 → 0.10.6-dev.20261010
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-EMFX3V77.js → chunk-52SCEPEW.js} +219 -9
- package/dist/chunk-52SCEPEW.js.map +1 -0
- package/dist/{chunk-6QMWJAWR.js → chunk-H2BAPN33.js} +108 -3
- package/dist/{chunk-6QMWJAWR.js.map → chunk-H2BAPN33.js.map} +1 -1
- package/dist/cli.cjs +358 -11
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +35 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +325 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +325 -9
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +107 -2
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-EMFX3V77.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -17996,10 +17996,11 @@ function locusOf(graph, ev) {
|
|
|
17996
17996
|
const rawLine = ev.attributes?.[CODE_LINENO_ATTR2];
|
|
17997
17997
|
const line = typeof rawLine === "number" ? rawLine : Number(rawLine);
|
|
17998
17998
|
const node = graph.hasNode(ev.affectedNode) ? graph.getNodeAttributes(ev.affectedNode) : void 0;
|
|
17999
|
+
const symbol = node?.type === "SymbolNode" ? node.qualname : void 0;
|
|
17999
18000
|
return {
|
|
18000
18001
|
file,
|
|
18001
18002
|
...Number.isFinite(line) ? { lineStart: line, lineEnd: line } : {},
|
|
18002
|
-
...
|
|
18003
|
+
...symbol ? { symbol } : {},
|
|
18003
18004
|
service: node?.service ?? ev.service,
|
|
18004
18005
|
provenance: import_types61.Provenance.OBSERVED
|
|
18005
18006
|
};
|
|
@@ -18095,7 +18096,8 @@ function buildIncidentCard(graph, errorEvent, incidents, policies) {
|
|
|
18095
18096
|
const policyCards = applicable.map((p) => ({
|
|
18096
18097
|
policyName: p.policyName,
|
|
18097
18098
|
severity: p.severity,
|
|
18098
|
-
message: p.reason
|
|
18099
|
+
message: p.reason,
|
|
18100
|
+
onViolation: p.onViolation
|
|
18099
18101
|
}));
|
|
18100
18102
|
const divergences = inGraph ? computeDivergences(graph, { node: affected, incidents }).divergences.map((d) => ({
|
|
18101
18103
|
type: d.type,
|
|
@@ -18122,8 +18124,111 @@ function buildIncidentCard(graph, errorEvent, incidents, policies) {
|
|
|
18122
18124
|
...divergences.length > 0 ? { divergence: divergences } : {},
|
|
18123
18125
|
headline: renderHeadline(graph, errorEvent, locus, rootCause?.node ?? null)
|
|
18124
18126
|
};
|
|
18127
|
+
card.grade = gradeIncidentCard(card);
|
|
18125
18128
|
return import_types61.IncidentCardSchema.parse(card);
|
|
18126
18129
|
}
|
|
18130
|
+
var GRADE_PRIORS_VERSION = "sniper-architecture-priors-2026-10-08";
|
|
18131
|
+
var GRADE_WEIGHTS = {
|
|
18132
|
+
evidence: 3,
|
|
18133
|
+
locus: 2,
|
|
18134
|
+
tests: 2,
|
|
18135
|
+
reach: 1.5,
|
|
18136
|
+
kind: 1.5,
|
|
18137
|
+
chain: 1,
|
|
18138
|
+
recur: 1,
|
|
18139
|
+
div: 1
|
|
18140
|
+
};
|
|
18141
|
+
var PROVENANCE_WEIGHT = {
|
|
18142
|
+
OBSERVED: 1,
|
|
18143
|
+
EXTRACTED: 0.8,
|
|
18144
|
+
INFERRED: 0.4,
|
|
18145
|
+
FRONTIER: 0.3,
|
|
18146
|
+
STALE: 0.2
|
|
18147
|
+
};
|
|
18148
|
+
var REACH_NODES_MAX = 50;
|
|
18149
|
+
var TIMEOUT = /timeout|timed out|deadline/i;
|
|
18150
|
+
var round2 = (n) => Math.round(n * 1e4) / 1e4;
|
|
18151
|
+
function factor(name, value, evidence, reason) {
|
|
18152
|
+
return {
|
|
18153
|
+
value: value === null ? null : round2(value),
|
|
18154
|
+
weight: GRADE_WEIGHTS[name],
|
|
18155
|
+
evidence,
|
|
18156
|
+
...reason ? { reason } : {}
|
|
18157
|
+
};
|
|
18158
|
+
}
|
|
18159
|
+
function gradeIncidentCard(card) {
|
|
18160
|
+
const rc = card.rootCause;
|
|
18161
|
+
const chain = rc?.chain ?? [];
|
|
18162
|
+
const gates = {
|
|
18163
|
+
policyNotBlock: {
|
|
18164
|
+
passed: !(card.policies ?? []).some((p) => p.onViolation === "block"),
|
|
18165
|
+
evidence: ["policies[].onViolation"]
|
|
18166
|
+
},
|
|
18167
|
+
notSymptomOnly: {
|
|
18168
|
+
passed: rc?.classification !== "symptom-only",
|
|
18169
|
+
evidence: ["rootCause.classification"]
|
|
18170
|
+
},
|
|
18171
|
+
locusResolves: { passed: card.locus !== null, evidence: ["locus"] }
|
|
18172
|
+
};
|
|
18173
|
+
const gamma = Object.values(gates).every((g) => g.passed) ? 1 : 0;
|
|
18174
|
+
const carrying = chain.some((h) => h.carriesSignal === true) ? chain.filter((h) => h.carriesSignal === true) : chain;
|
|
18175
|
+
const hopWeights = carrying.map((h) => PROVENANCE_WEIGHT[h.provenance]);
|
|
18176
|
+
const evidence = hopWeights.length === 0 ? factor("evidence", null, ["rootCause.chain[].provenance"], "no root-cause chain on the card") : hopWeights.some((w) => w === void 0) ? factor("evidence", null, ["rootCause.chain[].provenance"], "a chain hop has a provenance with no weight") : factor("evidence", Math.min(...hopWeights), ["rootCause.chain[].provenance"]);
|
|
18177
|
+
const locus = card.locus ? factor("locus", card.locus.symbol ? 1 : 0.6, ["locus.symbol", "locus.file"]) : factor("locus", null, ["locus"], "no locus on the card (the locus gate fails)");
|
|
18178
|
+
const tests = factor(
|
|
18179
|
+
"tests",
|
|
18180
|
+
null,
|
|
18181
|
+
[],
|
|
18182
|
+
"the card carries no test-file classification or test-to-symbol edges"
|
|
18183
|
+
);
|
|
18184
|
+
const reach = card.blastRadius ? factor(
|
|
18185
|
+
"reach",
|
|
18186
|
+
Math.max(0, 1 - card.blastRadius.totalAffected / REACH_NODES_MAX),
|
|
18187
|
+
["blastRadius.totalAffected"],
|
|
18188
|
+
`nodes in the blast radius against a prior bound of ${REACH_NODES_MAX}`
|
|
18189
|
+
) : factor("reach", null, ["blastRadius"], "no blast radius on the card");
|
|
18190
|
+
const timeout = TIMEOUT.test(`${card.exceptionType ?? ""} ${card.message}`);
|
|
18191
|
+
const kindValue = timeout ? 0.3 : card.incidentKind === "exception" ? 1 : card.incidentKind === "5xx" || card.incidentKind === "status-error" ? 0.7 : card.incidentKind === "connector" ? 0.6 : 0.4;
|
|
18192
|
+
const kind = factor("kind", kindValue, ["incidentKind", "exceptionType", "message"]);
|
|
18193
|
+
const chainFactor = rc ? factor(
|
|
18194
|
+
"chain",
|
|
18195
|
+
1 / (1 + 0.25 * (Math.max(1, chain.length - 1) - 1)),
|
|
18196
|
+
["rootCause.chain"]
|
|
18197
|
+
) : factor("chain", null, ["rootCause"], "no root cause on the card");
|
|
18198
|
+
const occurrences = card.count ?? 1;
|
|
18199
|
+
const recurValue = Math.min(1, Math.log(1 + occurrences) / Math.log(11));
|
|
18200
|
+
const recur = factor(
|
|
18201
|
+
"recur",
|
|
18202
|
+
recurValue,
|
|
18203
|
+
["count"],
|
|
18204
|
+
card.count === void 0 ? "no coalesced count; one recorded occurrence" : void 0
|
|
18205
|
+
);
|
|
18206
|
+
const div = factor("div", (card.divergence ?? []).length > 0 ? 1 : 0.7, ["divergence"]);
|
|
18207
|
+
const factors = { evidence, locus, tests, reach, kind, chain: chainFactor, recur, div };
|
|
18208
|
+
let weighted = 0;
|
|
18209
|
+
let total = 0;
|
|
18210
|
+
for (const f of Object.values(factors)) {
|
|
18211
|
+
if (f.value === null) continue;
|
|
18212
|
+
weighted += f.weight * f.value;
|
|
18213
|
+
total += f.weight;
|
|
18214
|
+
}
|
|
18215
|
+
const C = round2(weighted / total);
|
|
18216
|
+
const G = round2(gamma * C);
|
|
18217
|
+
const band = gamma === 0 || G < 0.5 ? "out" : G >= 0.75 ? "full" : "diagnose-only";
|
|
18218
|
+
return {
|
|
18219
|
+
G,
|
|
18220
|
+
gamma,
|
|
18221
|
+
gates,
|
|
18222
|
+
C,
|
|
18223
|
+
factors,
|
|
18224
|
+
band,
|
|
18225
|
+
urgency: {
|
|
18226
|
+
value: null,
|
|
18227
|
+
reason: "f_fresh needs the time of the last OBSERVED signal at the node, which the card does not carry"
|
|
18228
|
+
},
|
|
18229
|
+
priorsVersion: GRADE_PRIORS_VERSION
|
|
18230
|
+
};
|
|
18231
|
+
}
|
|
18127
18232
|
|
|
18128
18233
|
// src/ask.ts
|
|
18129
18234
|
init_cjs_shims();
|
|
@@ -25378,12 +25483,22 @@ init_cjs_shims();
|
|
|
25378
25483
|
// src/connectors/kubernetes/client.ts
|
|
25379
25484
|
init_cjs_shims();
|
|
25380
25485
|
var import_node_https = require("https");
|
|
25486
|
+
var K8S_RATE_LIMIT = { capacity: 120, refillMs: 1e3 };
|
|
25381
25487
|
function deploymentsPath(namespace) {
|
|
25382
25488
|
return `/apis/apps/v1/namespaces/${namespace}/deployments`;
|
|
25383
25489
|
}
|
|
25384
25490
|
function podsPath(namespace) {
|
|
25385
25491
|
return `/api/v1/namespaces/${namespace}/pods`;
|
|
25386
25492
|
}
|
|
25493
|
+
function podLogPath(namespace, podName, opts = {}) {
|
|
25494
|
+
const params = new URLSearchParams();
|
|
25495
|
+
if (opts.container) params.set("container", opts.container);
|
|
25496
|
+
if (typeof opts.tailLines === "number" && Number.isFinite(opts.tailLines)) params.set("tailLines", String(Math.trunc(opts.tailLines)));
|
|
25497
|
+
if (typeof opts.limitBytes === "number" && Number.isFinite(opts.limitBytes)) params.set("limitBytes", String(Math.trunc(opts.limitBytes)));
|
|
25498
|
+
if (opts.previous) params.set("previous", "true");
|
|
25499
|
+
const qs = params.toString();
|
|
25500
|
+
return `${podsPath(namespace)}/${podName}/log${qs ? `?${qs}` : ""}`;
|
|
25501
|
+
}
|
|
25387
25502
|
function makeK8sFetchImpl(transport) {
|
|
25388
25503
|
const agent = new import_node_https.Agent({
|
|
25389
25504
|
...transport.ca ? { ca: transport.ca } : {},
|
|
@@ -25434,7 +25549,7 @@ async function listResource(transport, namespace, path82, opts = {}) {
|
|
|
25434
25549
|
{ method: "GET", headers: { ...transport.token ? bearerAuthHeader(transport.token) : {}, Accept: "application/json" } },
|
|
25435
25550
|
// accountKey: the (cluster, namespace) pair — an identifier, safe to log, the
|
|
25436
25551
|
// rate-limit bucket for one namespace on one cluster (ADR-131).
|
|
25437
|
-
{ provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
|
|
25552
|
+
{ provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, rateLimit: K8S_RATE_LIMIT, fetchImpl }
|
|
25438
25553
|
);
|
|
25439
25554
|
if (!res.ok) {
|
|
25440
25555
|
throw new Error(`kubernetes ${path82} failed: ${res.status} ${res.statusText}`);
|
|
@@ -25455,6 +25570,28 @@ async function fetchDeployments(transport, namespace, opts = {}) {
|
|
|
25455
25570
|
async function fetchPods(transport, namespace, opts = {}) {
|
|
25456
25571
|
return listResource(transport, namespace, podsPath(namespace), opts);
|
|
25457
25572
|
}
|
|
25573
|
+
async function fetchPodLog(transport, namespace, podName, opts = {}) {
|
|
25574
|
+
const base = opts.apiUrl ?? transport.server;
|
|
25575
|
+
const path82 = podLogPath(namespace, podName, {
|
|
25576
|
+
...opts.container ? { container: opts.container } : {},
|
|
25577
|
+
...opts.tailLines !== void 0 ? { tailLines: opts.tailLines } : {},
|
|
25578
|
+
...opts.limitBytes !== void 0 ? { limitBytes: opts.limitBytes } : {},
|
|
25579
|
+
...opts.previous ? { previous: true } : {}
|
|
25580
|
+
});
|
|
25581
|
+
const url = `${base.replace(/\/$/, "")}${path82}`;
|
|
25582
|
+
const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
|
|
25583
|
+
try {
|
|
25584
|
+
const res = await junctionFetch(
|
|
25585
|
+
url,
|
|
25586
|
+
{ method: "GET", headers: { ...transport.token ? bearerAuthHeader(transport.token) : {}, Accept: "text/plain" } },
|
|
25587
|
+
{ provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, rateLimit: K8S_RATE_LIMIT, fetchImpl }
|
|
25588
|
+
);
|
|
25589
|
+
if (!res.ok) return void 0;
|
|
25590
|
+
return await res.text();
|
|
25591
|
+
} catch {
|
|
25592
|
+
return void 0;
|
|
25593
|
+
}
|
|
25594
|
+
}
|
|
25458
25595
|
|
|
25459
25596
|
// src/connectors/kubernetes/kubeconfig.ts
|
|
25460
25597
|
init_cjs_shims();
|
|
@@ -25562,6 +25699,123 @@ function parseK8sTargetName(targetName) {
|
|
|
25562
25699
|
return { serviceName, fault };
|
|
25563
25700
|
}
|
|
25564
25701
|
|
|
25702
|
+
// src/connectors/kubernetes/process-context.ts
|
|
25703
|
+
init_cjs_shims();
|
|
25704
|
+
var DEFAULT_MAX_LOG_LINES = 50;
|
|
25705
|
+
var DEFAULT_MAX_LOG_BYTES2 = 2 * 1024;
|
|
25706
|
+
var DEFAULT_MAX_ENTRIES = 64;
|
|
25707
|
+
var DEFAULT_MAX_VALUE_CHARS = 512;
|
|
25708
|
+
var REDACTED = "***REDACTED***";
|
|
25709
|
+
var SECRET_KEY_RE = /TOKEN|SECRET|KEY|PASSW(?:OR)?D|PWD|(?:^|[^A-Z])PASS(?:$|[^A-Z])|CREDENTIAL|(?:^|[^A-Z])AUTH|PRIVATE|DSN|COOKIE|SESSION|SALT|SIGNATURE|CERT|CONN(?:ECTION)?_?STR/i;
|
|
25710
|
+
function isSecretKey(name) {
|
|
25711
|
+
return SECRET_KEY_RE.test(name);
|
|
25712
|
+
}
|
|
25713
|
+
var PRIVATE_KEY_BLOCK_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g;
|
|
25714
|
+
var PRIVATE_KEY_TAIL_RE = /(?:^[A-Za-z0-9+/=]{32,}\r?\n)+-----END [A-Z ]*PRIVATE KEY-----/gm;
|
|
25715
|
+
var URL_USERINFO_RE = /([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^\s/@]*)@/g;
|
|
25716
|
+
var SECRET_PAIR_RE = /((?:^|[\s?&;,{("'])[A-Za-z0-9_.-]*?(?:passw(?:or)?d|pwd|(?<![a-z])pass(?![a-z])|secret|token|api[_-]?key|access[_-]?key|private[_-]?key|client[_-]?key|credentials?|(?<![a-z])auth(?![a-z])|(?<![a-z])dsn(?![a-z]))[A-Za-z0-9_.-]*["']?\s*[=:]\s*["']?)([^\s&;,"'})]+)/gi;
|
|
25717
|
+
var AUTH_SCHEME_RE = /\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{8,}/g;
|
|
25718
|
+
var KNOWN_TOKEN_RE = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}|\bAKIA[0-9A-Z]{16}\b|\bgh[pousr]_[A-Za-z0-9]{20,}|\bgithub_pat_[A-Za-z0-9_]{20,}|\bsk_(?:live|test)_[A-Za-z0-9]{10,}|\bxox[abprs]-[A-Za-z0-9-]{10,}|\bglpat-[A-Za-z0-9_-]{16,}|\bAIza[0-9A-Za-z_-]{30,}|\bneat_pat_[A-Za-z0-9_-]{16,}/g;
|
|
25719
|
+
function redactText(text) {
|
|
25720
|
+
return text.replace(PRIVATE_KEY_BLOCK_RE, REDACTED).replace(PRIVATE_KEY_TAIL_RE, REDACTED).replace(URL_USERINFO_RE, (_m, scheme, userinfo) => {
|
|
25721
|
+
const colon = userinfo.indexOf(":");
|
|
25722
|
+
return colon > 0 ? `${scheme}${userinfo.slice(0, colon)}:${REDACTED}@` : `${scheme}${REDACTED}@`;
|
|
25723
|
+
}).replace(SECRET_PAIR_RE, (_m, key) => `${key}${REDACTED}`).replace(AUTH_SCHEME_RE, (_m, scheme) => `${scheme} ${REDACTED}`).replace(KNOWN_TOKEN_RE, REDACTED);
|
|
25724
|
+
}
|
|
25725
|
+
function redactValue(name, value) {
|
|
25726
|
+
if (isSecretKey(name)) return REDACTED;
|
|
25727
|
+
return redactText(value);
|
|
25728
|
+
}
|
|
25729
|
+
function redactArg(arg) {
|
|
25730
|
+
const eq = /^(--?)?([A-Za-z0-9_.-]+)=([\s\S]*)$/.exec(arg);
|
|
25731
|
+
if (eq && isSecretKey(eq[2])) {
|
|
25732
|
+
return `${eq[1] ?? ""}${eq[2]}=${REDACTED}`;
|
|
25733
|
+
}
|
|
25734
|
+
return redactText(arg);
|
|
25735
|
+
}
|
|
25736
|
+
function isSecretFlag(arg) {
|
|
25737
|
+
const m = /^--?([A-Za-z0-9_.-]+)$/.exec(arg);
|
|
25738
|
+
return m !== null && isSecretKey(m[1]);
|
|
25739
|
+
}
|
|
25740
|
+
function capValue(s, max = DEFAULT_MAX_VALUE_CHARS) {
|
|
25741
|
+
return s.length > max ? `${s.slice(0, max)}\u2026` : s;
|
|
25742
|
+
}
|
|
25743
|
+
function describeEnvVar(ev) {
|
|
25744
|
+
const name = typeof ev.name === "string" ? ev.name : "";
|
|
25745
|
+
if (name.length === 0) return void 0;
|
|
25746
|
+
if (typeof ev.value === "string") {
|
|
25747
|
+
return `${name}=${capValue(redactValue(name, ev.value))}`;
|
|
25748
|
+
}
|
|
25749
|
+
const from = ev.valueFrom;
|
|
25750
|
+
if (from) {
|
|
25751
|
+
if (from.secretKeyRef) {
|
|
25752
|
+
const ref = typeof from.secretKeyRef.name === "string" && from.secretKeyRef.name.length > 0 ? from.secretKeyRef.name : "<unnamed>";
|
|
25753
|
+
return `${name}=<from secret ${ref}>`;
|
|
25754
|
+
}
|
|
25755
|
+
if (from.configMapKeyRef) {
|
|
25756
|
+
const ref = typeof from.configMapKeyRef.name === "string" && from.configMapKeyRef.name.length > 0 ? from.configMapKeyRef.name : "<unnamed>";
|
|
25757
|
+
const key = typeof from.configMapKeyRef.key === "string" && from.configMapKeyRef.key.length > 0 ? ` key ${from.configMapKeyRef.key}` : "";
|
|
25758
|
+
return `${name}=<from configMap ${ref}${key}>`;
|
|
25759
|
+
}
|
|
25760
|
+
if (from.fieldRef && typeof from.fieldRef.fieldPath === "string" && from.fieldRef.fieldPath.length > 0) {
|
|
25761
|
+
return `${name}=<from field ${from.fieldRef.fieldPath}>`;
|
|
25762
|
+
}
|
|
25763
|
+
if (from.resourceFieldRef && typeof from.resourceFieldRef.resource === "string" && from.resourceFieldRef.resource.length > 0) {
|
|
25764
|
+
return `${name}=<from resource ${from.resourceFieldRef.resource}>`;
|
|
25765
|
+
}
|
|
25766
|
+
return `${name}=<from reference>`;
|
|
25767
|
+
}
|
|
25768
|
+
return `${name}=`;
|
|
25769
|
+
}
|
|
25770
|
+
function buildContainerEnv(container) {
|
|
25771
|
+
const out = [];
|
|
25772
|
+
for (const ev of container.env ?? []) {
|
|
25773
|
+
const d = describeEnvVar(ev);
|
|
25774
|
+
if (d !== void 0) out.push(d);
|
|
25775
|
+
if (out.length >= DEFAULT_MAX_ENTRIES) break;
|
|
25776
|
+
}
|
|
25777
|
+
return out;
|
|
25778
|
+
}
|
|
25779
|
+
function buildContainerArgs(container) {
|
|
25780
|
+
const raw = [];
|
|
25781
|
+
for (const c of container.command ?? []) if (typeof c === "string") raw.push(c);
|
|
25782
|
+
for (const a of container.args ?? []) if (typeof a === "string") raw.push(a);
|
|
25783
|
+
const out = [];
|
|
25784
|
+
for (let i = 0; i < raw.length && out.length < DEFAULT_MAX_ENTRIES; i++) {
|
|
25785
|
+
const arg = raw[i];
|
|
25786
|
+
out.push(capValue(redactArg(arg)));
|
|
25787
|
+
const next = raw[i + 1];
|
|
25788
|
+
if (isSecretFlag(arg) && next !== void 0 && !next.startsWith("-") && out.length < DEFAULT_MAX_ENTRIES) {
|
|
25789
|
+
out.push(REDACTED);
|
|
25790
|
+
i++;
|
|
25791
|
+
}
|
|
25792
|
+
}
|
|
25793
|
+
return out;
|
|
25794
|
+
}
|
|
25795
|
+
function capProcessLog(text, maxLines = DEFAULT_MAX_LOG_LINES, maxBytes = DEFAULT_MAX_LOG_BYTES2) {
|
|
25796
|
+
const trimmed = text.replace(/\s+$/, "");
|
|
25797
|
+
if (trimmed.length === 0) return "";
|
|
25798
|
+
const lines = trimmed.split("\n");
|
|
25799
|
+
const tail = lines.length > maxLines ? lines.slice(lines.length - maxLines) : lines;
|
|
25800
|
+
let out = tail.join("\n");
|
|
25801
|
+
if (out.length > maxBytes) out = out.slice(out.length - maxBytes);
|
|
25802
|
+
return out;
|
|
25803
|
+
}
|
|
25804
|
+
function buildProcessContext(input) {
|
|
25805
|
+
const out = {};
|
|
25806
|
+
if (typeof input.log === "string") {
|
|
25807
|
+
const capped = capProcessLog(redactText(input.log));
|
|
25808
|
+
if (capped.length > 0) out.processLog = capped;
|
|
25809
|
+
}
|
|
25810
|
+
if (input.container) {
|
|
25811
|
+
const args = buildContainerArgs(input.container);
|
|
25812
|
+
if (args.length > 0) out.containerArgs = args;
|
|
25813
|
+
const env = buildContainerEnv(input.container);
|
|
25814
|
+
if (env.length > 0) out.containerEnv = env;
|
|
25815
|
+
}
|
|
25816
|
+
return out;
|
|
25817
|
+
}
|
|
25818
|
+
|
|
25565
25819
|
// src/connectors/kubernetes/map.ts
|
|
25566
25820
|
function serviceNameFor(deployment, config) {
|
|
25567
25821
|
const name = deployment.metadata?.name ?? "";
|
|
@@ -25579,6 +25833,9 @@ function podsForDeployment(deployment, pods) {
|
|
|
25579
25833
|
function nowIso2() {
|
|
25580
25834
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
25581
25835
|
}
|
|
25836
|
+
function podLogKey(podName, container) {
|
|
25837
|
+
return container ? `${podName}\0${container}` : podName;
|
|
25838
|
+
}
|
|
25582
25839
|
function podLevelFault(deployment, pods) {
|
|
25583
25840
|
const name = deployment.metadata?.name ?? "";
|
|
25584
25841
|
const owned = podsForDeployment(deployment, pods);
|
|
@@ -25596,7 +25853,9 @@ function podLevelFault(deployment, pods) {
|
|
|
25596
25853
|
fault: "image-pull",
|
|
25597
25854
|
message: `Deployment ${name} cannot pull image ${image} (${reason})`,
|
|
25598
25855
|
timestamp: pod.status?.startTime ?? nowIso2(),
|
|
25599
|
-
attributes: attrs
|
|
25856
|
+
attributes: attrs,
|
|
25857
|
+
pod,
|
|
25858
|
+
...typeof cs.name === "string" && cs.name.length > 0 ? { container: cs.name } : {}
|
|
25600
25859
|
};
|
|
25601
25860
|
}
|
|
25602
25861
|
if (reason === CRASH_LOOP_REASON && !crash) crash = { cs, pod };
|
|
@@ -25618,11 +25877,47 @@ function podLevelFault(deployment, pods) {
|
|
|
25618
25877
|
fault: "crash-loop",
|
|
25619
25878
|
message: `Deployment ${name} is crashlooping (restarts: ${restarts}); ${detail}`,
|
|
25620
25879
|
timestamp: term?.finishedAt ?? pod.status?.startTime ?? nowIso2(),
|
|
25621
|
-
attributes: attrs
|
|
25880
|
+
attributes: attrs,
|
|
25881
|
+
pod,
|
|
25882
|
+
...typeof cs.name === "string" && cs.name.length > 0 ? { container: cs.name } : {}
|
|
25622
25883
|
};
|
|
25623
25884
|
}
|
|
25624
25885
|
return null;
|
|
25625
25886
|
}
|
|
25887
|
+
function faultedContainer(finding) {
|
|
25888
|
+
const containers = finding.pod?.spec?.containers;
|
|
25889
|
+
if (!Array.isArray(containers) || containers.length === 0) return void 0;
|
|
25890
|
+
if (finding.container) {
|
|
25891
|
+
const named2 = containers.find((c) => c.name === finding.container);
|
|
25892
|
+
if (named2) return named2;
|
|
25893
|
+
}
|
|
25894
|
+
return containers.length === 1 ? containers[0] : void 0;
|
|
25895
|
+
}
|
|
25896
|
+
function mergeProcessContext(finding, logs) {
|
|
25897
|
+
const container = faultedContainer(finding);
|
|
25898
|
+
const podName = finding.pod?.metadata?.name;
|
|
25899
|
+
const log = logs && podName ? logs.get(podLogKey(podName, finding.container)) : void 0;
|
|
25900
|
+
if (!container && log === void 0) return;
|
|
25901
|
+
const ctx = buildProcessContext({
|
|
25902
|
+
...log !== void 0 ? { log } : {},
|
|
25903
|
+
...container ? { container } : {}
|
|
25904
|
+
});
|
|
25905
|
+
if (ctx.processLog !== void 0) finding.attributes["k8s.processLog"] = ctx.processLog;
|
|
25906
|
+
if (ctx.containerArgs !== void 0) finding.attributes["k8s.containerArgs"] = ctx.containerArgs;
|
|
25907
|
+
if (ctx.containerEnv !== void 0) finding.attributes["k8s.containerEnv"] = ctx.containerEnv;
|
|
25908
|
+
}
|
|
25909
|
+
function faultedPods(deployments, pods, config) {
|
|
25910
|
+
const out = [];
|
|
25911
|
+
const expectedZero = config.expectedZero ? new Set(config.expectedZero) : void 0;
|
|
25912
|
+
for (const deployment of deployments) {
|
|
25913
|
+
const finding = classifyDeployment(deployment, pods, expectedZero);
|
|
25914
|
+
if (!finding || finding.fault !== "crash-loop") continue;
|
|
25915
|
+
const podName = finding.pod?.metadata?.name;
|
|
25916
|
+
if (typeof podName !== "string" || podName.length === 0) continue;
|
|
25917
|
+
out.push({ podName, ...finding.container ? { container: finding.container } : {} });
|
|
25918
|
+
}
|
|
25919
|
+
return out;
|
|
25920
|
+
}
|
|
25626
25921
|
function classifyDeployment(deployment, pods, expectedZero) {
|
|
25627
25922
|
const name = deployment.metadata?.name ?? "";
|
|
25628
25923
|
const desired = typeof deployment.spec?.replicas === "number" ? deployment.spec.replicas : 1;
|
|
@@ -25650,12 +25945,13 @@ function classifyDeployment(deployment, pods, expectedZero) {
|
|
|
25650
25945
|
attributes: { "k8s.desiredReplicas": desired, "k8s.readyReplicas": ready }
|
|
25651
25946
|
};
|
|
25652
25947
|
}
|
|
25653
|
-
function mapDeploymentToSignal(deployment, pods, config) {
|
|
25948
|
+
function mapDeploymentToSignal(deployment, pods, config, logs) {
|
|
25654
25949
|
const name = deployment.metadata?.name;
|
|
25655
25950
|
if (typeof name !== "string" || name.length === 0) return null;
|
|
25656
25951
|
const expectedZero = config.expectedZero ? new Set(config.expectedZero) : void 0;
|
|
25657
25952
|
const finding = classifyDeployment(deployment, pods, expectedZero);
|
|
25658
25953
|
if (!finding) return null;
|
|
25954
|
+
mergeProcessContext(finding, logs);
|
|
25659
25955
|
const serviceName = serviceNameFor(deployment, config);
|
|
25660
25956
|
const namespace = deployment.metadata?.namespace ?? config.namespace;
|
|
25661
25957
|
const attributes = {
|
|
@@ -25708,12 +26004,12 @@ function deployStateSignal(deployment, pods, config) {
|
|
|
25708
26004
|
deployState: observedDeployState(deployment, pods)
|
|
25709
26005
|
};
|
|
25710
26006
|
}
|
|
25711
|
-
function mapWorkloadsToSignals(deployments, pods, config) {
|
|
26007
|
+
function mapWorkloadsToSignals(deployments, pods, config, logs) {
|
|
25712
26008
|
const out = [];
|
|
25713
26009
|
for (const deployment of deployments) {
|
|
25714
26010
|
const deployState = deployStateSignal(deployment, pods, config);
|
|
25715
26011
|
if (deployState) out.push(deployState);
|
|
25716
|
-
const incident = mapDeploymentToSignal(deployment, pods, config);
|
|
26012
|
+
const incident = mapDeploymentToSignal(deployment, pods, config, logs);
|
|
25717
26013
|
if (incident) out.push(incident);
|
|
25718
26014
|
}
|
|
25719
26015
|
return out;
|
|
@@ -25812,6 +26108,8 @@ async function startK8sSubstratePolling(input) {
|
|
|
25812
26108
|
}
|
|
25813
26109
|
|
|
25814
26110
|
// src/connectors/kubernetes/index.ts
|
|
26111
|
+
var PROCESS_LOG_TAIL_LINES = 200;
|
|
26112
|
+
var PROCESS_LOG_LIMIT_BYTES = 64 * 1024;
|
|
25815
26113
|
var KubernetesConnector = class {
|
|
25816
26114
|
constructor(config, fetchImpl) {
|
|
25817
26115
|
this.config = config;
|
|
@@ -25832,7 +26130,25 @@ var KubernetesConnector = class {
|
|
|
25832
26130
|
fetchDeployments(transport, namespace, opts),
|
|
25833
26131
|
fetchPods(transport, namespace, opts)
|
|
25834
26132
|
]);
|
|
25835
|
-
|
|
26133
|
+
const logs = await this.fetchFaultedPodLogs(transport, namespace, deployments, pods, opts);
|
|
26134
|
+
return mapWorkloadsToSignals(deployments, pods, this.config, logs);
|
|
26135
|
+
}
|
|
26136
|
+
async fetchFaultedPodLogs(transport, namespace, deployments, pods, opts) {
|
|
26137
|
+
const logs = /* @__PURE__ */ new Map();
|
|
26138
|
+
const targets = faultedPods(deployments, pods, this.config);
|
|
26139
|
+
await Promise.all(
|
|
26140
|
+
targets.map(async (t) => {
|
|
26141
|
+
const text = await fetchPodLog(transport, namespace, t.podName, {
|
|
26142
|
+
...t.container ? { container: t.container } : {},
|
|
26143
|
+
tailLines: PROCESS_LOG_TAIL_LINES,
|
|
26144
|
+
limitBytes: PROCESS_LOG_LIMIT_BYTES,
|
|
26145
|
+
previous: true,
|
|
26146
|
+
...opts
|
|
26147
|
+
});
|
|
26148
|
+
if (typeof text === "string" && text.length > 0) logs.set(podLogKey(t.podName, t.container), text);
|
|
26149
|
+
})
|
|
26150
|
+
);
|
|
26151
|
+
return logs;
|
|
25836
26152
|
}
|
|
25837
26153
|
};
|
|
25838
26154
|
function createKubernetesConnector(graph, config, fetchImpl) {
|