@neat.is/core 0.10.6-dev.20261009 → 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-ECBRGTB2.js";
4
+ } from "./chunk-52SCEPEW.js";
5
5
  import {
6
6
  buildApi,
7
7
  checkCompatibility,
package/dist/neatd.cjs CHANGED
@@ -25377,12 +25377,22 @@ init_cjs_shims();
25377
25377
  // src/connectors/kubernetes/client.ts
25378
25378
  init_cjs_shims();
25379
25379
  var import_node_https = require("https");
25380
+ var K8S_RATE_LIMIT = { capacity: 120, refillMs: 1e3 };
25380
25381
  function deploymentsPath(namespace) {
25381
25382
  return `/apis/apps/v1/namespaces/${namespace}/deployments`;
25382
25383
  }
25383
25384
  function podsPath(namespace) {
25384
25385
  return `/api/v1/namespaces/${namespace}/pods`;
25385
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
+ }
25386
25396
  function makeK8sFetchImpl(transport) {
25387
25397
  const agent = new import_node_https.Agent({
25388
25398
  ...transport.ca ? { ca: transport.ca } : {},
@@ -25433,7 +25443,7 @@ async function listResource(transport, namespace, path84, opts = {}) {
25433
25443
  { method: "GET", headers: { ...transport.token ? bearerAuthHeader(transport.token) : {}, Accept: "application/json" } },
25434
25444
  // accountKey: the (cluster, namespace) pair — an identifier, safe to log, the
25435
25445
  // rate-limit bucket for one namespace on one cluster (ADR-131).
25436
- { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
25446
+ { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, rateLimit: K8S_RATE_LIMIT, fetchImpl }
25437
25447
  );
25438
25448
  if (!res.ok) {
25439
25449
  throw new Error(`kubernetes ${path84} failed: ${res.status} ${res.statusText}`);
@@ -25454,6 +25464,28 @@ async function fetchDeployments(transport, namespace, opts = {}) {
25454
25464
  async function fetchPods(transport, namespace, opts = {}) {
25455
25465
  return listResource(transport, namespace, podsPath(namespace), opts);
25456
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
+ }
25457
25489
 
25458
25490
  // src/connectors/kubernetes/kubeconfig.ts
25459
25491
  init_cjs_shims();
@@ -25561,6 +25593,123 @@ function parseK8sTargetName(targetName) {
25561
25593
  return { serviceName, fault };
25562
25594
  }
25563
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
+
25564
25713
  // src/connectors/kubernetes/map.ts
25565
25714
  function serviceNameFor(deployment, config) {
25566
25715
  const name = deployment.metadata?.name ?? "";
@@ -25578,6 +25727,9 @@ function podsForDeployment(deployment, pods) {
25578
25727
  function nowIso2() {
25579
25728
  return (/* @__PURE__ */ new Date()).toISOString();
25580
25729
  }
25730
+ function podLogKey(podName, container) {
25731
+ return container ? `${podName}\0${container}` : podName;
25732
+ }
25581
25733
  function podLevelFault(deployment, pods) {
25582
25734
  const name = deployment.metadata?.name ?? "";
25583
25735
  const owned = podsForDeployment(deployment, pods);
@@ -25595,7 +25747,9 @@ function podLevelFault(deployment, pods) {
25595
25747
  fault: "image-pull",
25596
25748
  message: `Deployment ${name} cannot pull image ${image} (${reason})`,
25597
25749
  timestamp: pod.status?.startTime ?? nowIso2(),
25598
- attributes: attrs
25750
+ attributes: attrs,
25751
+ pod,
25752
+ ...typeof cs.name === "string" && cs.name.length > 0 ? { container: cs.name } : {}
25599
25753
  };
25600
25754
  }
25601
25755
  if (reason === CRASH_LOOP_REASON && !crash) crash = { cs, pod };
@@ -25617,11 +25771,47 @@ function podLevelFault(deployment, pods) {
25617
25771
  fault: "crash-loop",
25618
25772
  message: `Deployment ${name} is crashlooping (restarts: ${restarts}); ${detail}`,
25619
25773
  timestamp: term?.finishedAt ?? pod.status?.startTime ?? nowIso2(),
25620
- attributes: attrs
25774
+ attributes: attrs,
25775
+ pod,
25776
+ ...typeof cs.name === "string" && cs.name.length > 0 ? { container: cs.name } : {}
25621
25777
  };
25622
25778
  }
25623
25779
  return null;
25624
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
+ }
25625
25815
  function classifyDeployment(deployment, pods, expectedZero) {
25626
25816
  const name = deployment.metadata?.name ?? "";
25627
25817
  const desired = typeof deployment.spec?.replicas === "number" ? deployment.spec.replicas : 1;
@@ -25649,12 +25839,13 @@ function classifyDeployment(deployment, pods, expectedZero) {
25649
25839
  attributes: { "k8s.desiredReplicas": desired, "k8s.readyReplicas": ready }
25650
25840
  };
25651
25841
  }
25652
- function mapDeploymentToSignal(deployment, pods, config) {
25842
+ function mapDeploymentToSignal(deployment, pods, config, logs) {
25653
25843
  const name = deployment.metadata?.name;
25654
25844
  if (typeof name !== "string" || name.length === 0) return null;
25655
25845
  const expectedZero = config.expectedZero ? new Set(config.expectedZero) : void 0;
25656
25846
  const finding = classifyDeployment(deployment, pods, expectedZero);
25657
25847
  if (!finding) return null;
25848
+ mergeProcessContext(finding, logs);
25658
25849
  const serviceName = serviceNameFor(deployment, config);
25659
25850
  const namespace = deployment.metadata?.namespace ?? config.namespace;
25660
25851
  const attributes = {
@@ -25707,12 +25898,12 @@ function deployStateSignal(deployment, pods, config) {
25707
25898
  deployState: observedDeployState(deployment, pods)
25708
25899
  };
25709
25900
  }
25710
- function mapWorkloadsToSignals(deployments, pods, config) {
25901
+ function mapWorkloadsToSignals(deployments, pods, config, logs) {
25711
25902
  const out = [];
25712
25903
  for (const deployment of deployments) {
25713
25904
  const deployState = deployStateSignal(deployment, pods, config);
25714
25905
  if (deployState) out.push(deployState);
25715
- const incident = mapDeploymentToSignal(deployment, pods, config);
25906
+ const incident = mapDeploymentToSignal(deployment, pods, config, logs);
25716
25907
  if (incident) out.push(incident);
25717
25908
  }
25718
25909
  return out;
@@ -25811,6 +26002,8 @@ async function startK8sSubstratePolling(input) {
25811
26002
  }
25812
26003
 
25813
26004
  // src/connectors/kubernetes/index.ts
26005
+ var PROCESS_LOG_TAIL_LINES = 200;
26006
+ var PROCESS_LOG_LIMIT_BYTES = 64 * 1024;
25814
26007
  var KubernetesConnector = class {
25815
26008
  constructor(config, fetchImpl) {
25816
26009
  this.config = config;
@@ -25831,7 +26024,25 @@ var KubernetesConnector = class {
25831
26024
  fetchDeployments(transport, namespace, opts),
25832
26025
  fetchPods(transport, namespace, opts)
25833
26026
  ]);
25834
- 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;
25835
26046
  }
25836
26047
  };
25837
26048
  function createKubernetesConnector(graph, config, fetchImpl) {