@neat.is/core 0.9.20-dev.20260922 → 0.9.21-dev.20260923

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
@@ -6742,9 +6742,30 @@ function withRecoveredCodeAttrs(attrs, locus) {
6742
6742
  if (locus.codeLineno !== void 0) attrs[CODE_LINENO_ATTR] = locus.codeLineno;
6743
6743
  return attrs;
6744
6744
  }
6745
+ var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
6746
+ "authorization",
6747
+ "proxy-authorization",
6748
+ "cookie",
6749
+ "set-cookie",
6750
+ "x-api-key",
6751
+ "api-key",
6752
+ "x-auth-token",
6753
+ "x-amz-security-token",
6754
+ "x-csrf-token"
6755
+ ]);
6756
+ function attrHeaderName(key) {
6757
+ const lower = key.toLowerCase();
6758
+ const marker = ".header.";
6759
+ const at = lower.lastIndexOf(marker);
6760
+ return at >= 0 ? lower.slice(at + marker.length) : lower;
6761
+ }
6762
+ function isSensitiveAttrKey(key) {
6763
+ return SENSITIVE_HEADER_NAMES.has(attrHeaderName(key));
6764
+ }
6745
6765
  function sanitizeAttributes(attrs) {
6746
6766
  const out = {};
6747
6767
  for (const [k, v] of Object.entries(attrs)) {
6768
+ if (isSensitiveAttrKey(k)) continue;
6748
6769
  if (typeof v === "bigint") out[k] = v.toString();
6749
6770
  else out[k] = v;
6750
6771
  }
@@ -7335,6 +7356,14 @@ async function readErrorFileTail(errorsPath, maxBytes) {
7335
7356
  await handle.close();
7336
7357
  }
7337
7358
  }
