@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/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
|
-
...
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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);
|