@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/cli.cjs CHANGED
@@ -18365,10 +18365,11 @@ function locusOf(graph, ev) {
18365
18365
  const rawLine = ev.attributes?.[CODE_LINENO_ATTR2];
18366
18366
  const line = typeof rawLine === "number" ? rawLine : Number(rawLine);
18367
18367
  const node = graph.hasNode(ev.affectedNode) ? graph.getNodeAttributes(ev.affectedNode) : void 0;
18368
+ const symbol = node?.type === "SymbolNode" ? node.qualname : void 0;
18368
18369
  return {
18369
18370
  file,
18370
18371
  ...Number.isFinite(line) ? { lineStart: line, lineEnd: line } : {},
18371
- ...node?.name ? { symbol: node.name } : {},
18372
+ ...symbol ? { symbol } : {},
18372
18373
  service: node?.service ?? ev.service,
18373
18374
  provenance: import_types63.Provenance.OBSERVED
18374
18375
  };
@@ -18464,7 +18465,8 @@ function buildIncidentCard(graph, errorEvent, incidents, policies) {
18464
18465
  const policyCards = applicable.map((p) => ({
18465
18466
  policyName: p.policyName,
18466
18467
  severity: p.severity,
18467
- message: p.reason
18468
+ message: p.reason,
18469
+ onViolation: p.onViolation
18468
18470
  }));
18469
18471
  const divergences = inGraph ? computeDivergences(graph, { node: affected, incidents }).divergences.map((d) => ({
18470
18472
  type: d.type,
@@ -18491,8 +18493,111 @@ function buildIncidentCard(graph, errorEvent, incidents, policies) {
18491
18493
  ...divergences.length > 0 ? { divergence: divergences } : {},
18492
18494
  headline: renderHeadline(graph, errorEvent, locus, rootCause?.node ?? null)
18493
18495
  };
18496
+ card.grade = gradeIncidentCard(card);
18494
18497
  return import_types63.IncidentCardSchema.parse(card);
18495
18498
  }
18499
+ var GRADE_PRIORS_VERSION = "sniper-architecture-priors-2026-10-08";
18500
+ var GRADE_WEIGHTS = {
18501
+ evidence: 3,
18502
+ locus: 2,
18503
+ tests: 2,
18504
+ reach: 1.5,
18505
+ kind: 1.5,
18506
+ chain: 1,
18507
+ recur: 1,
18508
+ div: 1
18509
+ };
18510
+ var PROVENANCE_WEIGHT = {
18511
+ OBSERVED: 1,
18512
+ EXTRACTED: 0.8,
18513
+ INFERRED: 0.4,
18514
+ FRONTIER: 0.3,
18515
+ STALE: 0.2
18516
+ };
18517
+ var REACH_NODES_MAX = 50;
18518
+ var TIMEOUT = /timeout|timed out|deadline/i;
18519
+ var round2 = (n) => Math.round(n * 1e4) / 1e4;
18520
+ function factor(name, value, evidence, reason) {
18521
+ return {
18522
+ value: value === null ? null : round2(value),
18523
+ weight: GRADE_WEIGHTS[name],
18524
+ evidence,
18525
+ ...reason ? { reason } : {}
18526
+ };
18527
+ }
18528
+ function gradeIncidentCard(card) {
18529
+ const rc = card.rootCause;
18530
+ const chain = rc?.chain ?? [];
18531
+ const gates = {
18532
+ policyNotBlock: {
18533
+ passed: !(card.policies ?? []).some((p) => p.onViolation === "block"),
18534
+ evidence: ["policies[].onViolation"]
18535
+ },
18536
+ notSymptomOnly: {
18537
+ passed: rc?.classification !== "symptom-only",
18538
+ evidence: ["rootCause.classification"]
18539
+ },
18540
+ locusResolves: { passed: card.locus !== null, evidence: ["locus"] }
18541
+ };
18542
+ const gamma = Object.values(gates).every((g) => g.passed) ? 1 : 0;
18543
+ const carrying = chain.some((h) => h.carriesSignal === true) ? chain.filter((h) => h.carriesSignal === true) : chain;
18544
+ const hopWeights = carrying.map((h) => PROVENANCE_WEIGHT[h.provenance]);
18545
+ 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"]);
18546
+ 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)");
18547
+ const tests = factor(
18548
+ "tests",
18549
+ null,
18550
+ [],
18551
+ "the card carries no test-file classification or test-to-symbol edges"
18552
+ );
18553
+ const reach = card.blastRadius ? factor(
18554
+ "reach",
18555
+ Math.max(0, 1 - card.blastRadius.totalAffected / REACH_NODES_MAX),
18556
+ ["blastRadius.totalAffected"],
18557
+ `nodes in the blast radius against a prior bound of ${REACH_NODES_MAX}`
18558
+ ) : factor("reach", null, ["blastRadius"], "no blast radius on the card");
18559
+ const timeout = TIMEOUT.test(`${card.exceptionType ?? ""} ${card.message}`);
18560
+ 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;
18561
+ const kind = factor("kind", kindValue, ["incidentKind", "exceptionType", "message"]);
18562
+ const chainFactor = rc ? factor(
18563
+ "chain",
18564
+ 1 / (1 + 0.25 * (Math.max(1, chain.length - 1) - 1)),
18565
+ ["rootCause.chain"]
18566
+ ) : factor("chain", null, ["rootCause"], "no root cause on the card");
18567
+ const occurrences = card.count ?? 1;
18568
+ const recurValue = Math.min(1, Math.log(1 + occurrences) / Math.log(11));
18569
+ const recur = factor(
18570
+ "recur",
18571
+ recurValue,
18572
+ ["count"],
18573
+ card.count === void 0 ? "no coalesced count; one recorded occurrence" : void 0
18574
+ );
18575
+ const div = factor("div", (card.divergence ?? []).length > 0 ? 1 : 0.7, ["divergence"]);
18576
+ const factors = { evidence, locus, tests, reach, kind, chain: chainFactor, recur, div };
18577
+ let weighted = 0;
18578
+ let total = 0;
18579
+ for (const f of Object.values(factors)) {
18580
+ if (f.value === null) continue;
18581
+ weighted += f.weight * f.value;
18582
+ total += f.weight;
18583
+ }
18584
+ const C = round2(weighted / total);
18585
+ const G = round2(gamma * C);
18586
+ const band = gamma === 0 || G < 0.5 ? "out" : G >= 0.75 ? "full" : "diagnose-only";
18587
+ return {
18588
+ G,
18589
+ gamma,
18590
+ gates,
18591
+ C,
18592
+ factors,
18593
+ band,
18594
+ urgency: {
18595
+ value: null,
18596
+ reason: "f_fresh needs the time of the last OBSERVED signal at the node, which the card does not carry"
18597
+ },
18598
+ priorsVersion: GRADE_PRIORS_VERSION
18599
+ };
18600
+ }
18496
18601
 
18497
18602
  // src/ask.ts
18498
18603
  init_cjs_shims();
@@ -25477,12 +25582,22 @@ init_cjs_shims();
25477
25582
  // src/connectors/kubernetes/client.ts
25478
25583
  init_cjs_shims();
25479
25584
  var import_node_https = require("https");
25585
+ var K8S_RATE_LIMIT = { capacity: 120, refillMs: 1e3 };
25480
25586
  function deploymentsPath(namespace) {
25481
25587
  return `/apis/apps/v1/namespaces/${namespace}/deployments`;
25482
25588
  }
25483
25589
  function podsPath(namespace) {
25484
25590
  return `/api/v1/namespaces/${namespace}/pods`;
25485
25591
  }
25592
+ function podLogPath(namespace, podName, opts = {}) {
25593
+ const params = new URLSearchParams();
25594
+ if (opts.container) params.set("container", opts.container);
25595
+ if (typeof opts.tailLines === "number" && Number.isFinite(opts.tailLines)) params.set("tailLines", String(Math.trunc(opts.tailLines)));
25596
+ if (typeof opts.limitBytes === "number" && Number.isFinite(opts.limitBytes)) params.set("limitBytes", String(Math.trunc(opts.limitBytes)));
25597
+ if (opts.previous) params.set("previous", "true");
25598
+ const qs = params.toString();
25599
+ return `${podsPath(namespace)}/${podName}/log${qs ? `?${qs}` : ""}`;
25600
+ }
25486
25601
  function makeK8sFetchImpl(transport) {
25487
25602
  const agent = new import_node_https.Agent({
25488
25603
  ...transport.ca ? { ca: transport.ca } : {},
@@ -25533,7 +25648,7 @@ async function listResource(transport, namespace, path104, opts = {}) {
25533
25648
  { method: "GET", headers: { ...transport.token ? bearerAuthHeader(transport.token) : {}, Accept: "application/json" } },
25534
25649
  // accountKey: the (cluster, namespace) pair — an identifier, safe to log, the
25535
25650
  // rate-limit bucket for one namespace on one cluster (ADR-131).
25536
- { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
25651
+ { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, rateLimit: K8S_RATE_LIMIT, fetchImpl }
25537
25652
  );
25538
25653
  if (!res.ok) {
25539
25654
  throw new Error(`kubernetes ${path104} failed: ${res.status} ${res.statusText}`);
@@ -25554,6 +25669,28 @@ async function fetchDeployments(transport, namespace, opts = {}) {
25554
25669
  async function fetchPods(transport, namespace, opts = {}) {
25555
25670
  return listResource(transport, namespace, podsPath(namespace), opts);
25556
25671
  }
25672
+ async function fetchPodLog(transport, namespace, podName, opts = {}) {
25673
+ const base = opts.apiUrl ?? transport.server;
25674
+ const path104 = podLogPath(namespace, podName, {
25675
+ ...opts.container ? { container: opts.container } : {},
25676
+ ...opts.tailLines !== void 0 ? { tailLines: opts.tailLines } : {},
25677
+ ...opts.limitBytes !== void 0 ? { limitBytes: opts.limitBytes } : {},
25678
+ ...opts.previous ? { previous: true } : {}
25679
+ });
25680
+ const url = `${base.replace(/\/$/, "")}${path104}`;
25681
+ const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
25682
+ try {
25683
+ const res = await junctionFetch(
25684
+ url,
25685
+ { method: "GET", headers: { ...transport.token ? bearerAuthHeader(transport.token) : {}, Accept: "text/plain" } },
25686
+ { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, rateLimit: K8S_RATE_LIMIT, fetchImpl }
25687
+ );
25688
+ if (!res.ok) return void 0;
25689
+ return await res.text();
25690
+ } catch {
25691
+ return void 0;
25692
+ }
25693
+ }
25557
25694
 
25558
25695
  // src/connectors/kubernetes/kubeconfig.ts
25559
25696
  init_cjs_shims();
@@ -25661,6 +25798,123 @@ function parseK8sTargetName(targetName) {
25661
25798
  return { serviceName, fault };
25662
25799
  }
25663
25800
 
25801
+ // src/connectors/kubernetes/process-context.ts
25802
+ init_cjs_shims();
25803
+ var DEFAULT_MAX_LOG_LINES = 50;
25804
+ var DEFAULT_MAX_LOG_BYTES2 = 2 * 1024;
25805
+ var DEFAULT_MAX_ENTRIES = 64;
25806
+ var DEFAULT_MAX_VALUE_CHARS = 512;
25807
+ var REDACTED = "***REDACTED***";
25808
+ 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;
25809
+ function isSecretKey(name) {
25810
+ return SECRET_KEY_RE.test(name);
25811
+ }
25812
+ var PRIVATE_KEY_BLOCK_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g;
25813
+ var PRIVATE_KEY_TAIL_RE = /(?:^[A-Za-z0-9+/=]{32,}\r?\n)+-----END [A-Z ]*PRIVATE KEY-----/gm;
25814
+ var URL_USERINFO_RE = /([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^\s/@]*)@/g;
25815
+ 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;
25816
+ var AUTH_SCHEME_RE = /\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{8,}/g;
25817
+ 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;
25818
+ function redactText(text) {
25819
+ return text.replace(PRIVATE_KEY_BLOCK_RE, REDACTED).replace(PRIVATE_KEY_TAIL_RE, REDACTED).replace(URL_USERINFO_RE, (_m, scheme, userinfo) => {
25820
+ const colon = userinfo.indexOf(":");
25821
+ return colon > 0 ? `${scheme}${userinfo.slice(0, colon)}:${REDACTED}@` : `${scheme}${REDACTED}@`;
25822
+ }).replace(SECRET_PAIR_RE, (_m, key) => `${key}${REDACTED}`).replace(AUTH_SCHEME_RE, (_m, scheme) => `${scheme} ${REDACTED}`).replace(KNOWN_TOKEN_RE, REDACTED);
25823
+ }
25824
+ function redactValue(name, value) {
25825
+ if (isSecretKey(name)) return REDACTED;
25826
+ return redactText(value);
25827
+ }
25828
+ function redactArg(arg) {
25829
+ const eq = /^(--?)?([A-Za-z0-9_.-]+)=([\s\S]*)$/.exec(arg);
25830
+ if (eq && isSecretKey(eq[2])) {
25831
+ return `${eq[1] ?? ""}${eq[2]}=${REDACTED}`;
25832
+ }
25833
+ return redactText(arg);
25834
+ }
25835
+ function isSecretFlag(arg) {
25836
+ const m = /^--?([A-Za-z0-9_.-]+)$/.exec(arg);
25837
+ return m !== null && isSecretKey(m[1]);
25838
+ }
25839
+ function capValue(s, max = DEFAULT_MAX_VALUE_CHARS) {
25840
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
25841
+ }
25842
+ function describeEnvVar(ev) {
25843
+ const name = typeof ev.name === "string" ? ev.name : "";
25844
+ if (name.length === 0) return void 0;
25845
+ if (typeof ev.value === "string") {
25846
+ return `${name}=${capValue(redactValue(name, ev.value))}`;
25847
+ }
25848
+ const from = ev.valueFrom;
25849
+ if (from) {
25850
+ if (from.secretKeyRef) {
25851
+ const ref = typeof from.secretKeyRef.name === "string" && from.secretKeyRef.name.length > 0 ? from.secretKeyRef.name : "<unnamed>";
25852
+ return `${name}=<from secret ${ref}>`;
25853
+ }
25854
+ if (from.configMapKeyRef) {
25855
+ const ref = typeof from.configMapKeyRef.name === "string" && from.configMapKeyRef.name.length > 0 ? from.configMapKeyRef.name : "<unnamed>";
25856
+ const key = typeof from.configMapKeyRef.key === "string" && from.configMapKeyRef.key.length > 0 ? ` key ${from.configMapKeyRef.key}` : "";
25857
+ return `${name}=<from configMap ${ref}${key}>`;
25858
+ }
25859
+ if (from.fieldRef && typeof from.fieldRef.fieldPath === "string" && from.fieldRef.fieldPath.length > 0) {
25860
+ return `${name}=<from field ${from.fieldRef.fieldPath}>`;
25861
+ }
25862
+ if (from.resourceFieldRef && typeof from.resourceFieldRef.resource === "string" && from.resourceFieldRef.resource.length > 0) {
25863
+ return `${name}=<from resource ${from.resourceFieldRef.resource}>`;
25864
+ }
25865
+ return `${name}=<from reference>`;
25866
+ }
25867
+ return `${name}=`;
25868
+ }
25869
+ function buildContainerEnv(container) {
25870
+ const out = [];
25871
+ for (const ev of container.env ?? []) {
25872
+ const d = describeEnvVar(ev);
25873
+ if (d !== void 0) out.push(d);
25874
+ if (out.length >= DEFAULT_MAX_ENTRIES) break;
25875
+ }
25876
+ return out;
25877
+ }
25878
+ function buildContainerArgs(container) {
25879
+ const raw = [];
25880
+ for (const c of container.command ?? []) if (typeof c === "string") raw.push(c);
25881
+ for (const a of container.args ?? []) if (typeof a === "string") raw.push(a);
25882
+ const out = [];
25883
+ for (let i = 0; i < raw.length && out.length < DEFAULT_MAX_ENTRIES; i++) {
25884
+ const arg = raw[i];
25885
+ out.push(capValue(redactArg(arg)));
25886
+ const next = raw[i + 1];
25887
+ if (isSecretFlag(arg) && next !== void 0 && !next.startsWith("-") && out.length < DEFAULT_MAX_ENTRIES) {
25888
+ out.push(REDACTED);
25889
+ i++;
25890
+ }
25891
+ }
25892
+ return out;
25893
+ }
25894
+ function capProcessLog(text, maxLines = DEFAULT_MAX_LOG_LINES, maxBytes = DEFAULT_MAX_LOG_BYTES2) {
25895
+ const trimmed = text.replace(/\s+$/, "");
25896
+ if (trimmed.length === 0) return "";
25897
+ const lines = trimmed.split("\n");
25898
+ const tail = lines.length > maxLines ? lines.slice(lines.length - maxLines) : lines;
25899
+ let out = tail.join("\n");
25900
+ if (out.length > maxBytes) out = out.slice(out.length - maxBytes);
25901
+ return out;
25902
+ }
25903
+ function buildProcessContext(input) {
25904
+ const out = {};
25905
+ if (typeof input.log === "string") {
25906
+ const capped = capProcessLog(redactText(input.log));
25907
+ if (capped.length > 0) out.processLog = capped;
25908
+ }
25909
+ if (input.container) {
25910
+ const args = buildContainerArgs(input.container);
25911
+ if (args.length > 0) out.containerArgs = args;
25912
+ const env = buildContainerEnv(input.container);
25913
+ if (env.length > 0) out.containerEnv = env;
25914
+ }
25915
+ return out;
25916
+ }
25917
+
25664
25918
  // src/connectors/kubernetes/map.ts
25665
25919
  function serviceNameFor(deployment, config) {
25666
25920
  const name = deployment.metadata?.name ?? "";
@@ -25678,6 +25932,9 @@ function podsForDeployment(deployment, pods) {
25678
25932
  function nowIso2() {
25679
25933
  return (/* @__PURE__ */ new Date()).toISOString();
25680
25934
  }
25935
+ function podLogKey(podName, container) {
25936
+ return container ? `${podName}\0${container}` : podName;
25937
+ }
25681
25938
  function podLevelFault(deployment, pods) {
25682
25939
  const name = deployment.metadata?.name ?? "";
25683
25940
  const owned = podsForDeployment(deployment, pods);
@@ -25695,7 +25952,9 @@ function podLevelFault(deployment, pods) {
25695
25952
  fault: "image-pull",
25696
25953
  message: `Deployment ${name} cannot pull image ${image} (${reason})`,
25697
25954
  timestamp: pod.status?.startTime ?? nowIso2(),
25698
- attributes: attrs
25955
+ attributes: attrs,
25956
+ pod,
25957
+ ...typeof cs.name === "string" && cs.name.length > 0 ? { container: cs.name } : {}
25699
25958
  };
25700
25959
  }
25701
25960
  if (reason === CRASH_LOOP_REASON && !crash) crash = { cs, pod };
@@ -25717,11 +25976,47 @@ function podLevelFault(deployment, pods) {
25717
25976
  fault: "crash-loop",
25718
25977
  message: `Deployment ${name} is crashlooping (restarts: ${restarts}); ${detail}`,
25719
25978
  timestamp: term?.finishedAt ?? pod.status?.startTime ?? nowIso2(),
25720
- attributes: attrs
25979
+ attributes: attrs,
25980
+ pod,
25981
+ ...typeof cs.name === "string" && cs.name.length > 0 ? { container: cs.name } : {}
25721
25982
  };
25722
25983
  }
25723
25984
  return null;
25724
25985
  }
25986
+ function faultedContainer(finding) {
25987
+ const containers = finding.pod?.spec?.containers;
25988
+ if (!Array.isArray(containers) || containers.length === 0) return void 0;
25989
+ if (finding.container) {
25990
+ const named2 = containers.find((c) => c.name === finding.container);
25991
+ if (named2) return named2;
25992
+ }
25993
+ return containers.length === 1 ? containers[0] : void 0;
25994
+ }
25995
+ function mergeProcessContext(finding, logs) {
25996
+ const container = faultedContainer(finding);
25997
+ const podName = finding.pod?.metadata?.name;
25998
+ const log = logs && podName ? logs.get(podLogKey(podName, finding.container)) : void 0;
25999
+ if (!container && log === void 0) return;
26000
+ const ctx = buildProcessContext({
26001
+ ...log !== void 0 ? { log } : {},
26002
+ ...container ? { container } : {}
26003
+ });
26004
+ if (ctx.processLog !== void 0) finding.attributes["k8s.processLog"] = ctx.processLog;
26005
+ if (ctx.containerArgs !== void 0) finding.attributes["k8s.containerArgs"] = ctx.containerArgs;
26006
+ if (ctx.containerEnv !== void 0) finding.attributes["k8s.containerEnv"] = ctx.containerEnv;
26007
+ }
26008
+ function faultedPods(deployments, pods, config) {
26009
+ const out = [];
26010
+ const expectedZero = config.expectedZero ? new Set(config.expectedZero) : void 0;
26011
+ for (const deployment of deployments) {
26012
+ const finding = classifyDeployment(deployment, pods, expectedZero);
26013
+ if (!finding || finding.fault !== "crash-loop") continue;
26014
+ const podName = finding.pod?.metadata?.name;
26015
+ if (typeof podName !== "string" || podName.length === 0) continue;
26016
+ out.push({ podName, ...finding.container ? { container: finding.container } : {} });
26017
+ }
26018
+ return out;
26019
+ }
25725
26020
  function classifyDeployment(deployment, pods, expectedZero) {
25726
26021
  const name = deployment.metadata?.name ?? "";
25727
26022
  const desired = typeof deployment.spec?.replicas === "number" ? deployment.spec.replicas : 1;
@@ -25749,12 +26044,13 @@ function classifyDeployment(deployment, pods, expectedZero) {
25749
26044
  attributes: { "k8s.desiredReplicas": desired, "k8s.readyReplicas": ready }
25750
26045
  };
25751
26046
  }
25752
- function mapDeploymentToSignal(deployment, pods, config) {
26047
+ function mapDeploymentToSignal(deployment, pods, config, logs) {
25753
26048
  const name = deployment.metadata?.name;
25754
26049
  if (typeof name !== "string" || name.length === 0) return null;
25755
26050
  const expectedZero = config.expectedZero ? new Set(config.expectedZero) : void 0;
25756
26051
  const finding = classifyDeployment(deployment, pods, expectedZero);
25757
26052
  if (!finding) return null;
26053
+ mergeProcessContext(finding, logs);
25758
26054
  const serviceName = serviceNameFor(deployment, config);
25759
26055
  const namespace = deployment.metadata?.namespace ?? config.namespace;
25760
26056
  const attributes = {
@@ -25807,12 +26103,12 @@ function deployStateSignal(deployment, pods, config) {
25807
26103
  deployState: observedDeployState(deployment, pods)
25808
26104
  };
25809
26105
  }
25810
- function mapWorkloadsToSignals(deployments, pods, config) {
26106
+ function mapWorkloadsToSignals(deployments, pods, config, logs) {
25811
26107
  const out = [];
25812
26108
  for (const deployment of deployments) {
25813
26109
  const deployState = deployStateSignal(deployment, pods, config);
25814
26110
  if (deployState) out.push(deployState);
25815
- const incident = mapDeploymentToSignal(deployment, pods, config);
26111
+ const incident = mapDeploymentToSignal(deployment, pods, config, logs);
25816
26112
  if (incident) out.push(incident);
25817
26113
  }
25818
26114
  return out;
@@ -25911,6 +26207,8 @@ async function startK8sSubstratePolling(input) {
25911
26207
  }
25912
26208
 
25913
26209
  // src/connectors/kubernetes/index.ts
26210
+ var PROCESS_LOG_TAIL_LINES = 200;
26211
+ var PROCESS_LOG_LIMIT_BYTES = 64 * 1024;
25914
26212
  var KubernetesConnector = class {
25915
26213
  constructor(config, fetchImpl) {
25916
26214
  this.config = config;
@@ -25931,7 +26229,25 @@ var KubernetesConnector = class {
25931
26229
  fetchDeployments(transport, namespace, opts),
25932
26230
  fetchPods(transport, namespace, opts)
25933
26231
  ]);
25934
- return mapWorkloadsToSignals(deployments, pods, this.config);
26232
+ const logs = await this.fetchFaultedPodLogs(transport, namespace, deployments, pods, opts);
26233
+ return mapWorkloadsToSignals(deployments, pods, this.config, logs);
26234
+ }
26235
+ async fetchFaultedPodLogs(transport, namespace, deployments, pods, opts) {
26236
+ const logs = /* @__PURE__ */ new Map();
26237
+ const targets = faultedPods(deployments, pods, this.config);
26238
+ await Promise.all(
26239
+ targets.map(async (t) => {
26240
+ const text = await fetchPodLog(transport, namespace, t.podName, {
26241
+ ...t.container ? { container: t.container } : {},
26242
+ tailLines: PROCESS_LOG_TAIL_LINES,
26243
+ limitBytes: PROCESS_LOG_LIMIT_BYTES,
26244
+ previous: true,
26245
+ ...opts
26246
+ });
26247
+ if (typeof text === "string" && text.length > 0) logs.set(podLogKey(t.podName, t.container), text);
26248
+ })
26249
+ );
26250
+ return logs;
25935
26251
  }
25936
26252
  };
25937
26253
  function createKubernetesConnector(graph, config, fetchImpl) {
@@ -27012,7 +27328,7 @@ var import_semver2 = __toESM(require("semver"), 1);
27012
27328
  // src/installers/templates.ts
27013
27329
  init_cjs_shims();
27014
27330
  var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
27015
- var OTEL_INIT_STAMP = "// neat-template-version: 8 \u2014 CommonJS TypeScript uses a synchronous bootstrap (#910); OTel init degrades to no-OBSERVED when dependencies are absent (#820); daemon.json endpoint resolution (ADR-096) + layered file-first capture (ADR-090).";
27331
+ var OTEL_INIT_STAMP = "// neat-template-version: 9 \u2014 CommonJS TypeScript uses a synchronous bootstrap (#910); OTel init degrades to no-OBSERVED when dependencies are absent (#820); daemon.json endpoint resolution (ADR-096) + layered file-first capture (ADR-090); spans flushed on exit (#1353).";
27016
27332
  var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer%20' + encodeURIComponent(process.env.NEAT_OTEL_TOKEN)";
27017
27333
  var OTEL_OTLP_PROTOCOL_JS = "process.env.OTEL_EXPORTER_OTLP_PROTOCOL ||= 'http/json'";
27018
27334
  var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
@@ -27316,6 +27632,31 @@ function __neatInstallFacades() {
27316
27632
  }
27317
27633
  var CALLSITE_PROCESSOR_JS = neatCaptureSource(false);
27318
27634
  var CALLSITE_PROCESSOR_TS = neatCaptureSource(true);
27635
+ function neatFlushOnExitSource(ts) {
27636
+ const anyT = ts ? ": any" : "";
27637
+ return `;(function () {
27638
+ let __neatFlushing${anyT}
27639
+ const __neatFlush = () => {
27640
+ if (!__neatFlushing) {
27641
+ __neatFlushing = new Promise((resolve) => {
27642
+ const t${anyT} = setTimeout(() => resolve(true), 2000)
27643
+ if (t && typeof t.unref === 'function') t.unref()
27644
+ Promise.resolve().then(() => sdk.shutdown()).catch(() => {}).then(() => { clearTimeout(t); resolve(false) })
27645
+ })
27646
+ }
27647
+ return __neatFlushing
27648
+ }
27649
+ // Only a flush that timed out forces the exit; otherwise an app's own
27650
+ // beforeExit cleanup still runs.
27651
+ process.once('beforeExit', (code${anyT}) => { __neatFlush().then((timedOut${anyT}) => { if (timedOut) process.exit(process.exitCode ?? code) }) })
27652
+ for (const sig of ['SIGTERM', 'SIGINT']) {
27653
+ process.once(sig${ts ? " as any" : ""}, () => {
27654
+ const alone = process.listenerCount(sig${ts ? " as any" : ""}) === 0
27655
+ __neatFlush().then(() => { if (alone) process.kill(process.pid, sig) })
27656
+ })
27657
+ }
27658
+ })()`;
27659
+ }
27319
27660
  function neatWireCaptureSource(ts) {
27320
27661
  const providerT = ts ? ": any" : "";
27321
27662
  return `try {
@@ -27368,6 +27709,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
27368
27709
  __INSTRUMENTATION_BLOCK__
27369
27710
  const sdk = new NodeSDK({ instrumentations })
27370
27711
  sdk.start()
27712
+ ${neatFlushOnExitSource(false)}
27371
27713
  ${neatWireCaptureSource(false)}
27372
27714
  } catch (__neatOtelErr) {
27373
27715
  const __neatMsg = String((__neatOtelErr && __neatOtelErr.message) || __neatOtelErr)
@@ -27406,6 +27748,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
27406
27748
  __INSTRUMENTATION_BLOCK__
27407
27749
  const sdk = new NodeSDK({ instrumentations })
27408
27750
  sdk.start()
27751
+ ${neatFlushOnExitSource(false)}
27409
27752
  ${neatWireCaptureSource(false)}
27410
27753
  } catch (__neatOtelErr) {
27411
27754
  const __neatMsg = String((__neatOtelErr && __neatOtelErr.message) || __neatOtelErr)
@@ -27442,6 +27785,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
27442
27785
  __INSTRUMENTATION_BLOCK__
27443
27786
  const sdk = new NodeSDK({ instrumentations })
27444
27787
  sdk.start()
27788
+ ${neatFlushOnExitSource(true)}
27445
27789
  ${neatWireCaptureSource(true)}
27446
27790
  } catch (__neatOtelErr) {
27447
27791
  const __neatMsg = String((__neatOtelErr && __neatOtelErr.message) || __neatOtelErr)
@@ -27579,6 +27923,7 @@ const sdk = new NodeSDK({
27579
27923
  instrumentations: [getNodeAutoInstrumentations()],
27580
27924
  })
27581
27925
  sdk.start()
27926
+ ${neatFlushOnExitSource(true)}
27582
27927
  `;
27583
27928
  var FRAMEWORK_OTEL_INIT_JS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
27584
27929
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
@@ -27594,6 +27939,7 @@ const sdk = new NodeSDK({
27594
27939
  instrumentations: [getNodeAutoInstrumentations()],
27595
27940
  })
27596
27941
  sdk.start()
27942
+ ${neatFlushOnExitSource(false)}
27597
27943
  `;
27598
27944
  function renderFrameworkOtelInit(template, serviceName, projectName) {
27599
27945
  return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName);
@@ -34587,7 +34933,8 @@ function formatIncidentLine(card) {
34587
34933
  const cls = rc.classification ? `${rc.classification} ` : "";
34588
34934
  tag = ` \xB7 ${cls}${rc.confidence.toFixed(2)}${provs ? ` [${provs}]` : ""}`;
34589
34935
  }
34590
- return `\u2716 incident [${card.incidentKind}] ${card.headline}${tag}`;
34936
+ const grade = card.grade ? ` \xB7 grade ${card.grade.G.toFixed(2)} ${card.grade.band}` : "";
34937
+ return `\u2716 incident [${card.incidentKind}] ${card.headline}${tag}${grade}`;
34591
34938
  }
34592
34939
  function incidentJson(card) {
34593
34940
  return JSON.stringify(card);