7359
+ function redactPersistedAttributes(ev) {
7360
+ if (!ev.attributes) return ev;
7361
+ const scrubbed = sanitizeAttributes(ev.attributes);
7362
+ if (Object.keys(scrubbed).length === Object.keys(ev.attributes).length) return ev;
7363
+ if (Object.keys(scrubbed).length > 0) return { ...ev, attributes: scrubbed };
7364
+ const { attributes: _dropped, ...rest } = ev;
7365
+ return rest;
7366
+ }
7338
7367
  async function readErrorEvents(errorsPath, opts) {
7339
7368
  let raw;
7340
7369
  try {
@@ -7343,7 +7372,7 @@ async function readErrorEvents(errorsPath, opts) {
7343
7372
  if (err.code === "ENOENT") return [];
7344
7373
  throw err;
7345
7374
  }
7346
- const events = raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
7375
+ const events = raw.split("\n").filter((line) => line.length > 0).map((line) => redactPersistedAttributes(JSON.parse(line)));
7347
7376
  const deduped = dedupeIncidents(events);
7348
7377
  const cap = opts?.limit !== void 0 && opts.limit > 0 ? Math.min(opts.limit, INCIDENT_READ_MAX_EVENTS) : INCIDENT_READ_MAX_EVENTS;
7349
7378
  return deduped.length > cap ? deduped.slice(deduped.length - cap) : deduped;
@@ -17323,6 +17352,77 @@ function formatDivergence(d) {
17323
17352
  const conf = d.confidence.toFixed(2);
17324
17353
  return ` [${conf}] ${d.type} ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
17325
17354
  }
17355
+ var JS_TS_LANGUAGES = /* @__PURE__ */ new Set(["javascript", "typescript"]);
17356
+ var COVERAGE_COLUMNS = [
17357
+ { key: "symbols", label: "symbols" },
17358
+ { key: "routes", label: "routes" },
17359
+ { key: "data", label: "data" },
17360
+ { key: "crossServiceCalls", label: "cross-service calls" }
17361
+ ];
17362
+ function isTableInfra(kind) {
17363
+ return kind !== void 0 && /(table|collection)$/.test(kind);
17364
+ }
17365
+ function computeLanguageCoverage(nodes, edges) {
17366
+ const serviceLang = /* @__PURE__ */ new Map();
17367
+ const fileService = /* @__PURE__ */ new Map();
17368
+ const nodeById = /* @__PURE__ */ new Map();
17369
+ for (const n of nodes) {
17370
+ nodeById.set(n.id, n);
17371
+ if (n.type === import_types62.NodeType.ServiceNode) serviceLang.set(n.name, n.language);
17372
+ else if (n.type === import_types62.NodeType.FileNode) fileService.set(n.id, n.service);
17373
+ }
17374
+ const cov = /* @__PURE__ */ new Map();
17375
+ const ensure = (lang) => {
17376
+ let c = cov.get(lang);
17377
+ if (!c) {
17378
+ c = { symbols: false, routes: false, data: false, crossServiceCalls: false };
17379
+ cov.set(lang, c);
17380
+ }
17381
+ return c;
17382
+ };
17383
+ for (const lang of serviceLang.values()) ensure(lang);
17384
+ for (const n of nodes) {
17385
+ if (n.type === import_types62.NodeType.SymbolNode) {
17386
+ if (n.discoveredVia === "otel") continue;
17387
+ const lang = serviceLang.get(n.service);
17388
+ if (lang) ensure(lang).symbols = true;
17389
+ } else if (n.type === import_types62.NodeType.RouteNode) {
17390
+ if (n.discoveredVia === "otel") continue;
17391
+ const lang = serviceLang.get(n.service);
17392
+ if (lang) ensure(lang).routes = true;
17393
+ }
17394
+ }
17395
+ for (const e of edges) {
17396
+ if (e.type !== import_types62.EdgeType.CALLS || e.provenance !== import_types62.Provenance.EXTRACTED) continue;
17397
+ const svc = fileService.get(e.source);
17398
+ const lang = svc ? serviceLang.get(svc) : void 0;
17399
+ if (!lang) continue;
17400
+ const target = nodeById.get(e.target);
17401
+ if (!target) continue;
17402
+ if (target.type === import_types62.NodeType.RouteNode) ensure(lang).crossServiceCalls = true;
17403
+ else if (target.type === import_types62.NodeType.InfraNode && isTableInfra(target.kind)) ensure(lang).data = true;
17404
+ }
17405
+ return cov;
17406
+ }
17407
+ function renderLanguageCoverage(nodes, edges) {
17408
+ const cov = computeLanguageCoverage(nodes, edges);
17409
+ const langs = [...cov.keys()];
17410
+ if (!langs.some((l) => !JS_TS_LANGUAGES.has(l))) return [];
17411
+ const glyph = (on) => on ? ok(sym.ok) : dim("\u2014");
17412
+ const header = ["", ...COVERAGE_COLUMNS.map((c) => c.label)];
17413
+ const rows = [header];
17414
+ for (const lang of [...langs].sort()) {
17415
+ const c = cov.get(lang);
17416
+ rows.push([lang, ...COVERAGE_COLUMNS.map((col) => glyph(c[col.key]))]);
17417
+ }
17418
+ const lines = ["static coverage by language (this scan):"];
17419
+ for (const row of table(rows)) lines.push(` ${row}`);
17420
+ lines.push(
17421
+ ` ${sym.arrow} cross-service call recognition is JS/TS-only today (#1158); non-JS/TS callers rely on OBSERVED for cross-service topology.`
17422
+ );
17423
+ lines.push("");
17424
+ return lines;
17425
+ }
17326
17426
  function renderValueForwardSummary(input) {
17327
17427
  const { graph, divergences, verbose } = input;
17328
17428
  const nodes = [];
@@ -17356,6 +17456,7 @@ function renderValueForwardSummary(input) {
17356
17456
  lines.push(" \u2192 run your services with the generated otel-init to populate OBSERVED edges.");
17357
17457
  lines.push("");
17358
17458
  }
17459
+ for (const line of renderLanguageCoverage(nodes, edges)) lines.push(line);
17359
17460
  lines.push(renderOtelEnvBlock());
17360
17461
  lines.push("");
17361
17462
  if (verbose) {
@@ -19630,6 +19731,10 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
19630
19731
  // src/connectors/registry.ts
19631
19732
  init_cjs_shims();
19632
19733
 
19734
+ // src/connectors/gcp-auth.ts
19735
+ init_cjs_shims();
19736
+ var import_node_crypto3 = require("crypto");
19737
+
19633
19738
  // src/connectors/junction.ts
19634
19739
  init_cjs_shims();
19635
19740
  var buckets = /* @__PURE__ */ new Map();
@@ -19891,6 +19996,108 @@ async function dbJunction(run, policy) {
19891
19996
  }
19892
19997
  }
19893
19998
 
19999
+ // src/connectors/gcp-auth.ts
20000
+ var DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token";
20001
+ var JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
20002
+ var ASSERTION_LIFETIME_S = 3600;
20003
+ var DEFAULT_REFRESH_SKEW_MS = 6e4;
20004
+ var GCP_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
20005
+ function parseServiceAccountKey(json) {
20006
+ let raw;
20007
+ try {
20008
+ raw = JSON.parse(json);
20009
+ } catch {
20010
+ throw new Error("gcp service-account key: not valid JSON");
20011
+ }
20012
+ if (!raw || typeof raw !== "object") {
20013
+ throw new Error("gcp service-account key: expected a JSON object");
20014
+ }
20015
+ const obj = raw;
20016
+ const clientEmail = obj["client_email"];
20017
+ const privateKey = obj["private_key"];
20018
+ const projectId = obj["project_id"];
20019
+ if (typeof clientEmail !== "string" || clientEmail.length === 0) {
20020
+ throw new Error("gcp service-account key: missing client_email");
20021
+ }
20022
+ if (typeof privateKey !== "string" || privateKey.length === 0) {
20023
+ throw new Error("gcp service-account key: missing private_key");
20024
+ }
20025
+ if (typeof projectId !== "string" || projectId.length === 0) {
20026
+ throw new Error("gcp service-account key: missing project_id");
20027
+ }
20028
+ const key = { client_email: clientEmail, private_key: privateKey, project_id: projectId };
20029
+ if (typeof obj["token_uri"] === "string" && obj["token_uri"].length > 0) {
20030
+ key.token_uri = obj["token_uri"];
20031
+ }
20032
+ return key;
20033
+ }
20034
+ function base64url(input) {
20035
+ return Buffer.from(input).toString("base64url");
20036
+ }
20037
+ function buildServiceAccountAssertion(key, scope, tokenUri, nowMs) {
20038
+ const iat = Math.floor(nowMs / 1e3);
20039
+ const header = { alg: "RS256", typ: "JWT" };
20040
+ const claims = {
20041
+ iss: key.client_email,
20042
+ scope,
20043
+ aud: tokenUri,
20044
+ iat,
20045
+ exp: iat + ASSERTION_LIFETIME_S
20046
+ };
20047
+ const signingInput = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(claims))}`;
20048
+ const signature = (0, import_node_crypto3.createSign)("RSA-SHA256").update(signingInput).end().sign(key.private_key);
20049
+ return `${signingInput}.${base64url(signature)}`;
20050
+ }
20051
+ async function mintGcpAccessToken(key, scope, opts = {}) {
20052
+ const now = opts.now ?? Date.now;
20053
+ const tokenUri = opts.tokenUri ?? key.token_uri ?? DEFAULT_TOKEN_URI;
20054
+ const mintedAtMs = now();
20055
+ const assertion = buildServiceAccountAssertion(key, scope, tokenUri, mintedAtMs);
20056
+ const body = new URLSearchParams({ grant_type: JWT_BEARER_GRANT, assertion }).toString();
20057
+ const res = await junctionFetch(
20058
+ tokenUri,
20059
+ {
20060
+ method: "POST",
20061
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
20062
+ body
20063
+ },
20064
+ // accountKey: the GCP project id — an identifier, safe to log, the same
20065
+ // per-(provider, accountKey) rate-limit bucket key the GCP connectors use.
20066
+ { provider: "gcp-auth", accountKey: key.project_id, ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {} }
20067
+ );
20068
+ if (!res.ok) {
20069
+ throw new Error(`gcp token mint failed: ${res.status} ${res.statusText}`);
20070
+ }
20071
+ const json = await res.json();
20072
+ const accessToken = json.access_token;
20073
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
20074
+ throw new Error("gcp token mint returned no access_token");
20075
+ }
20076
+ const expiresInS = typeof json.expires_in === "number" && json.expires_in > 0 ? json.expires_in : ASSERTION_LIFETIME_S;
20077
+ return { accessToken, expiresAtMs: mintedAtMs + expiresInS * 1e3 };
20078
+ }
20079
+ function createGcpTokenSource(key, scope, opts = {}) {
20080
+ const now = opts.now ?? Date.now;
20081
+ const skewMs = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;
20082
+ let cached = null;
20083
+ let inFlight = null;
20084
+ const fresh = () => cached !== null && now() < cached.expiresAtMs - skewMs;
20085
+ return async () => {
20086
+ if (!fresh()) {
20087
+ if (!inFlight) {
20088
+ inFlight = mintGcpAccessToken(key, scope, opts).then((minted) => {
20089
+ cached = minted;
20090
+ return minted;
20091
+ }).finally(() => {
20092
+ inFlight = null;
20093
+ });
20094
+ }
20095
+ await inFlight;
20096
+ }
20097
+ return { projectId: key.project_id, accessToken: cached.accessToken };
20098
+ };
20099
+ }
20100
+
19894
20101
  // src/connectors/supabase/index.ts
19895
20102
  init_cjs_shims();
19896
20103
 
@@ -20757,7 +20964,7 @@ var import_types75 = require("@neat.is/types");
20757
20964
 
20758
20965
  // src/connectors/cloudflare/client.ts
20759
20966
  init_cjs_shims();
20760
- var import_node_crypto3 = require("crypto");
20967
+ var import_node_crypto4 = require("crypto");
20761
20968
  var DEFAULT_BASE_URL = "https://api.cloudflare.com/client/v4";
20762
20969
  var DEFAULT_EVENT_LIMIT = 1e3;
20763
20970
  async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
@@ -20770,7 +20977,7 @@ async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
20770
20977
  const body = {
20771
20978
  // Cloudflare's schema requires an identifier per query even for an
20772
20979
  // ad-hoc, unsaved one — a fresh id per tick, never reused.
20773
- queryId: `neat-connector-${(0, import_node_crypto3.randomUUID)()}`,
20980
+ queryId: `neat-connector-${(0, import_node_crypto4.randomUUID)()}`,
20774
20981
  timeframe: { from: window.fromMs, to: window.toMs },
20775
20982
  view: "events",
20776
20983
  limit: config.eventLimit ?? DEFAULT_EVENT_LIMIT,
@@ -22778,7 +22985,8 @@ function getProviderFieldSchema(provider) {
22778
22985
  function knownProviderNames() {
22779
22986
  return [...Object.keys(PROVIDER_DISPATCH), ...Object.keys(PUSH_PROVIDER_DISPATCH)].sort();
22780
22987
  }
22781
- function resolveEntryCredentials(dispatch, entry2, env) {
22988
+ var GCP_SERVICE_ACCOUNT_PROVIDERS = /* @__PURE__ */ new Set(["cloud-run", "gcp-lb", "firebase"]);
22989
+ function resolveEntryCredentials(dispatch, entry2, env, fetchImpl) {
22782
22990
  let credentials;
22783
22991
  try {
22784
22992
  const resolved = resolveCredential(entry2.credential, env);
@@ -22787,6 +22995,15 @@ function resolveEntryCredentials(dispatch, entry2, env) {
22787
22995
  if (err instanceof EnvRefUnsetError) return { ok: false, kind: "unset-env", reason: err.message };
22788
22996
  return { ok: false, kind: "error", reason: err.message };
22789
22997
  }
22998
+ if (GCP_SERVICE_ACCOUNT_PROVIDERS.has(entry2.provider) && typeof credentials.serviceAccountKey === "string" && credentials.serviceAccountKey.length > 0) {
22999
+ try {
23000
+ const key = parseServiceAccountKey(credentials.serviceAccountKey);
23001
+ const source = createGcpTokenSource(key, GCP_LOGGING_READ_SCOPE, fetchImpl ? { fetchImpl } : {});
23002
+ return { ok: true, credentials: { projectId: key.project_id }, refreshCredentials: source };
23003
+ } catch (err) {
23004
+ return { ok: false, kind: "error", reason: err.message };
23005
+ }
23006
+ }
22790
23007
  const missingCreds = dispatch.requiredCredentialFields.filter((k) => !credentials[k]);
22791
23008
  if (missingCreds.length > 0) {
22792
23009
  return {
@@ -22836,7 +23053,10 @@ function buildRegistration(entry2, graph, env = process.env) {
22836
23053
  connector: built.connector,
22837
23054
  credentials,
22838
23055
  resolveTarget: built.resolveTarget,
22839
- ...intervalMs !== void 0 ? { intervalMs } : {}
23056
+ ...intervalMs !== void 0 ? { intervalMs } : {},
23057
+ // A refreshable credential (ADR-230) resolves to a per-tick token source; carry it so the daemon wires
23058
+ // it to the poll loop's `refreshCredentials`. Absent for a static credential.
23059
+ ...creds.refreshCredentials ? { refreshCredentials: creds.refreshCredentials } : {}
22840
23060
  }
22841
23061
  };
22842
23062
  }
@@ -22845,7 +23065,7 @@ async function validateConnectorEntry(entry2, env = process.env, fetchImpl) {
22845
23065
  if (!dispatch) {
22846
23066
  return { status: "unknown-provider", reason: `unknown provider "${entry2.provider}"` };
22847
23067
  }
22848
- const creds = resolveEntryCredentials(dispatch, entry2, env);
23068
+ const creds = resolveEntryCredentials(dispatch, entry2, env, fetchImpl);
22849
23069
  if (!creds.ok) {
22850
23070
  if (creds.kind === "unset-env") return { status: "unset-env", reason: creds.reason };
22851
23071
  return { status: "missing-field", reason: creds.reason };
@@ -22858,8 +23078,16 @@ async function validateConnectorEntry(entry2, env = process.env, fetchImpl) {
22858
23078
  reason: `options missing required field(s): ${missingOpts.join(", ")}`
22859
23079
  };
22860
23080
  }
23081
+ let credentials = creds.credentials;
23082
+ if (creds.refreshCredentials) {
23083
+ try {
23084
+ credentials = { ...credentials, ...await creds.refreshCredentials() };
23085
+ } catch (err) {
23086
+ return { status: "rejected", reason: err.message };
23087
+ }
23088
+ }
22861
23089
  const result = await dispatch.validate({
22862
- credentials: creds.credentials,
23090
+ credentials,
22863
23091
  options,
22864
23092
  ...fetchImpl ? { fetchImpl } : {}
22865
23093
  });
@@ -22905,7 +23133,13 @@ async function startConnectorPolling(input) {
22905
23133
  },
22906
23134
  input.graph,
22907
23135
  registration.resolveTarget,
22908
- { intervalMs: registration.intervalMs, connectorId: registration.id }
23136
+ {
23137
+ intervalMs: registration.intervalMs,
23138
+ connectorId: registration.id,
23139
+ // A refreshable credential (ADR-230) mints a fresh token each tick; the static path leaves this unset
23140
+ // and the loop reuses ctx.credentials, exactly as before.
23141
+ ...registration.refreshCredentials ? { refreshCredentials: registration.refreshCredentials } : {}
23142
+ }
22909
23143
  )
22910
23144
  );
22911
23145
  return () => {
@@ -24392,6 +24626,9 @@ init_otel();
24392
24626
  // src/connectors/hosted.ts
24393
24627
  init_cjs_shims();
24394
24628
 
24629
+ // src/connectors/railway/target-ref.ts
24630
+ init_cjs_shims();
24631
+
24395
24632
  // src/connectors/hosted-repos.ts
24396
24633
  init_cjs_shims();
24397
24634
  var import_node_os6 = __toESM(require("os"), 1);
@@ -24499,7 +24736,7 @@ init_otel_grpc();
24499
24736
  init_cjs_shims();
24500
24737
  var import_node_fs44 = require("fs");
24501
24738
  var import_node_path81 = __toESM(require("path"), 1);
24502
- var import_node_crypto4 = require("crypto");
24739
+ var import_node_crypto5 = require("crypto");
24503
24740
  var DEFAULT_LIMIT = 10;
24504
24741
  var NOMIC_DIM = 768;
24505
24742
  var MINI_LM_DIM = 384;
@@ -24563,7 +24800,7 @@ function embedText(node) {
24563
24800
  return parts.join(" ");
24564
24801
  }
24565
24802
  function attrsHash(node) {
24566
- return (0, import_node_crypto4.createHash)("sha1").update(embedText(node)).digest("hex").slice(0, 16);
24803
+ return (0, import_node_crypto5.createHash)("sha1").update(embedText(node)).digest("hex").slice(0, 16);
24567
24804
  }
24568
24805
  function cosine(a, b) {
24569
24806
  if (a.length !== b.length) return 0;
@@ -25290,9 +25527,9 @@ init_cjs_shims();
25290
25527
  var import_node_fs46 = require("fs");
25291
25528
  var import_node_path83 = __toESM(require("path"), 1);
25292
25529
  var import_node_child_process2 = require("child_process");
25293
- var import_node_crypto5 = require("crypto");
25530
+ var import_node_crypto6 = require("crypto");
25294
25531
  function generateToken() {
25295
- return (0, import_node_crypto5.randomBytes)(32).toString("base64url");
25532
+ return (0, import_node_crypto6.randomBytes)(32).toString("base64url");
25296
25533
  }
25297
25534
  async function probeBinary(binary, arg) {
25298
25535
  return new Promise((resolve) => {
@@ -29448,7 +29685,7 @@ var import_node_child_process5 = require("child_process");
29448
29685
  init_cjs_shims();
29449
29686
  var import_node_http2 = require("http");
29450
29687
  var import_node_child_process4 = require("child_process");
29451
- var import_node_crypto6 = require("crypto");
29688
+ var import_node_crypto7 = require("crypto");
29452
29689
 
29453
29690
  // src/profiles.ts
29454
29691
  init_cjs_shims();
@@ -29755,7 +29992,7 @@ async function loopbackReceiveToken(webUrl, deps, opts = {}) {
29755
29992
  const out = deps.out ?? (() => {
29756
29993
  });
29757
29994
  const openFn = deps.openBrowser ?? defaultOpenBrowser;
29758
- const state = (0, import_node_crypto6.randomBytes)(16).toString("hex");
29995
+ const state = (0, import_node_crypto7.randomBytes)(16).toString("hex");
29759
29996
  return new Promise((resolve) => {
29760
29997
  let settled = false;
29761
29998
  let timer;
@@ -32092,6 +32329,15 @@ function formatDivergenceLine2(d) {
32092
32329
  const at = d.location ? ` at ${d.location}` : "";
32093
32330
  return `\u26A0 divergence [observed-failing] ${d.source}${at} declares a call observed failing (${d.failureKind})`;
32094
32331
  }
32332
+ case "deploy-mismatch":
32333
+ if (d.kind === "image") {
32334
+ return `\u26A0 divergence [deploy-mismatch] ${d.source} declares image ${d.declaredImage ?? "unknown"}, running ${d.observedImage ?? "unknown"}`;
32335
+ }
32336
+ return `\u26A0 divergence [deploy-mismatch] ${d.source} declares ${d.declaredReplicas ?? "unknown"} replicas, ${d.observedReplicas ?? "unknown"} ready`;
32337
+ default: {
32338
+ const other = d;
32339
+ return `\u26A0 divergence [${other.type}] ${other.source} \u2192 ${other.target}`;
32340
+ }
32095
32341
  }
32096
32342
  }
32097
32343
  function formatStaleLine(edgeId) {