@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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  routeSpanToProject,
3
3
  startDaemon
4
- } from "./chunk-EMFX3V77.js";
4
+ } from "./chunk-52SCEPEW.js";
5
5
  import {
6
6
  buildApi,
7
7
  checkCompatibility,
@@ -25,7 +25,7 @@ import {
25
25
  startStalenessLoop,
26
26
  stitchTrace,
27
27
  thresholdForEdgeType
28
- } from "./chunk-6QMWJAWR.js";
28
+ } from "./chunk-H2BAPN33.js";
29
29
  import {
30
30
  ProjectNameCollisionError,
31
31
  addProject,
package/dist/neatd.cjs CHANGED
@@ -18008,10 +18008,11 @@ function locusOf(graph, ev) {
18008
18008
  const rawLine = ev.attributes?.[CODE_LINENO_ATTR2];
18009
18009
  const line = typeof rawLine === "number" ? rawLine : Number(rawLine);
18010
18010
  const node = graph.hasNode(ev.affectedNode) ? graph.getNodeAttributes(ev.affectedNode) : void 0;
18011
+ const symbol = node?.type === "SymbolNode" ? node.qualname : void 0;
18011
18012
  return {
18012
18013
  file,
18013
18014
  ...Number.isFinite(line) ? { lineStart: line, lineEnd: line } : {},
18014
- ...node?.name ? { symbol: node.name } : {},
18015
+ ...symbol ? { symbol } : {},
18015
18016
  service: node?.service ?? ev.service,
18016
18017
  provenance: import_types61.Provenance.OBSERVED
18017
18018
  };
@@ -18107,7 +18108,8 @@ function buildIncidentCard(graph, errorEvent, incidents, policies) {
18107
18108
  const policyCards = applicable.map((p) => ({
18108
18109
  policyName: p.policyName,
18109
18110
  severity: p.severity,
18110
- message: p.reason
18111
+ message: p.reason,
18112
+ onViolation: p.onViolation
18111
18113
  }));
18112
18114
  const divergences = inGraph ? computeDivergences(graph, { node: affected, incidents }).divergences.map((d) => ({
18113
18115
  type: d.type,
@@ -18134,8 +18136,111 @@ function buildIncidentCard(graph, errorEvent, incidents, policies) {
18134
18136
  ...divergences.length > 0 ? { divergence: divergences } : {},
18135
18137
  headline: renderHeadline(graph, errorEvent, locus, rootCause?.node ?? null)
18136
18138
  };
18139
+ card.grade = gradeIncidentCard(card);
18137
18140
  return import_types61.IncidentCardSchema.parse(card);
18138
18141
  }
18142
+ var GRADE_PRIORS_VERSION = "sniper-architecture-priors-2026-10-08";
18143
+ var GRADE_WEIGHTS = {
18144
+ evidence: 3,
18145
+ locus: 2,
18146
+ tests: 2,
18147
+ reach: 1.5,
18148
+ kind: 1.5,
18149
+ chain: 1,
18150
+ recur: 1,
18151
+ div: 1
18152
+ };
18153
+ var PROVENANCE_WEIGHT = {
18154
+ OBSERVED: 1,
18155
+ EXTRACTED: 0.8,
18156
+ INFERRED: 0.4,
18157
+ FRONTIER: 0.3,
18158
+ STALE: 0.2
18159
+ };
18160
+ var REACH_NODES_MAX = 50;
18161
+ var TIMEOUT = /timeout|timed out|deadline/i;
18162
+ var round2 = (n) => Math.round(n * 1e4) / 1e4;
18163
+ function factor(name, value, evidence, reason) {
18164
+ return {
18165
+ value: value === null ? null : round2(value),
18166
+ weight: GRADE_WEIGHTS[name],
18167
+ evidence,
18168
+ ...reason ? { reason } : {}
18169
+ };
18170
+ }
18171
+ function gradeIncidentCard(card) {
18172
+ const rc = card.rootCause;
18173
+ const chain = rc?.chain ?? [];
18174
+ const gates = {
18175
+ policyNotBlock: {
18176
+ passed: !(card.policies ?? []).some((p) => p.onViolation === "block"),
18177
+ evidence: ["policies[].onViolation"]
18178
+ },
18179
+ notSymptomOnly: {
18180
+ passed: rc?.classification !== "symptom-only",
18181
+ evidence: ["rootCause.classification"]
18182
+ },
18183
+ locusResolves: { passed: card.locus !== null, evidence: ["locus"] }
18184
+ };
18185
+ const gamma = Object.values(gates).every((g) => g.passed) ? 1 : 0;
18186
+ const carrying = chain.some((h) => h.carriesSignal === true) ? chain.filter((h) => h.carriesSignal === true) : chain;
18187
+ const hopWeights = carrying.map((h) => PROVENANCE_WEIGHT[h.provenance]);
18188
+ 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"]);
18189
+ 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)");
18190
+ const tests = factor(
18191
+ "tests",
18192
+ null,
18193
+ [],
18194
+ "the card carries no test-file classification or test-to-symbol edges"
18195
+ );
18196
+ const reach = card.blastRadius ? factor(
18197
+ "reach",
18198
+ Math.max(0, 1 - card.blastRadius.totalAffected / REACH_NODES_MAX),
18199
+ ["blastRadius.totalAffected"],
18200
+ `nodes in the blast radius against a prior bound of ${REACH_NODES_MAX}`
18201
+ ) : factor("reach", null, ["blastRadius"], "no blast radius on the card");
18202
+ const timeout = TIMEOUT.test(`${card.exceptionType ?? ""} ${card.message}`);
18203
+ 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;
18204
+ const kind = factor("kind", kindValue, ["incidentKind", "exceptionType", "message"]);
18205
+ const chainFactor = rc ? factor(
18206
+ "chain",
18207
+ 1 / (1 + 0.25 * (Math.max(1, chain.length - 1) - 1)),
18208
+ ["rootCause.chain"]
18209
+ ) : factor("chain", null, ["rootCause"], "no root cause on the card");
18210
+ const occurrences = card.count ?? 1;
18211
+ const recurValue = Math.min(1, Math.log(1 + occurrences) / Math.log(11));
18212
+ const recur = factor(
18213
+ "recur",
18214
+ recurValue,
18215
+ ["count"],
18216
+ card.count === void 0 ? "no coalesced count; one recorded occurrence" : void 0
18217
+ );
18218
+ const div = factor("div", (card.divergence ?? []).length > 0 ? 1 : 0.7, ["divergence"]);
18219
+ const factors = { evidence, locus, tests, reach, kind, chain: chainFactor, recur, div };
18220
+ let weighted = 0;
18221
+ let total = 0;
18222
+ for (const f of Object.values(factors)) {
18223
+ if (f.value === null) continue;
18224
+ weighted += f.weight * f.value;
18225
+ total += f.weight;
18226
+ }
18227
+ const C = round2(weighted / total);
18228
+ const G = round2(gamma * C);
18229
+ const band = gamma === 0 || G < 0.5 ? "out" : G >= 0.75 ? "full" : "diagnose-only";
18230
+ return {
18231
+ G,
18232
+ gamma,
18233
+ gates,
18234
+ C,
18235
+ factors,
18236
+ band,
18237
+ urgency: {
18238
+ value: null,
18239
+ reason: "f_fresh needs the time of the last OBSERVED signal at the node, which the card does not carry"
18240
+ },
18241
+ priorsVersion: GRADE_PRIORS_VERSION
18242
+ };
18243
+ }
18139
18244
 
18140
18245
  // src/ask.ts
18141
18246
  init_cjs_shims();
@@ -25272,12 +25377,22 @@ init_cjs_shims();
25272
25377
  // src/connectors/kubernetes/client.ts
25273
25378
  init_cjs_shims();
25274
25379
  var import_node_https = require("https");
25380
+ var K8S_RATE_LIMIT = { capacity: 120, refillMs: 1e3 };
25275
25381
  function deploymentsPath(namespace) {
25276
25382
  return `/apis/apps/v1/namespaces/${namespace}/deployments`;
25277
25383
  }
25278
25384
  function podsPath(namespace) {
25279
25385
  return `/api/v1/namespaces/${namespace}/pods`;
25280
25386
  }
25387
+ function podLogPath(namespace, podName, opts = {}) {
25388
+ const params = new URLSearchParams();
25389
+ if (opts.container) params.set("container", opts.container);
25390
+ if (typeof opts.tailLines === "number" && Number.isFinite(opts.tailLines)) params.set("tailLines", String(Math.trunc(opts.tailLines)));
25391
+ if (typeof opts.limitBytes === "number" && Number.isFinite(opts.limitBytes)) params.set("limitBytes", String(Math.trunc(opts.limitBytes)));
25392
+ if (opts.previous) params.set("previous", "true");
25393
+ const qs = params.toString();
25394
+ return `${podsPath(namespace)}/${podName}/log${qs ? `?${qs}` : ""}`;
25395
+ }
25281
25396
  function makeK8sFetchImpl(transport) {
25282
25397
  const agent = new import_node_https.Agent({
25283
25398
  ...transport.ca ? { ca: transport.ca } : {},
@@ -25328,7 +25443,7 @@ async function listResource(transport, namespace, path84, opts = {}) {
25328
25443
  { method: "GET", headers: { ...transport.token ? bearerAuthHeader(transport.token) : {}, Accept: "application/json" } },
25329
25444
  // accountKey: the (cluster, namespace) pair — an identifier, safe to log, the
25330
25445
  // rate-limit bucket for one namespace on one cluster (ADR-131).
25331
- { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
25446
+ { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, rateLimit: K8S_RATE_LIMIT, fetchImpl }
25332
25447
  );
25333
25448
  if (!res.ok) {
25334
25449
  throw new Error(`kubernetes ${path84} failed: ${res.status} ${res.statusText}`);
@@ -25349,6 +25464,28 @@ async function fetchDeployments(transport, namespace, opts = {}) {
25349
25464
  async function fetchPods(transport, namespace, opts = {}) {
25350
25465
  return listResource(transport, namespace, podsPath(namespace), opts);
25351
25466
  }
25467
+ async function fetchPodLog(transport, namespace, podName, opts = {}) {
25468
+ const base = opts.apiUrl ?? transport.server;
25469
+ const path84 = podLogPath(namespace, podName, {
25470
+ ...opts.container ? { container: opts.container } : {},
25471
+ ...opts.tailLines !== void 0 ? { tailLines: opts.tailLines } : {},
25472
+ ...opts.limitBytes !== void 0 ? { limitBytes: opts.limitBytes } : {},
25473
+ ...opts.previous ? { previous: true } : {}
25474
+ });
25475
+ const url = `${base.replace(/\/$/, "")}${path84}`;
25476
+ const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
25477
+ try {
25478
+ const res = await junctionFetch(
25479
+ url,
25480
+ { method: "GET", headers: { ...transport.token ? bearerAuthHeader(transport.token) : {}, Accept: "text/plain" } },
25481
+ { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, rateLimit: K8S_RATE_LIMIT, fetchImpl }
25482
+ );
25483
+ if (!res.ok) return void 0;
25484
+ return await res.text();
25485
+ } catch {
25486
+ return void 0;
25487
+ }
25488
+ }
25352
25489
 
25353
25490
  // src/connectors/kubernetes/kubeconfig.ts
25354
25491
  init_cjs_shims();
@@ -25456,6 +25593,123 @@ function parseK8sTargetName(targetName) {
25456
25593
  return { serviceName, fault };
25457
25594
  }
25458
25595
 
25596
+ // src/connectors/kubernetes/process-context.ts
25597
+ init_cjs_shims();
25598
+ var DEFAULT_MAX_LOG_LINES = 50;
25599
+ var DEFAULT_MAX_LOG_BYTES2 = 2 * 1024;
25600
+ var DEFAULT_MAX_ENTRIES = 64;
25601
+ var DEFAULT_MAX_VALUE_CHARS = 512;
25602
+ var REDACTED = "***REDACTED***";
25603
+ 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;
25604
+ function isSecretKey(name) {
25605
+ return SECRET_KEY_RE.test(name);
25606
+ }
25607
+ var PRIVATE_KEY_BLOCK_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g;
25608
+ var PRIVATE_KEY_TAIL_RE = /(?:^[A-Za-z0-9+/=]{32,}\r?\n)+-----END [A-Z ]*PRIVATE KEY-----/gm;
25609
+ var URL_USERINFO_RE = /([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^\s/@]*)@/g;
25610
+ 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;
25611
+ var AUTH_SCHEME_RE = /\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{8,}/g;
25612
+ 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;
25613
+ function redactText(text) {
25614
+ return text.replace(PRIVATE_KEY_BLOCK_RE, REDACTED).replace(PRIVATE_KEY_TAIL_RE, REDACTED).replace(URL_USERINFO_RE, (_m, scheme, userinfo) => {
25615
+ const colon = userinfo.indexOf(":");
25616
+ return colon > 0 ? `${scheme}${userinfo.slice(0, colon)}:${REDACTED}@` : `${scheme}${REDACTED}@`;
25617
+ }).replace(SECRET_PAIR_RE, (_m, key) => `${key}${REDACTED}`).replace(AUTH_SCHEME_RE, (_m, scheme) => `${scheme} ${REDACTED}`).replace(KNOWN_TOKEN_RE, REDACTED);
25618
+ }
25619
+ function redactValue(name, value) {
25620
+ if (isSecretKey(name)) return REDACTED;
25621
+ return redactText(value);
25622
+ }
25623
+ function redactArg(arg) {
25624
+ const eq = /^(--?)?([A-Za-z0-9_.-]+)=([\s\S]*)$/.exec(arg);
25625
+ if (eq && isSecretKey(eq[2])) {
25626
+ return `${eq[1] ?? ""}${eq[2]}=${REDACTED}`;
25627
+ }
25628
+ return redactText(arg);
25629
+ }
25630
+ function isSecretFlag(arg) {
25631
+ const m = /^--?([A-Za-z0-9_.-]+)$/.exec(arg);
25632
+ return m !== null && isSecretKey(m[1]);
25633
+ }
25634
+ function capValue(s, max = DEFAULT_MAX_VALUE_CHARS) {
25635
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
25636
+ }
25637
+ function describeEnvVar(ev) {
25638
+ const name = typeof ev.name === "string" ? ev.name : "";
25639
+ if (name.length === 0) return void 0;
25640
+ if (typeof ev.value === "string") {
25641
+ return `${name}=${capValue(redactValue(name, ev.value))}`;
25642
+ }
25643
+ const from = ev.valueFrom;
25644
+ if (from) {
25645
+ if (from.secretKeyRef) {
25646
+ const ref = typeof from.secretKeyRef.name === "string" && from.secretKeyRef.name.length > 0 ? from.secretKeyRef.name : "<unnamed>";
25647
+ return `${name}=<from secret ${ref}>`;
25648
+ }
25649
+ if (from.configMapKeyRef) {
25650
+ const ref = typeof from.configMapKeyRef.name === "string" && from.configMapKeyRef.name.length > 0 ? from.configMapKeyRef.name : "<unnamed>";
25651
+ const key = typeof from.configMapKeyRef.key === "string" && from.configMapKeyRef.key.length > 0 ? ` key ${from.configMapKeyRef.key}` : "";
25652
+ return `${name}=<from configMap ${ref}${key}>`;
25653
+ }
25654
+ if (from.fieldRef && typeof from.fieldRef.fieldPath === "string" && from.fieldRef.fieldPath.length > 0) {
25655
+ return `${name}=<from field ${from.fieldRef.fieldPath}>`;
25656
+ }
25657
+ if (from.resourceFieldRef && typeof from.resourceFieldRef.resource === "string" && from.resourceFieldRef.resource.length > 0) {
25658
+ return `${name}=<from resource ${from.resourceFieldRef.resource}>`;
25659
+ }
25660
+ return `${name}=<from reference>`;
25661
+ }
25662
+ return `${name}=`;
25663
+ }
25664
+ function buildContainerEnv(container) {
25665
+ const out = [];
25666
+ for (const ev of container.env ?? []) {
25667
+ const d = describeEnvVar(ev);
25668
+ if (d !== void 0) out.push(d);
25669
+ if (out.length >= DEFAULT_MAX_ENTRIES) break;
25670
+ }
25671
+ return out;
25672
+ }
25673
+ function buildContainerArgs(container) {
25674
+ const raw = [];
25675
+ for (const c of container.command ?? []) if (typeof c === "string") raw.push(c);
25676
+ for (const a of container.args ?? []) if (typeof a === "string") raw.push(a);
25677
+ const out = [];
25678
+ for (let i = 0; i < raw.length && out.length < DEFAULT_MAX_ENTRIES; i++) {
25679
+ const arg = raw[i];
25680
+ out.push(capValue(redactArg(arg)));
25681
+ const next = raw[i + 1];
25682
+ if (isSecretFlag(arg) && next !== void 0 && !next.startsWith("-") && out.length < DEFAULT_MAX_ENTRIES) {
25683
+ out.push(REDACTED);
25684
+ i++;
25685
+ }
25686
+ }
25687
+ return out;
25688
+ }
25689
+ function capProcessLog(text, maxLines = DEFAULT_MAX_LOG_LINES, maxBytes = DEFAULT_MAX_LOG_BYTES2) {
25690
+ const trimmed = text.replace(/\s+$/, "");
25691
+ if (trimmed.length === 0) return "";
25692
+ const lines = trimmed.split("\n");
25693
+ const tail = lines.length > maxLines ? lines.slice(lines.length - maxLines) : lines;
25694
+ let out = tail.join("\n");
25695
+ if (out.length > maxBytes) out = out.slice(out.length - maxBytes);
25696
+ return out;
25697
+ }
25698
+ function buildProcessContext(input) {
25699
+ const out = {};
25700
+ if (typeof input.log === "string") {
25701
+ const capped = capProcessLog(redactText(input.log));
25702
+ if (capped.length > 0) out.processLog = capped;
25703
+ }
25704
+ if (input.container) {
25705
+ const args = buildContainerArgs(input.container);
25706
+ if (args.length > 0) out.containerArgs = args;
25707
+ const env = buildContainerEnv(input.container);
25708
+ if (env.length > 0) out.containerEnv = env;
25709
+ }
25710
+ return out;
25711
+ }
25712
+
25459
25713
  // src/connectors/kubernetes/map.ts
25460
25714
  function serviceNameFor(deployment, config) {
25461
25715
  const name = deployment.metadata?.name ?? "";
@@ -25473,6 +25727,9 @@ function podsForDeployment(deployment, pods) {
25473
25727
  function nowIso2() {
25474
25728
  return (/* @__PURE__ */ new Date()).toISOString();
25475
25729
  }
25730
+ function podLogKey(podName, container) {
25731
+ return container ? `${podName}\0${container}` : podName;
25732
+ }
25476
25733
  function podLevelFault(deployment, pods) {
25477
25734
  const name = deployment.metadata?.name ?? "";
25478
25735
  const owned = podsForDeployment(deployment, pods);
@@ -25490,7 +25747,9 @@ function podLevelFault(deployment, pods) {
25490
25747
  fault: "image-pull",
25491
25748
  message: `Deployment ${name} cannot pull image ${image} (${reason})`,
25492
25749
  timestamp: pod.status?.startTime ?? nowIso2(),
25493
- attributes: attrs
25750
+ attributes: attrs,
25751
+ pod,
25752
+ ...typeof cs.name === "string" && cs.name.length > 0 ? { container: cs.name } : {}
25494
25753
  };
25495
25754
  }
25496
25755
  if (reason === CRASH_LOOP_REASON && !crash) crash = { cs, pod };
@@ -25512,11 +25771,47 @@ function podLevelFault(deployment, pods) {
25512
25771
  fault: "crash-loop",
25513
25772
  message: `Deployment ${name} is crashlooping (restarts: ${restarts}); ${detail}`,
25514
25773
  timestamp: term?.finishedAt ?? pod.status?.startTime ?? nowIso2(),
25515
- attributes: attrs
25774
+ attributes: attrs,
25775
+ pod,
25776
+ ...typeof cs.name === "string" && cs.name.length > 0 ? { container: cs.name } : {}
25516
25777
  };
25517
25778
  }
25518
25779
  return null;
25519
25780
  }
25781
+ function faultedContainer(finding) {
25782
+ const containers = finding.pod?.spec?.containers;
25783
+ if (!Array.isArray(containers) || containers.length === 0) return void 0;
25784
+ if (finding.container) {
25785
+ const named2 = containers.find((c) => c.name === finding.container);
25786
+ if (named2) return named2;
25787
+ }
25788
+ return containers.length === 1 ? containers[0] : void 0;
25789
+ }
25790
+ function mergeProcessContext(finding, logs) {
25791
+ const container = faultedContainer(finding);
25792
+ const podName = finding.pod?.metadata?.name;
25793
+ const log = logs && podName ? logs.get(podLogKey(podName, finding.container)) : void 0;
25794
+ if (!container && log === void 0) return;
25795
+ const ctx = buildProcessContext({
25796
+ ...log !== void 0 ? { log } : {},
25797
+ ...container ? { container } : {}
25798
+ });
25799
+ if (ctx.processLog !== void 0) finding.attributes["k8s.processLog"] = ctx.processLog;
25800
+ if (ctx.containerArgs !== void 0) finding.attributes["k8s.containerArgs"] = ctx.containerArgs;
25801
+ if (ctx.containerEnv !== void 0) finding.attributes["k8s.containerEnv"] = ctx.containerEnv;
25802
+ }
25803
+ function faultedPods(deployments, pods, config) {
25804
+ const out = [];
25805
+ const expectedZero = config.expectedZero ? new Set(config.expectedZero) : void 0;
25806
+ for (const deployment of deployments) {
25807
+ const finding = classifyDeployment(deployment, pods, expectedZero);
25808
+ if (!finding || finding.fault !== "crash-loop") continue;
25809
+ const podName = finding.pod?.metadata?.name;
25810
+ if (typeof podName !== "string" || podName.length === 0) continue;
25811
+ out.push({ podName, ...finding.container ? { container: finding.container } : {} });
25812
+ }
25813
+ return out;
25814
+ }
25520
25815
  function classifyDeployment(deployment, pods, expectedZero) {
25521
25816
  const name = deployment.metadata?.name ?? "";
25522
25817
  const desired = typeof deployment.spec?.replicas === "number" ? deployment.spec.replicas : 1;
@@ -25544,12 +25839,13 @@ function classifyDeployment(deployment, pods, expectedZero) {
25544
25839
  attributes: { "k8s.desiredReplicas": desired, "k8s.readyReplicas": ready }
25545
25840
  };
25546
25841
  }
25547
- function mapDeploymentToSignal(deployment, pods, config) {
25842
+ function mapDeploymentToSignal(deployment, pods, config, logs) {
25548
25843
  const name = deployment.metadata?.name;
25549
25844
  if (typeof name !== "string" || name.length === 0) return null;
25550
25845
  const expectedZero = config.expectedZero ? new Set(config.expectedZero) : void 0;
25551
25846
  const finding = classifyDeployment(deployment, pods, expectedZero);
25552
25847
  if (!finding) return null;
25848
+ mergeProcessContext(finding, logs);
25553
25849
  const serviceName = serviceNameFor(deployment, config);
25554
25850
  const namespace = deployment.metadata?.namespace ?? config.namespace;
25555
25851
  const attributes = {
@@ -25602,12 +25898,12 @@ function deployStateSignal(deployment, pods, config) {
25602
25898
  deployState: observedDeployState(deployment, pods)
25603
25899
  };
25604
25900
  }
25605
- function mapWorkloadsToSignals(deployments, pods, config) {
25901
+ function mapWorkloadsToSignals(deployments, pods, config, logs) {
25606
25902
  const out = [];
25607
25903
  for (const deployment of deployments) {
25608
25904
  const deployState = deployStateSignal(deployment, pods, config);
25609
25905
  if (deployState) out.push(deployState);
25610
- const incident = mapDeploymentToSignal(deployment, pods, config);
25906
+ const incident = mapDeploymentToSignal(deployment, pods, config, logs);
25611
25907
  if (incident) out.push(incident);
25612
25908
  }
25613
25909
  return out;
@@ -25706,6 +26002,8 @@ async function startK8sSubstratePolling(input) {
25706
26002
  }
25707
26003
 
25708
26004
  // src/connectors/kubernetes/index.ts
26005
+ var PROCESS_LOG_TAIL_LINES = 200;
26006
+ var PROCESS_LOG_LIMIT_BYTES = 64 * 1024;
25709
26007
  var KubernetesConnector = class {
25710
26008
  constructor(config, fetchImpl) {
25711
26009
  this.config = config;
@@ -25726,7 +26024,25 @@ var KubernetesConnector = class {
25726
26024
  fetchDeployments(transport, namespace, opts),
25727
26025
  fetchPods(transport, namespace, opts)
25728
26026
  ]);
25729
- return mapWorkloadsToSignals(deployments, pods, this.config);
26027
+ const logs = await this.fetchFaultedPodLogs(transport, namespace, deployments, pods, opts);
26028
+ return mapWorkloadsToSignals(deployments, pods, this.config, logs);
26029
+ }
26030
+ async fetchFaultedPodLogs(transport, namespace, deployments, pods, opts) {
26031
+ const logs = /* @__PURE__ */ new Map();
26032
+ const targets = faultedPods(deployments, pods, this.config);
26033
+ await Promise.all(
26034
+ targets.map(async (t) => {
26035
+ const text = await fetchPodLog(transport, namespace, t.podName, {
26036
+ ...t.container ? { container: t.container } : {},
26037
+ tailLines: PROCESS_LOG_TAIL_LINES,
26038
+ limitBytes: PROCESS_LOG_LIMIT_BYTES,
26039
+ previous: true,
26040
+ ...opts
26041
+ });
26042
+ if (typeof text === "string" && text.length > 0) logs.set(podLogKey(t.podName, t.container), text);
26043
+ })
26044
+ );
26045
+ return logs;
25730
26046
  }
25731
26047
  };
25732
26048
  function createKubernetesConnector(graph, config, fetchImpl) {