@neat.is/core 0.4.15 → 0.4.17

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/server.cjs CHANGED
@@ -56,9 +56,9 @@ function mountBearerAuth(app, opts) {
56
56
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
57
57
  const publicRead = opts.publicRead === true;
58
58
  app.addHook("preHandler", (req, reply, done) => {
59
- const path42 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
59
+ const path43 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
60
60
  for (const suffix of suffixes) {
61
- if (path42 === suffix || path42.endsWith(suffix)) {
61
+ if (path43 === suffix || path43.endsWith(suffix)) {
62
62
  done();
63
63
  return;
64
64
  }
@@ -186,8 +186,8 @@ function reshapeGrpcRequest(req) {
186
186
  };
187
187
  }
188
188
  function resolveProtoRoot() {
189
- const here = import_node_path38.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
190
- return import_node_path38.default.resolve(here, "..", "proto");
189
+ const here = import_node_path39.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
190
+ return import_node_path39.default.resolve(here, "..", "proto");
191
191
  }
192
192
  function loadTraceService() {
193
193
  const protoRoot = resolveProtoRoot();
@@ -255,13 +255,13 @@ async function startOtelGrpcReceiver(opts) {
255
255
  })
256
256
  };
257
257
  }
258
- var import_node_url, import_node_path38, import_node_crypto2, grpc, protoLoader;
258
+ var import_node_url, import_node_path39, import_node_crypto2, grpc, protoLoader;
259
259
  var init_otel_grpc = __esm({
260
260
  "src/otel-grpc.ts"() {
261
261
  "use strict";
262
262
  init_cjs_shims();
263
263
  import_node_url = require("url");
264
- import_node_path38 = __toESM(require("path"), 1);
264
+ import_node_path39 = __toESM(require("path"), 1);
265
265
  import_node_crypto2 = require("crypto");
266
266
  grpc = __toESM(require("@grpc/grpc-js"), 1);
267
267
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -371,10 +371,10 @@ function parseOtlpRequest(body) {
371
371
  return out;
372
372
  }
373
373
  function loadProtoRoot() {
374
- const here = import_node_path39.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
375
- const protoRoot = import_node_path39.default.resolve(here, "..", "proto");
374
+ const here = import_node_path40.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
375
+ const protoRoot = import_node_path40.default.resolve(here, "..", "proto");
376
376
  const root = new import_protobufjs.default.Root();
377
- root.resolvePath = (_origin, target) => import_node_path39.default.resolve(protoRoot, target);
377
+ root.resolvePath = (_origin, target) => import_node_path40.default.resolve(protoRoot, target);
378
378
  root.loadSync(
379
379
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
380
380
  { keepCase: true }
@@ -570,12 +570,12 @@ async function buildOtelReceiver(opts) {
570
570
  };
571
571
  return decorated;
572
572
  }
573
- var import_node_path39, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
573
+ var import_node_path40, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
574
574
  var init_otel = __esm({
575
575
  "src/otel.ts"() {
576
576
  "use strict";
577
577
  init_cjs_shims();
578
- import_node_path39 = __toESM(require("path"), 1);
578
+ import_node_path40 = __toESM(require("path"), 1);
579
579
  import_node_url2 = require("url");
580
580
  import_fastify2 = __toESM(require("fastify"), 1);
581
581
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -591,7 +591,7 @@ var init_otel = __esm({
591
591
 
592
592
  // src/server.ts
593
593
  init_cjs_shims();
594
- var import_node_path41 = __toESM(require("path"), 1);
594
+ var import_node_path42 = __toESM(require("path"), 1);
595
595
 
596
596
  // src/graph.ts
597
597
  init_cjs_shims();
@@ -615,7 +615,7 @@ function getGraph(project = DEFAULT_PROJECT) {
615
615
  init_cjs_shims();
616
616
  var import_fastify = __toESM(require("fastify"), 1);
617
617
  var import_cors = __toESM(require("@fastify/cors"), 1);
618
- var import_types24 = require("@neat.is/types");
618
+ var import_types25 = require("@neat.is/types");
619
619
 
620
620
  // src/extend/index.ts
621
621
  init_cjs_shims();
@@ -1358,19 +1358,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1358
1358
  function longestIncomingWalk(graph, start, maxDepth) {
1359
1359
  let best = { path: [start], edges: [] };
1360
1360
  const visited = /* @__PURE__ */ new Set([start]);
1361
- function step(node, path42, edges) {
1362
- if (path42.length > best.path.length) {
1363
- best = { path: [...path42], edges: [...edges] };
1361
+ function step(node, path43, edges) {
1362
+ if (path43.length > best.path.length) {
1363
+ best = { path: [...path43], edges: [...edges] };
1364
1364
  }
1365
- if (path42.length - 1 >= maxDepth) return;
1365
+ if (path43.length - 1 >= maxDepth) return;
1366
1366
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1367
1367
  for (const [srcId, edge] of incoming) {
1368
1368
  if (visited.has(srcId)) continue;
1369
1369
  visited.add(srcId);
1370
- path42.push(srcId);
1370
+ path43.push(srcId);
1371
1371
  edges.push(edge);
1372
- step(srcId, path42, edges);
1373
- path42.pop();
1372
+ step(srcId, path43, edges);
1373
+ path43.pop();
1374
1374
  edges.pop();
1375
1375
  visited.delete(srcId);
1376
1376
  }
@@ -2174,6 +2174,41 @@ function thresholdForEdgeType(edgeType, overrides) {
2174
2174
  const map = overrides ?? loadStaleThresholdsFromEnv();
2175
2175
  return map[edgeType] ?? FALLBACK_STALE_THRESHOLD_MS;
2176
2176
  }
2177
+ var DEFAULT_INCIDENT_THRESHOLDS = {
2178
+ threshold: 5,
2179
+ windowMs: 6e4
2180
+ };
2181
+ function loadIncidentThresholdsFromEnv() {
2182
+ const raw = process.env.NEAT_INCIDENT_THRESHOLDS;
2183
+ if (!raw) return DEFAULT_INCIDENT_THRESHOLDS;
2184
+ try {
2185
+ const overrides = JSON.parse(raw);
2186
+ const merged = { ...DEFAULT_INCIDENT_THRESHOLDS };
2187
+ if (typeof overrides.threshold === "number" && Number.isFinite(overrides.threshold) && overrides.threshold >= 1) {
2188
+ merged.threshold = Math.floor(overrides.threshold);
2189
+ }
2190
+ if (typeof overrides.windowMs === "number" && Number.isFinite(overrides.windowMs) && overrides.windowMs >= 0) {
2191
+ merged.windowMs = overrides.windowMs;
2192
+ }
2193
+ return merged;
2194
+ } catch (err) {
2195
+ console.warn(
2196
+ `[neat] NEAT_INCIDENT_THRESHOLDS could not be parsed (${err.message}); using defaults`
2197
+ );
2198
+ return DEFAULT_INCIDENT_THRESHOLDS;
2199
+ }
2200
+ }
2201
+ function httpResponseStatus(span) {
2202
+ for (const key of ["http.response.status_code", "http.status_code"]) {
2203
+ const v = span.attributes[key];
2204
+ if (typeof v === "number" && Number.isFinite(v)) return v;
2205
+ if (typeof v === "string") {
2206
+ const n = Number(v);
2207
+ if (Number.isFinite(n)) return n;
2208
+ }
2209
+ }
2210
+ return void 0;
2211
+ }
2177
2212
  function nowIso(ctx) {
2178
2213
  return new Date(ctx.now ? ctx.now() : Date.now()).toISOString();
2179
2214
  }
@@ -2562,6 +2597,71 @@ function sanitizeAttributes(attrs) {
2562
2597
  }
2563
2598
  return out;
2564
2599
  }
2600
+ async function recordFailingResponseIncident(ctx, span, affectedNode, timestamp, statusCode, count, firstTimestamp) {
2601
+ const attrs = sanitizeAttributes(span.attributes);
2602
+ const first = firstTimestamp ?? timestamp;
2603
+ const peer = pickAddress(span);
2604
+ const message = count > 1 ? `${count} consecutive HTTP ${statusCode} responses` + (peer ? ` to ${peer}` : "") : `HTTP ${statusCode} response` + (peer ? ` from ${peer}` : "");
2605
+ const ev = {
2606
+ id: `${span.traceId}:${span.spanId}`,
2607
+ timestamp,
2608
+ service: span.service,
2609
+ traceId: span.traceId,
2610
+ spanId: span.spanId,
2611
+ errorType: "http-failure",
2612
+ errorMessage: message,
2613
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
2614
+ affectedNode,
2615
+ httpStatusCode: statusCode,
2616
+ incidentCount: count,
2617
+ firstTimestamp: first,
2618
+ lastTimestamp: timestamp
2619
+ };
2620
+ await appendErrorEvent(ctx, ev);
2621
+ }
2622
+ async function advance4xxBurst(ctx, span, affectedNode, ts, nowMs, status2) {
2623
+ const { threshold, windowMs } = loadIncidentThresholdsFromEnv();
2624
+ if (!ctx.burstState) ctx.burstState = /* @__PURE__ */ new Map();
2625
+ const peer = pickAddress(span) ?? span.spanId;
2626
+ const key = `${span.service}->${peer}`;
2627
+ const existing = ctx.burstState.get(key);
2628
+ let state;
2629
+ if (existing && nowMs - existing.lastMs <= windowMs) {
2630
+ existing.count += 1;
2631
+ existing.lastTs = ts;
2632
+ existing.lastMs = nowMs;
2633
+ existing.codes.set(status2, (existing.codes.get(status2) ?? 0) + 1);
2634
+ state = existing;
2635
+ } else {
2636
+ state = {
2637
+ count: 1,
2638
+ firstTs: ts,
2639
+ lastTs: ts,
2640
+ lastMs: nowMs,
2641
+ codes: /* @__PURE__ */ new Map([[status2, 1]])
2642
+ };
2643
+ ctx.burstState.set(key, state);
2644
+ }
2645
+ if (state.count < threshold) return;
2646
+ let dominant = status2;
2647
+ let max = 0;
2648
+ for (const [code, n] of state.codes) {
2649
+ if (n > max) {
2650
+ max = n;
2651
+ dominant = code;
2652
+ }
2653
+ }
2654
+ await recordFailingResponseIncident(
2655
+ ctx,
2656
+ span,
2657
+ affectedNode,
2658
+ state.lastTs,
2659
+ dominant,
2660
+ state.count,
2661
+ state.firstTs
2662
+ );
2663
+ ctx.burstState.delete(key);
2664
+ }
2565
2665
  async function handleSpan(ctx, span) {
2566
2666
  const ts = span.startTimeIso ?? nowIso(ctx);
2567
2667
  const nowMs = ctx.now ? ctx.now() : Date.now();
@@ -2660,6 +2760,14 @@ async function handleSpan(ctx, span) {
2660
2760
  await appendErrorEvent(ctx, ev);
2661
2761
  }
2662
2762
  }
2763
+ if (span.statusCode !== 2) {
2764
+ const status2 = httpResponseStatus(span);
2765
+ if (status2 !== void 0 && status2 >= 500) {
2766
+ await recordFailingResponseIncident(ctx, span, sourceId, ts, status2, 1);
2767
+ } else if (status2 !== void 0 && status2 >= 400 && spanMintsObservedEdge(span.kind)) {
2768
+ await advance4xxBurst(ctx, span, sourceId, ts, nowMs, status2);
2769
+ }
2770
+ }
2663
2771
  void affectedNode;
2664
2772
  if (ctx.onPolicyTrigger) await ctx.onPolicyTrigger(ctx.graph);
2665
2773
  }
@@ -4679,7 +4787,7 @@ async function addConfigNodes(graph, services, scanPath) {
4679
4787
 
4680
4788
  // src/extract/calls/index.ts
4681
4789
  init_cjs_shims();
4682
- var import_types17 = require("@neat.is/types");
4790
+ var import_types18 = require("@neat.is/types");
4683
4791
 
4684
4792
  // src/extract/calls/http.ts
4685
4793
  init_cjs_shims();
@@ -5032,15 +5140,75 @@ function grpcEndpointsFromFile(file, serviceDir) {
5032
5140
  return out;
5033
5141
  }
5034
5142
 
5143
+ // src/extract/calls/supabase.ts
5144
+ init_cjs_shims();
5145
+ var import_node_path29 = __toESM(require("path"), 1);
5146
+ var import_types17 = require("@neat.is/types");
5147
+ var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
5148
+ var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
5149
+ var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
5150
+ function hostFromLiteral(literal) {
5151
+ if (!literal) return null;
5152
+ const m = /^https?:\/\/([^/:'"`\s]+)/.exec(literal.trim());
5153
+ if (!m) return null;
5154
+ const host = m[1];
5155
+ if (!/\.supabase\.(co|in)$/i.test(host)) return null;
5156
+ return host;
5157
+ }
5158
+ function readImports2(content) {
5159
+ return {
5160
+ hasSupabaseJs: SUPABASE_JS_IMPORT_RE.test(content),
5161
+ hasSupabaseSsr: SUPABASE_SSR_IMPORT_RE.test(content)
5162
+ };
5163
+ }
5164
+ function constructorMatchesImport(name, ctx) {
5165
+ if (name === "createClient") return ctx.hasSupabaseJs;
5166
+ return ctx.hasSupabaseSsr;
5167
+ }
5168
+ function supabaseEndpointsFromFile(file, serviceDir) {
5169
+ const ctx = readImports2(file.content);
5170
+ if (!ctx.hasSupabaseJs && !ctx.hasSupabaseSsr) return [];
5171
+ const out = [];
5172
+ const seen = /* @__PURE__ */ new Set();
5173
+ SUPABASE_CLIENT_RE.lastIndex = 0;
5174
+ let m;
5175
+ while ((m = SUPABASE_CLIENT_RE.exec(file.content)) !== null) {
5176
+ const ctor = m[1];
5177
+ if (!constructorMatchesImport(ctor, ctx)) continue;
5178
+ const host = hostFromLiteral(m[2]);
5179
+ const name = host ?? "env";
5180
+ if (seen.has(name)) continue;
5181
+ seen.add(name);
5182
+ const line = lineOf(file.content, m[0]);
5183
+ out.push({
5184
+ infraId: (0, import_types17.infraId)("supabase", name),
5185
+ name,
5186
+ kind: "supabase",
5187
+ edgeType: "CALLS",
5188
+ // `createClient(...)` from @supabase/supabase-js (or createServerClient /
5189
+ // createBrowserClient from @supabase/ssr) with the import in scope — a
5190
+ // framework-aware recognizer matched the SDK shape. Verified-call-site
5191
+ // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
5192
+ confidenceKind: "verified-call-site",
5193
+ evidence: {
5194
+ file: import_node_path29.default.relative(serviceDir, file.path),
5195
+ line,
5196
+ snippet: snippet(file.content, line)
5197
+ }
5198
+ });
5199
+ }
5200
+ return out;
5201
+ }
5202
+
5035
5203
  // src/extract/calls/index.ts
5036
5204
  function edgeTypeFromEndpoint(ep) {
5037
5205
  switch (ep.edgeType) {
5038
5206
  case "PUBLISHES_TO":
5039
- return import_types17.EdgeType.PUBLISHES_TO;
5207
+ return import_types18.EdgeType.PUBLISHES_TO;
5040
5208
  case "CONSUMES_FROM":
5041
- return import_types17.EdgeType.CONSUMES_FROM;
5209
+ return import_types18.EdgeType.CONSUMES_FROM;
5042
5210
  default:
5043
- return import_types17.EdgeType.CALLS;
5211
+ return import_types18.EdgeType.CALLS;
5044
5212
  }
5045
5213
  }
5046
5214
  function isAwsKind(kind) {
@@ -5060,6 +5228,7 @@ async function addExternalEndpointEdges(graph, services) {
5060
5228
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
5061
5229
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
5062
5230
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
5231
+ endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
5063
5232
  }
5064
5233
  if (endpoints.length === 0) continue;
5065
5234
  const seenEdges = /* @__PURE__ */ new Set();
@@ -5067,7 +5236,7 @@ async function addExternalEndpointEdges(graph, services) {
5067
5236
  if (!graph.hasNode(ep.infraId)) {
5068
5237
  const node = {
5069
5238
  id: ep.infraId,
5070
- type: import_types17.NodeType.InfraNode,
5239
+ type: import_types18.NodeType.InfraNode,
5071
5240
  name: ep.name,
5072
5241
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
5073
5242
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -5079,7 +5248,7 @@ async function addExternalEndpointEdges(graph, services) {
5079
5248
  nodesAdded++;
5080
5249
  }
5081
5250
  const edgeType = edgeTypeFromEndpoint(ep);
5082
- const confidence = (0, import_types17.confidenceForExtracted)(ep.confidenceKind);
5251
+ const confidence = (0, import_types18.confidenceForExtracted)(ep.confidenceKind);
5083
5252
  const relFile = toPosix2(ep.evidence.file);
5084
5253
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
5085
5254
  graph,
@@ -5089,7 +5258,7 @@ async function addExternalEndpointEdges(graph, services) {
5089
5258
  );
5090
5259
  nodesAdded += n;
5091
5260
  edgesAdded += e;
5092
- if (!(0, import_types17.passesExtractedFloor)(confidence)) {
5261
+ if (!(0, import_types18.passesExtractedFloor)(confidence)) {
5093
5262
  noteExtractedDropped({
5094
5263
  source: fileNodeId,
5095
5264
  target: ep.infraId,
@@ -5109,7 +5278,7 @@ async function addExternalEndpointEdges(graph, services) {
5109
5278
  source: fileNodeId,
5110
5279
  target: ep.infraId,
5111
5280
  type: edgeType,
5112
- provenance: import_types17.Provenance.EXTRACTED,
5281
+ provenance: import_types18.Provenance.EXTRACTED,
5113
5282
  confidence,
5114
5283
  evidence: ep.evidence
5115
5284
  };
@@ -5134,16 +5303,16 @@ init_cjs_shims();
5134
5303
 
5135
5304
  // src/extract/infra/docker-compose.ts
5136
5305
  init_cjs_shims();
5137
- var import_node_path29 = __toESM(require("path"), 1);
5138
- var import_types19 = require("@neat.is/types");
5306
+ var import_node_path30 = __toESM(require("path"), 1);
5307
+ var import_types20 = require("@neat.is/types");
5139
5308
 
5140
5309
  // src/extract/infra/shared.ts
5141
5310
  init_cjs_shims();
5142
- var import_types18 = require("@neat.is/types");
5311
+ var import_types19 = require("@neat.is/types");
5143
5312
  function makeInfraNode(kind, name, provider = "self", extras) {
5144
5313
  return {
5145
- id: (0, import_types18.infraId)(kind, name),
5146
- type: import_types18.NodeType.InfraNode,
5314
+ id: (0, import_types19.infraId)(kind, name),
5315
+ type: import_types19.NodeType.InfraNode,
5147
5316
  name,
5148
5317
  provider,
5149
5318
  kind,
@@ -5172,7 +5341,7 @@ function dependsOnList(value) {
5172
5341
  }
5173
5342
  function serviceNameToServiceNode(name, services) {
5174
5343
  for (const s of services) {
5175
- if (s.node.name === name || import_node_path29.default.basename(s.dir) === name) return s.node.id;
5344
+ if (s.node.name === name || import_node_path30.default.basename(s.dir) === name) return s.node.id;
5176
5345
  }
5177
5346
  return null;
5178
5347
  }
@@ -5181,7 +5350,7 @@ async function addComposeInfra(graph, scanPath, services) {
5181
5350
  let edgesAdded = 0;
5182
5351
  let composePath = null;
5183
5352
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
5184
- const abs = import_node_path29.default.join(scanPath, name);
5353
+ const abs = import_node_path30.default.join(scanPath, name);
5185
5354
  if (await exists2(abs)) {
5186
5355
  composePath = abs;
5187
5356
  break;
@@ -5194,13 +5363,13 @@ async function addComposeInfra(graph, scanPath, services) {
5194
5363
  } catch (err) {
5195
5364
  recordExtractionError(
5196
5365
  "infra docker-compose",
5197
- import_node_path29.default.relative(scanPath, composePath),
5366
+ import_node_path30.default.relative(scanPath, composePath),
5198
5367
  err
5199
5368
  );
5200
5369
  return { nodesAdded, edgesAdded };
5201
5370
  }
5202
5371
  if (!compose?.services) return { nodesAdded, edgesAdded };
5203
- const evidenceFile = import_node_path29.default.relative(scanPath, composePath).split(import_node_path29.default.sep).join("/");
5372
+ const evidenceFile = import_node_path30.default.relative(scanPath, composePath).split(import_node_path30.default.sep).join("/");
5204
5373
  const composeNameToNodeId = /* @__PURE__ */ new Map();
5205
5374
  for (const [composeName, svc] of Object.entries(compose.services)) {
5206
5375
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -5222,15 +5391,15 @@ async function addComposeInfra(graph, scanPath, services) {
5222
5391
  for (const dep of dependsOnList(svc.depends_on)) {
5223
5392
  const targetId = composeNameToNodeId.get(dep);
5224
5393
  if (!targetId) continue;
5225
- const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types19.EdgeType.DEPENDS_ON);
5394
+ const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types20.EdgeType.DEPENDS_ON);
5226
5395
  if (graph.hasEdge(edgeId)) continue;
5227
5396
  const edge = {
5228
5397
  id: edgeId,
5229
5398
  source: sourceId,
5230
5399
  target: targetId,
5231
- type: import_types19.EdgeType.DEPENDS_ON,
5232
- provenance: import_types19.Provenance.EXTRACTED,
5233
- confidence: (0, import_types19.confidenceForExtracted)("structural"),
5400
+ type: import_types20.EdgeType.DEPENDS_ON,
5401
+ provenance: import_types20.Provenance.EXTRACTED,
5402
+ confidence: (0, import_types20.confidenceForExtracted)("structural"),
5234
5403
  evidence: { file: evidenceFile }
5235
5404
  };
5236
5405
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5242,9 +5411,9 @@ async function addComposeInfra(graph, scanPath, services) {
5242
5411
 
5243
5412
  // src/extract/infra/dockerfile.ts
5244
5413
  init_cjs_shims();
5245
- var import_node_path30 = __toESM(require("path"), 1);
5414
+ var import_node_path31 = __toESM(require("path"), 1);
5246
5415
  var import_node_fs17 = require("fs");
5247
- var import_types20 = require("@neat.is/types");
5416
+ var import_types21 = require("@neat.is/types");
5248
5417
  function runtimeImage(content) {
5249
5418
  const lines = content.split("\n");
5250
5419
  let last = null;
@@ -5263,7 +5432,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5263
5432
  let nodesAdded = 0;
5264
5433
  let edgesAdded = 0;
5265
5434
  for (const service of services) {
5266
- const dockerfilePath = import_node_path30.default.join(service.dir, "Dockerfile");
5435
+ const dockerfilePath = import_node_path31.default.join(service.dir, "Dockerfile");
5267
5436
  if (!await exists2(dockerfilePath)) continue;
5268
5437
  let content;
5269
5438
  try {
@@ -5271,7 +5440,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5271
5440
  } catch (err) {
5272
5441
  recordExtractionError(
5273
5442
  "infra dockerfile",
5274
- import_node_path30.default.relative(scanPath, dockerfilePath),
5443
+ import_node_path31.default.relative(scanPath, dockerfilePath),
5275
5444
  err
5276
5445
  );
5277
5446
  continue;
@@ -5283,7 +5452,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5283
5452
  graph.addNode(node.id, node);
5284
5453
  nodesAdded++;
5285
5454
  }
5286
- const relDockerfile = toPosix2(import_node_path30.default.relative(service.dir, dockerfilePath));
5455
+ const relDockerfile = toPosix2(import_node_path31.default.relative(service.dir, dockerfilePath));
5287
5456
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
5288
5457
  graph,
5289
5458
  service.pkg.name,
@@ -5292,17 +5461,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5292
5461
  );
5293
5462
  nodesAdded += fn;
5294
5463
  edgesAdded += fe;
5295
- const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types20.EdgeType.RUNS_ON);
5464
+ const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types21.EdgeType.RUNS_ON);
5296
5465
  if (!graph.hasEdge(edgeId)) {
5297
5466
  const edge = {
5298
5467
  id: edgeId,
5299
5468
  source: fileNodeId,
5300
5469
  target: node.id,
5301
- type: import_types20.EdgeType.RUNS_ON,
5302
- provenance: import_types20.Provenance.EXTRACTED,
5303
- confidence: (0, import_types20.confidenceForExtracted)("structural"),
5470
+ type: import_types21.EdgeType.RUNS_ON,
5471
+ provenance: import_types21.Provenance.EXTRACTED,
5472
+ confidence: (0, import_types21.confidenceForExtracted)("structural"),
5304
5473
  evidence: {
5305
- file: toPosix2(import_node_path30.default.relative(scanPath, dockerfilePath))
5474
+ file: toPosix2(import_node_path31.default.relative(scanPath, dockerfilePath))
5306
5475
  }
5307
5476
  };
5308
5477
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5315,7 +5484,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
5315
5484
  // src/extract/infra/terraform.ts
5316
5485
  init_cjs_shims();
5317
5486
  var import_node_fs18 = require("fs");
5318
- var import_node_path31 = __toESM(require("path"), 1);
5487
+ var import_node_path32 = __toESM(require("path"), 1);
5319
5488
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
5320
5489
  async function walkTfFiles(start, depth = 0, max = 5) {
5321
5490
  if (depth > max) return [];
@@ -5324,11 +5493,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
5324
5493
  for (const entry of entries) {
5325
5494
  if (entry.isDirectory()) {
5326
5495
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
5327
- const child = import_node_path31.default.join(start, entry.name);
5496
+ const child = import_node_path32.default.join(start, entry.name);
5328
5497
  if (await isPythonVenvDir(child)) continue;
5329
5498
  out.push(...await walkTfFiles(child, depth + 1, max));
5330
5499
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
5331
- out.push(import_node_path31.default.join(start, entry.name));
5500
+ out.push(import_node_path32.default.join(start, entry.name));
5332
5501
  }
5333
5502
  }
5334
5503
  return out;
@@ -5356,7 +5525,7 @@ async function addTerraformResources(graph, scanPath) {
5356
5525
  // src/extract/infra/k8s.ts
5357
5526
  init_cjs_shims();
5358
5527
  var import_node_fs19 = require("fs");
5359
- var import_node_path32 = __toESM(require("path"), 1);
5528
+ var import_node_path33 = __toESM(require("path"), 1);
5360
5529
  var import_yaml3 = require("yaml");
5361
5530
  var K8S_KIND_TO_INFRA_KIND = {
5362
5531
  Service: "k8s-service",
@@ -5374,11 +5543,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
5374
5543
  for (const entry of entries) {
5375
5544
  if (entry.isDirectory()) {
5376
5545
  if (IGNORED_DIRS.has(entry.name)) continue;
5377
- const child = import_node_path32.default.join(start, entry.name);
5546
+ const child = import_node_path33.default.join(start, entry.name);
5378
5547
  if (await isPythonVenvDir(child)) continue;
5379
5548
  out.push(...await walkYamlFiles2(child, depth + 1, max));
5380
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path32.default.extname(entry.name))) {
5381
- out.push(import_node_path32.default.join(start, entry.name));
5549
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path33.default.extname(entry.name))) {
5550
+ out.push(import_node_path33.default.join(start, entry.name));
5382
5551
  }
5383
5552
  }
5384
5553
  return out;
@@ -5422,17 +5591,17 @@ async function addInfra(graph, scanPath, services) {
5422
5591
  }
5423
5592
 
5424
5593
  // src/extract/index.ts
5425
- var import_node_path34 = __toESM(require("path"), 1);
5594
+ var import_node_path35 = __toESM(require("path"), 1);
5426
5595
 
5427
5596
  // src/extract/retire.ts
5428
5597
  init_cjs_shims();
5429
5598
  var import_node_fs20 = require("fs");
5430
- var import_node_path33 = __toESM(require("path"), 1);
5431
- var import_types21 = require("@neat.is/types");
5599
+ var import_node_path34 = __toESM(require("path"), 1);
5600
+ var import_types22 = require("@neat.is/types");
5432
5601
  function dropOrphanedFileNodes(graph) {
5433
5602
  const orphans = [];
5434
5603
  graph.forEachNode((id, attrs) => {
5435
- if (attrs.type !== import_types21.NodeType.FileNode) return;
5604
+ if (attrs.type !== import_types22.NodeType.FileNode) return;
5436
5605
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
5437
5606
  orphans.push(id);
5438
5607
  }
@@ -5445,14 +5614,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
5445
5614
  const bases = [scanPath, ...serviceDirs];
5446
5615
  graph.forEachEdge((id, attrs) => {
5447
5616
  const edge = attrs;
5448
- if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
5617
+ if (edge.provenance !== import_types22.Provenance.EXTRACTED) return;
5449
5618
  const evidenceFile = edge.evidence?.file;
5450
5619
  if (!evidenceFile) return;
5451
- if (import_node_path33.default.isAbsolute(evidenceFile)) {
5620
+ if (import_node_path34.default.isAbsolute(evidenceFile)) {
5452
5621
  if (!(0, import_node_fs20.existsSync)(evidenceFile)) toDrop.push(id);
5453
5622
  return;
5454
5623
  }
5455
- const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path33.default.join(base, evidenceFile)));
5624
+ const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path34.default.join(base, evidenceFile)));
5456
5625
  if (!found) toDrop.push(id);
5457
5626
  });
5458
5627
  for (const id of toDrop) graph.dropEdge(id);
@@ -5492,7 +5661,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5492
5661
  }
5493
5662
  const droppedEntries = drainDroppedExtracted();
5494
5663
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
5495
- const rejectedPath = import_node_path34.default.join(import_node_path34.default.dirname(opts.errorsPath), "rejected.ndjson");
5664
+ const rejectedPath = import_node_path35.default.join(import_node_path35.default.dirname(opts.errorsPath), "rejected.ndjson");
5496
5665
  try {
5497
5666
  await writeRejectedExtracted(droppedEntries, rejectedPath);
5498
5667
  } catch (err) {
@@ -5604,8 +5773,8 @@ function canonicalJson(value) {
5604
5773
  // src/persist.ts
5605
5774
  init_cjs_shims();
5606
5775
  var import_node_fs22 = require("fs");
5607
- var import_node_path35 = __toESM(require("path"), 1);
5608
- var import_types22 = require("@neat.is/types");
5776
+ var import_node_path36 = __toESM(require("path"), 1);
5777
+ var import_types23 = require("@neat.is/types");
5609
5778
  var SCHEMA_VERSION = 4;
5610
5779
  function migrateV1ToV2(payload) {
5611
5780
  const nodes = payload.graph.nodes;
@@ -5627,12 +5796,12 @@ function migrateV2ToV3(payload) {
5627
5796
  for (const edge of edges) {
5628
5797
  const attrs = edge.attributes;
5629
5798
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
5630
- attrs.provenance = import_types22.Provenance.OBSERVED;
5799
+ attrs.provenance = import_types23.Provenance.OBSERVED;
5631
5800
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
5632
5801
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
5633
5802
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
5634
5803
  if (type && source && target) {
5635
- const newId = (0, import_types22.observedEdgeId)(source, target, type);
5804
+ const newId = (0, import_types23.observedEdgeId)(source, target, type);
5636
5805
  attrs.id = newId;
5637
5806
  if (edge.key) edge.key = newId;
5638
5807
  }
@@ -5641,7 +5810,7 @@ function migrateV2ToV3(payload) {
5641
5810
  return { ...payload, schemaVersion: 3 };
5642
5811
  }
5643
5812
  async function ensureDir(filePath) {
5644
- await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(filePath), { recursive: true });
5813
+ await import_node_fs22.promises.mkdir(import_node_path36.default.dirname(filePath), { recursive: true });
5645
5814
  }
5646
5815
  async function saveGraphToDisk(graph, outPath) {
5647
5816
  await ensureDir(outPath);
@@ -5716,23 +5885,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
5716
5885
 
5717
5886
  // src/projects.ts
5718
5887
  init_cjs_shims();
5719
- var import_node_path36 = __toESM(require("path"), 1);
5888
+ var import_node_path37 = __toESM(require("path"), 1);
5720
5889
  function pathsForProject(project, baseDir) {
5721
5890
  if (project === DEFAULT_PROJECT) {
5722
5891
  return {
5723
- snapshotPath: import_node_path36.default.join(baseDir, "graph.json"),
5724
- errorsPath: import_node_path36.default.join(baseDir, "errors.ndjson"),
5725
- staleEventsPath: import_node_path36.default.join(baseDir, "stale-events.ndjson"),
5726
- embeddingsCachePath: import_node_path36.default.join(baseDir, "embeddings.json"),
5727
- policyViolationsPath: import_node_path36.default.join(baseDir, "policy-violations.ndjson")
5892
+ snapshotPath: import_node_path37.default.join(baseDir, "graph.json"),
5893
+ errorsPath: import_node_path37.default.join(baseDir, "errors.ndjson"),
5894
+ staleEventsPath: import_node_path37.default.join(baseDir, "stale-events.ndjson"),
5895
+ embeddingsCachePath: import_node_path37.default.join(baseDir, "embeddings.json"),
5896
+ policyViolationsPath: import_node_path37.default.join(baseDir, "policy-violations.ndjson")
5728
5897
  };
5729
5898
  }
5730
5899
  return {
5731
- snapshotPath: import_node_path36.default.join(baseDir, `${project}.json`),
5732
- errorsPath: import_node_path36.default.join(baseDir, `errors.${project}.ndjson`),
5733
- staleEventsPath: import_node_path36.default.join(baseDir, `stale-events.${project}.ndjson`),
5734
- embeddingsCachePath: import_node_path36.default.join(baseDir, `embeddings.${project}.json`),
5735
- policyViolationsPath: import_node_path36.default.join(baseDir, `policy-violations.${project}.ndjson`)
5900
+ snapshotPath: import_node_path37.default.join(baseDir, `${project}.json`),
5901
+ errorsPath: import_node_path37.default.join(baseDir, `errors.${project}.ndjson`),
5902
+ staleEventsPath: import_node_path37.default.join(baseDir, `stale-events.${project}.ndjson`),
5903
+ embeddingsCachePath: import_node_path37.default.join(baseDir, `embeddings.${project}.json`),
5904
+ policyViolationsPath: import_node_path37.default.join(baseDir, `policy-violations.${project}.ndjson`)
5736
5905
  };
5737
5906
  }
5738
5907
  var Projects = class {
@@ -5774,15 +5943,15 @@ function parseExtraProjects(raw) {
5774
5943
  init_cjs_shims();
5775
5944
  var import_node_fs23 = require("fs");
5776
5945
  var import_node_os3 = __toESM(require("os"), 1);
5777
- var import_node_path37 = __toESM(require("path"), 1);
5778
- var import_types23 = require("@neat.is/types");
5946
+ var import_node_path38 = __toESM(require("path"), 1);
5947
+ var import_types24 = require("@neat.is/types");
5779
5948
  function neatHome() {
5780
5949
  const override = process.env.NEAT_HOME;
5781
- if (override && override.length > 0) return import_node_path37.default.resolve(override);
5782
- return import_node_path37.default.join(import_node_os3.default.homedir(), ".neat");
5950
+ if (override && override.length > 0) return import_node_path38.default.resolve(override);
5951
+ return import_node_path38.default.join(import_node_os3.default.homedir(), ".neat");
5783
5952
  }
5784
5953
  function registryPath() {
5785
- return import_node_path37.default.join(neatHome(), "projects.json");
5954
+ return import_node_path38.default.join(neatHome(), "projects.json");
5786
5955
  }
5787
5956
  async function readRegistry() {
5788
5957
  const file = registryPath();
@@ -5796,7 +5965,7 @@ async function readRegistry() {
5796
5965
  throw err;
5797
5966
  }
5798
5967
  const parsed = JSON.parse(raw);
5799
- return import_types23.RegistryFileSchema.parse(parsed);
5968
+ return import_types24.RegistryFileSchema.parse(parsed);
5800
5969
  }
5801
5970
  async function getProject(name) {
5802
5971
  const reg = await readRegistry();
@@ -5806,6 +5975,8 @@ async function listProjects() {
5806
5975
  const reg = await readRegistry();
5807
5976
  return reg.projects;
5808
5977
  }
5978
+ var DAY_MS2 = 24 * 60 * 60 * 1e3;
5979
+ var DEFAULT_PRUNE_TTL_MS = 7 * DAY_MS2;
5809
5980
 
5810
5981
  // src/streaming.ts
5811
5982
  init_cjs_shims();
@@ -5819,6 +5990,7 @@ function handleSse(req, reply, opts) {
5819
5990
  reply.raw.setHeader("Connection", "keep-alive");
5820
5991
  reply.raw.setHeader("X-Accel-Buffering", "no");
5821
5992
  reply.raw.flushHeaders?.();
5993
+ reply.raw.write(":open\n\n");
5822
5994
  let pending = 0;
5823
5995
  let dropped = false;
5824
5996
  const closeConnection = () => {
@@ -5998,11 +6170,11 @@ function registerRoutes(scope, ctx) {
5998
6170
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
5999
6171
  const parsed = [];
6000
6172
  for (const c of candidates) {
6001
- const r = import_types24.DivergenceTypeSchema.safeParse(c);
6173
+ const r = import_types25.DivergenceTypeSchema.safeParse(c);
6002
6174
  if (!r.success) {
6003
6175
  return reply.code(400).send({
6004
6176
  error: `unknown divergence type "${c}"`,
6005
- allowed: import_types24.DivergenceTypeSchema.options
6177
+ allowed: import_types25.DivergenceTypeSchema.options
6006
6178
  });
6007
6179
  }
6008
6180
  parsed.push(r.data);
@@ -6215,7 +6387,7 @@ function registerRoutes(scope, ctx) {
6215
6387
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
6216
6388
  let violations = await log.readAll();
6217
6389
  if (req.query.severity) {
6218
- const sev = import_types24.PolicySeveritySchema.safeParse(req.query.severity);
6390
+ const sev = import_types25.PolicySeveritySchema.safeParse(req.query.severity);
6219
6391
  if (!sev.success) {
6220
6392
  return reply.code(400).send({
6221
6393
  error: "invalid severity",
@@ -6232,7 +6404,7 @@ function registerRoutes(scope, ctx) {
6232
6404
  scope.post("/policies/check", async (req, reply) => {
6233
6405
  const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6234
6406
  if (!proj) return;
6235
- const parsed = import_types24.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6407
+ const parsed = import_types25.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6236
6408
  if (!parsed.success) {
6237
6409
  return reply.code(400).send({
6238
6410
  error: "invalid /policies/check body",
@@ -6475,7 +6647,7 @@ init_otel_grpc();
6475
6647
  // src/search.ts
6476
6648
  init_cjs_shims();
6477
6649
  var import_node_fs24 = require("fs");
6478
- var import_node_path40 = __toESM(require("path"), 1);
6650
+ var import_node_path41 = __toESM(require("path"), 1);
6479
6651
  var import_node_crypto3 = require("crypto");
6480
6652
  var DEFAULT_LIMIT = 10;
6481
6653
  var NOMIC_DIM = 768;
@@ -6614,7 +6786,7 @@ async function readCache(cachePath) {
6614
6786
  }
6615
6787
  }
6616
6788
  async function writeCache(cachePath, cache) {
6617
- await import_node_fs24.promises.mkdir(import_node_path40.default.dirname(cachePath), { recursive: true });
6789
+ await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(cachePath), { recursive: true });
6618
6790
  await import_node_fs24.promises.writeFile(cachePath, JSON.stringify(cache));
6619
6791
  }
6620
6792
  var VectorIndex = class {
@@ -6795,14 +6967,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
6795
6967
  async function main() {
6796
6968
  const baseDirEnv = process.env.NEAT_OUT_DIR;
6797
6969
  const legacyOutPath = process.env.NEAT_OUT_PATH;
6798
- const baseDir = baseDirEnv ? import_node_path41.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path41.default.resolve(import_node_path41.default.dirname(legacyOutPath)) : import_node_path41.default.resolve("./neat-out");
6799
- const defaultScanPath = import_node_path41.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
6970
+ const baseDir = baseDirEnv ? import_node_path42.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path42.default.resolve(import_node_path42.default.dirname(legacyOutPath)) : import_node_path42.default.resolve("./neat-out");
6971
+ const defaultScanPath = import_node_path42.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
6800
6972
  const registry = new Projects();
6801
6973
  await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
6802
6974
  for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
6803
6975
  const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
6804
6976
  const projectScan = process.env[envKey];
6805
- await bootProject(registry, name, projectScan ? import_node_path41.default.resolve(projectScan) : void 0, baseDir);
6977
+ await bootProject(registry, name, projectScan ? import_node_path42.default.resolve(projectScan) : void 0, baseDir);
6806
6978
  }
6807
6979
  const host = process.env.HOST ?? "0.0.0.0";
6808
6980
  const port = Number(process.env.PORT ?? 8080);