@neat.is/core 0.4.16 → 0.4.18

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