@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.
@@ -425,12 +425,22 @@ async function maybeStartHostedConnectors(input) {
425
425
 
426
426
  // src/connectors/kubernetes/client.ts
427
427
  import { Agent, request as httpsRequest } from "https";
428
+ var K8S_RATE_LIMIT = { capacity: 120, refillMs: 1e3 };
428
429
  function deploymentsPath(namespace) {
429
430
  return `/apis/apps/v1/namespaces/${namespace}/deployments`;
430
431
  }
431
432
  function podsPath(namespace) {
432
433
  return `/api/v1/namespaces/${namespace}/pods`;
433
434
  }
435
+ function podLogPath(namespace, podName, opts = {}) {
436
+ const params = new URLSearchParams();
437
+ if (opts.container) params.set("container", opts.container);
438
+ if (typeof opts.tailLines === "number" && Number.isFinite(opts.tailLines)) params.set("tailLines", String(Math.trunc(opts.tailLines)));
439
+ if (typeof opts.limitBytes === "number" && Number.isFinite(opts.limitBytes)) params.set("limitBytes", String(Math.trunc(opts.limitBytes)));
440
+ if (opts.previous) params.set("previous", "true");
441
+ const qs = params.toString();
442
+ return `${podsPath(namespace)}/${podName}/log${qs ? `?${qs}` : ""}`;
443
+ }
434
444
  function makeK8sFetchImpl(transport) {
435
445
  const agent = new Agent({
436
446
  ...transport.ca ? { ca: transport.ca } : {},
@@ -481,7 +491,7 @@ async function listResource(transport, namespace, path4, opts = {}) {
481
491
  { method: "GET", headers: { ...transport.token ? bearerAuthHeader(transport.token) : {}, Accept: "application/json" } },
482
492
  // accountKey: the (cluster, namespace) pair — an identifier, safe to log, the
483
493
  // rate-limit bucket for one namespace on one cluster (ADR-131).
484
- { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
494
+ { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, rateLimit: K8S_RATE_LIMIT, fetchImpl }
485
495
  );
486
496
  if (!res.ok) {
487
497
  throw new Error(`kubernetes ${path4} failed: ${res.status} ${res.statusText}`);
@@ -502,6 +512,28 @@ async function fetchDeployments(transport, namespace, opts = {}) {
502
512
  async function fetchPods(transport, namespace, opts = {}) {
503
513
  return listResource(transport, namespace, podsPath(namespace), opts);
504
514
  }
515
+ async function fetchPodLog(transport, namespace, podName, opts = {}) {
516
+ const base = opts.apiUrl ?? transport.server;
517
+ const path4 = podLogPath(namespace, podName, {
518
+ ...opts.container ? { container: opts.container } : {},
519
+ ...opts.tailLines !== void 0 ? { tailLines: opts.tailLines } : {},
520
+ ...opts.limitBytes !== void 0 ? { limitBytes: opts.limitBytes } : {},
521
+ ...opts.previous ? { previous: true } : {}
522
+ });
523
+ const url = `${base.replace(/\/$/, "")}${path4}`;
524
+ const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
525
+ try {
526
+ const res = await junctionFetch(
527
+ url,
528
+ { method: "GET", headers: { ...transport.token ? bearerAuthHeader(transport.token) : {}, Accept: "text/plain" } },
529
+ { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, rateLimit: K8S_RATE_LIMIT, fetchImpl }
530
+ );
531
+ if (!res.ok) return void 0;
532
+ return await res.text();
533
+ } catch {
534
+ return void 0;
535
+ }
536
+ }
505
537
 
506
538
  // src/connectors/kubernetes/kubeconfig.ts
507
539
  import { readFileSync } from "fs";
@@ -604,6 +636,122 @@ function parseK8sTargetName(targetName) {
604
636
  return { serviceName, fault };
605
637
  }
606
638
 
639
+ // src/connectors/kubernetes/process-context.ts
640
+ var DEFAULT_MAX_LOG_LINES = 50;
641
+ var DEFAULT_MAX_LOG_BYTES = 2 * 1024;
642
+ var DEFAULT_MAX_ENTRIES = 64;
643
+ var DEFAULT_MAX_VALUE_CHARS = 512;
644
+ var REDACTED = "***REDACTED***";
645
+ 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;
646
+ function isSecretKey(name) {
647
+ return SECRET_KEY_RE.test(name);
648
+ }
649
+ var PRIVATE_KEY_BLOCK_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g;
650
+ var PRIVATE_KEY_TAIL_RE = /(?:^[A-Za-z0-9+/=]{32,}\r?\n)+-----END [A-Z ]*PRIVATE KEY-----/gm;
651
+ var URL_USERINFO_RE = /([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^\s/@]*)@/g;
652
+ 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;
653
+ var AUTH_SCHEME_RE = /\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{8,}/g;
654
+ 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;
655
+ function redactText(text) {
656
+ return text.replace(PRIVATE_KEY_BLOCK_RE, REDACTED).replace(PRIVATE_KEY_TAIL_RE, REDACTED).replace(URL_USERINFO_RE, (_m, scheme, userinfo) => {
657
+ const colon = userinfo.indexOf(":");
658
+ return colon > 0 ? `${scheme}${userinfo.slice(0, colon)}:${REDACTED}@` : `${scheme}${REDACTED}@`;
659
+ }).replace(SECRET_PAIR_RE, (_m, key) => `${key}${REDACTED}`).replace(AUTH_SCHEME_RE, (_m, scheme) => `${scheme} ${REDACTED}`).replace(KNOWN_TOKEN_RE, REDACTED);
660
+ }
661
+ function redactValue(name, value) {
662
+ if (isSecretKey(name)) return REDACTED;
663
+ return redactText(value);
664
+ }
665
+ function redactArg(arg) {
666
+ const eq = /^(--?)?([A-Za-z0-9_.-]+)=([\s\S]*)$/.exec(arg);
667
+ if (eq && isSecretKey(eq[2])) {
668
+ return `${eq[1] ?? ""}${eq[2]}=${REDACTED}`;
669
+ }
670
+ return redactText(arg);
671
+ }
672
+ function isSecretFlag(arg) {
673
+ const m = /^--?([A-Za-z0-9_.-]+)$/.exec(arg);
674
+ return m !== null && isSecretKey(m[1]);
675
+ }
676
+ function capValue(s, max = DEFAULT_MAX_VALUE_CHARS) {
677
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
678
+ }
679
+ function describeEnvVar(ev) {
680
+ const name = typeof ev.name === "string" ? ev.name : "";
681
+ if (name.length === 0) return void 0;
682
+ if (typeof ev.value === "string") {
683
+ return `${name}=${capValue(redactValue(name, ev.value))}`;
684
+ }
685
+ const from = ev.valueFrom;
686
+ if (from) {
687
+ if (from.secretKeyRef) {
688
+ const ref = typeof from.secretKeyRef.name === "string" && from.secretKeyRef.name.length > 0 ? from.secretKeyRef.name : "<unnamed>";
689
+ return `${name}=<from secret ${ref}>`;
690
+ }
691
+ if (from.configMapKeyRef) {
692
+ const ref = typeof from.configMapKeyRef.name === "string" && from.configMapKeyRef.name.length > 0 ? from.configMapKeyRef.name : "<unnamed>";
693
+ const key = typeof from.configMapKeyRef.key === "string" && from.configMapKeyRef.key.length > 0 ? ` key ${from.configMapKeyRef.key}` : "";
694
+ return `${name}=<from configMap ${ref}${key}>`;
695
+ }
696
+ if (from.fieldRef && typeof from.fieldRef.fieldPath === "string" && from.fieldRef.fieldPath.length > 0) {
697
+ return `${name}=<from field ${from.fieldRef.fieldPath}>`;
698
+ }
699
+ if (from.resourceFieldRef && typeof from.resourceFieldRef.resource === "string" && from.resourceFieldRef.resource.length > 0) {
700
+ return `${name}=<from resource ${from.resourceFieldRef.resource}>`;
701
+ }
702
+ return `${name}=<from reference>`;
703
+ }
704
+ return `${name}=`;
705
+ }
706
+ function buildContainerEnv(container) {
707
+ const out = [];
708
+ for (const ev of container.env ?? []) {
709
+ const d = describeEnvVar(ev);
710
+ if (d !== void 0) out.push(d);
711
+ if (out.length >= DEFAULT_MAX_ENTRIES) break;
712
+ }
713
+ return out;
714
+ }
715
+ function buildContainerArgs(container) {
716
+ const raw = [];
717
+ for (const c of container.command ?? []) if (typeof c === "string") raw.push(c);
718
+ for (const a of container.args ?? []) if (typeof a === "string") raw.push(a);
719
+ const out = [];
720
+ for (let i = 0; i < raw.length && out.length < DEFAULT_MAX_ENTRIES; i++) {
721
+ const arg = raw[i];
722
+ out.push(capValue(redactArg(arg)));
723
+ const next = raw[i + 1];
724
+ if (isSecretFlag(arg) && next !== void 0 && !next.startsWith("-") && out.length < DEFAULT_MAX_ENTRIES) {
725
+ out.push(REDACTED);
726
+ i++;
727
+ }
728
+ }
729
+ return out;
730
+ }
731
+ function capProcessLog(text, maxLines = DEFAULT_MAX_LOG_LINES, maxBytes = DEFAULT_MAX_LOG_BYTES) {
732
+ const trimmed = text.replace(/\s+$/, "");
733
+ if (trimmed.length === 0) return "";
734
+ const lines = trimmed.split("\n");
735
+ const tail = lines.length > maxLines ? lines.slice(lines.length - maxLines) : lines;
736
+ let out = tail.join("\n");
737
+ if (out.length > maxBytes) out = out.slice(out.length - maxBytes);
738
+ return out;
739
+ }
740
+ function buildProcessContext(input) {
741
+ const out = {};
742
+ if (typeof input.log === "string") {
743
+ const capped = capProcessLog(redactText(input.log));
744
+ if (capped.length > 0) out.processLog = capped;
745
+ }
746
+ if (input.container) {
747
+ const args = buildContainerArgs(input.container);
748
+ if (args.length > 0) out.containerArgs = args;
749
+ const env = buildContainerEnv(input.container);
750
+ if (env.length > 0) out.containerEnv = env;
751
+ }
752
+ return out;
753
+ }
754
+
607
755
  // src/connectors/kubernetes/map.ts
608
756
  function serviceNameFor(deployment, config) {
609
757
  const name = deployment.metadata?.name ?? "";
@@ -621,6 +769,9 @@ function podsForDeployment(deployment, pods) {
621
769
  function nowIso() {
622
770
  return (/* @__PURE__ */ new Date()).toISOString();
623
771
  }
772
+ function podLogKey(podName, container) {
773
+ return container ? `${podName}\0${container}` : podName;
774
+ }
624
775
  function podLevelFault(deployment, pods) {
625
776
  const name = deployment.metadata?.name ?? "";
626
777
  const owned = podsForDeployment(deployment, pods);
@@ -638,7 +789,9 @@ function podLevelFault(deployment, pods) {
638
789
  fault: "image-pull",
639
790
  message: `Deployment ${name} cannot pull image ${image} (${reason})`,
640
791
  timestamp: pod.status?.startTime ?? nowIso(),
641
- attributes: attrs
792
+ attributes: attrs,
793
+ pod,
794
+ ...typeof cs.name === "string" && cs.name.length > 0 ? { container: cs.name } : {}
642
795
  };
643
796
  }
644
797
  if (reason === CRASH_LOOP_REASON && !crash) crash = { cs, pod };
@@ -660,11 +813,47 @@ function podLevelFault(deployment, pods) {
660
813
  fault: "crash-loop",
661
814
  message: `Deployment ${name} is crashlooping (restarts: ${restarts}); ${detail}`,
662
815
  timestamp: term?.finishedAt ?? pod.status?.startTime ?? nowIso(),
663
- attributes: attrs
816
+ attributes: attrs,
817
+ pod,
818
+ ...typeof cs.name === "string" && cs.name.length > 0 ? { container: cs.name } : {}
664
819
  };
665
820
  }
666
821
  return null;
667
822
  }
823
+ function faultedContainer(finding) {
824
+ const containers = finding.pod?.spec?.containers;
825
+ if (!Array.isArray(containers) || containers.length === 0) return void 0;
826
+ if (finding.container) {
827
+ const named2 = containers.find((c) => c.name === finding.container);
828
+ if (named2) return named2;
829
+ }
830
+ return containers.length === 1 ? containers[0] : void 0;
831
+ }
832
+ function mergeProcessContext(finding, logs) {
833
+ const container = faultedContainer(finding);
834
+ const podName = finding.pod?.metadata?.name;
835
+ const log = logs && podName ? logs.get(podLogKey(podName, finding.container)) : void 0;
836
+ if (!container && log === void 0) return;
837
+ const ctx = buildProcessContext({
838
+ ...log !== void 0 ? { log } : {},
839
+ ...container ? { container } : {}
840
+ });
841
+ if (ctx.processLog !== void 0) finding.attributes["k8s.processLog"] = ctx.processLog;
842
+ if (ctx.containerArgs !== void 0) finding.attributes["k8s.containerArgs"] = ctx.containerArgs;
843
+ if (ctx.containerEnv !== void 0) finding.attributes["k8s.containerEnv"] = ctx.containerEnv;
844
+ }
845
+ function faultedPods(deployments, pods, config) {
846
+ const out = [];
847
+ const expectedZero = config.expectedZero ? new Set(config.expectedZero) : void 0;
848
+ for (const deployment of deployments) {
849
+ const finding = classifyDeployment(deployment, pods, expectedZero);
850
+ if (!finding || finding.fault !== "crash-loop") continue;
851
+ const podName = finding.pod?.metadata?.name;
852
+ if (typeof podName !== "string" || podName.length === 0) continue;
853
+ out.push({ podName, ...finding.container ? { container: finding.container } : {} });
854
+ }
855
+ return out;
856
+ }
668
857
  function classifyDeployment(deployment, pods, expectedZero) {
669
858
  const name = deployment.metadata?.name ?? "";
670
859
  const desired = typeof deployment.spec?.replicas === "number" ? deployment.spec.replicas : 1;
@@ -692,12 +881,13 @@ function classifyDeployment(deployment, pods, expectedZero) {
692
881
  attributes: { "k8s.desiredReplicas": desired, "k8s.readyReplicas": ready }
693
882
  };
694
883
  }
695
- function mapDeploymentToSignal(deployment, pods, config) {
884
+ function mapDeploymentToSignal(deployment, pods, config, logs) {
696
885
  const name = deployment.metadata?.name;
697
886
  if (typeof name !== "string" || name.length === 0) return null;
698
887
  const expectedZero = config.expectedZero ? new Set(config.expectedZero) : void 0;
699
888
  const finding = classifyDeployment(deployment, pods, expectedZero);
700
889
  if (!finding) return null;
890
+ mergeProcessContext(finding, logs);
701
891
  const serviceName = serviceNameFor(deployment, config);
702
892
  const namespace = deployment.metadata?.namespace ?? config.namespace;
703
893
  const attributes = {
@@ -750,12 +940,12 @@ function deployStateSignal(deployment, pods, config) {
750
940
  deployState: observedDeployState(deployment, pods)
751
941
  };
752
942
  }
753
- function mapWorkloadsToSignals(deployments, pods, config) {
943
+ function mapWorkloadsToSignals(deployments, pods, config, logs) {
754
944
  const out = [];
755
945
  for (const deployment of deployments) {
756
946
  const deployState = deployStateSignal(deployment, pods, config);
757
947
  if (deployState) out.push(deployState);
758
- const incident = mapDeploymentToSignal(deployment, pods, config);
948
+ const incident = mapDeploymentToSignal(deployment, pods, config, logs);
759
949
  if (incident) out.push(incident);
760
950
  }
761
951
  return out;
@@ -852,6 +1042,8 @@ async function startK8sSubstratePolling(input) {
852
1042
  }
853
1043
 
854
1044
  // src/connectors/kubernetes/index.ts
1045
+ var PROCESS_LOG_TAIL_LINES = 200;
1046
+ var PROCESS_LOG_LIMIT_BYTES = 64 * 1024;
855
1047
  var KubernetesConnector = class {
856
1048
  constructor(config, fetchImpl) {
857
1049
  this.config = config;
@@ -872,7 +1064,25 @@ var KubernetesConnector = class {
872
1064
  fetchDeployments(transport, namespace, opts),
873
1065
  fetchPods(transport, namespace, opts)
874
1066
  ]);
875
- return mapWorkloadsToSignals(deployments, pods, this.config);
1067
+ const logs = await this.fetchFaultedPodLogs(transport, namespace, deployments, pods, opts);
1068
+ return mapWorkloadsToSignals(deployments, pods, this.config, logs);
1069
+ }
1070
+ async fetchFaultedPodLogs(transport, namespace, deployments, pods, opts) {
1071
+ const logs = /* @__PURE__ */ new Map();
1072
+ const targets = faultedPods(deployments, pods, this.config);
1073
+ await Promise.all(
1074
+ targets.map(async (t) => {
1075
+ const text = await fetchPodLog(transport, namespace, t.podName, {
1076
+ ...t.container ? { container: t.container } : {},
1077
+ tailLines: PROCESS_LOG_TAIL_LINES,
1078
+ limitBytes: PROCESS_LOG_LIMIT_BYTES,
1079
+ previous: true,
1080
+ ...opts
1081
+ });
1082
+ if (typeof text === "string" && text.length > 0) logs.set(podLogKey(t.podName, t.container), text);
1083
+ })
1084
+ );
1085
+ return logs;
876
1086
  }
877
1087
  };
878
1088
  function createKubernetesConnector(graph, config, fetchImpl) {
@@ -1802,4 +2012,4 @@ export {
1802
2012
  resolveHost,
1803
2013
  startDaemon
1804
2014
  };
1805
- //# sourceMappingURL=chunk-ECBRGTB2.js.map
2015
+ //# sourceMappingURL=chunk-52SCEPEW.js.map