@neat.is/core 0.9.8-dev.20260829 → 0.9.9

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/neatd.cjs CHANGED
@@ -6309,11 +6309,25 @@ function upsertInferredEdge(graph, type, source, target, ts) {
6309
6309
  };
6310
6310
  graph.addEdgeWithKey(id, source, target, edge);
6311
6311
  }
6312
+ function emitIncidentEvent(project, ev) {
6313
+ emitNeatEvent({
6314
+ type: "incident",
6315
+ project,
6316
+ payload: {
6317
+ incidentId: ev.id,
6318
+ affectedNode: ev.affectedNode,
6319
+ service: ev.service,
6320
+ incidentKind: (0, import_types8.incidentKindOf)(ev),
6321
+ at: ev.timestamp
6322
+ }
6323
+ });
6324
+ }
6312
6325
  async function appendErrorEvent(ctx, ev) {
6313
6326
  await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(ctx.errorsPath), { recursive: true });
6314
6327
  await import_node_fs7.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
6328
+ emitIncidentEvent(ctx.project ?? DEFAULT_PROJECT, ev);
6315
6329
  }
6316
- async function appendConnectorIncident(errorsPath, input) {
6330
+ async function appendConnectorIncident(errorsPath, input, project) {
6317
6331
  const ev = {
6318
6332
  id: input.id,
6319
6333
  timestamp: input.timestamp,
@@ -6327,6 +6341,7 @@ async function appendConnectorIncident(errorsPath, input) {
6327
6341
  };
6328
6342
  await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(errorsPath), { recursive: true });
6329
6343
  await import_node_fs7.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
6344
+ if (project) emitIncidentEvent(project, ev);
6330
6345
  }
6331
6346
  function landIncidentCallSite(span, callSite, trusted, graph) {
6332
6347
  const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
@@ -6420,12 +6435,13 @@ function buildErrorEventForReceiver(span, graph, scanPath) {
6420
6435
  affectedNode: locus.affectedNode
6421
6436
  };
6422
6437
  }
6423
- function makeErrorSpanWriter(errorsPath, graph, scanPath) {
6438
+ function makeErrorSpanWriter(errorsPath, graph, scanPath, project = DEFAULT_PROJECT) {
6424
6439
  return async (span) => {
6425
6440
  const ev = buildErrorEventForReceiver(span, graph, scanPath);
6426
6441
  if (!ev) return;
6427
6442
  await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(errorsPath), { recursive: true });
6428
6443
  await import_node_fs7.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
6444
+ emitIncidentEvent(project, ev);
6429
6445
  };
6430
6446
  }
6431
6447
  async function recordFailingResponseIncident(ctx, span, affectedNode, timestamp, statusCode, count, firstTimestamp) {
@@ -16132,7 +16148,7 @@ var Projects = class {
16132
16148
  init_cjs_shims();
16133
16149
  var import_fastify2 = __toESM(require("fastify"), 1);
16134
16150
  var import_cors = __toESM(require("@fastify/cors"), 1);
16135
- var import_types97 = require("@neat.is/types");
16151
+ var import_types98 = require("@neat.is/types");
16136
16152
 
16137
16153
  // src/extend/index.ts
16138
16154
  init_cjs_shims();
@@ -17174,9 +17190,160 @@ function queryLogEntries(opts) {
17174
17190
  return merged;
17175
17191
  }
17176
17192
 
17177
- // src/ask.ts
17193
+ // src/goodybag.ts
17178
17194
  init_cjs_shims();
17179
17195
  var import_types59 = require("@neat.is/types");
17196
+ var CODE_FILEPATH_ATTR2 = "code.filepath";
17197
+ var CODE_LINENO_ATTR2 = "code.lineno";
17198
+ var BLAST_NEAREST_LIMIT = 5;
17199
+ function grainOf2(graph, nodeId) {
17200
+ if (graph.hasNode(nodeId)) {
17201
+ const t = graph.getNodeAttributes(nodeId).type;
17202
+ if (typeof t === "string" && t.length > 0) {
17203
+ return (t.endsWith("Node") ? t.slice(0, -4) : t).toLowerCase();
17204
+ }
17205
+ }
17206
+ const colon = nodeId.indexOf(":");
17207
+ return colon > 0 ? nodeId.slice(0, colon) : "unknown";
17208
+ }
17209
+ function locusOf(graph, ev) {
17210
+ const file = ev.attributes?.[CODE_FILEPATH_ATTR2];
17211
+ if (typeof file !== "string" || file.length === 0) return null;
17212
+ const rawLine = ev.attributes?.[CODE_LINENO_ATTR2];
17213
+ const line = typeof rawLine === "number" ? rawLine : Number(rawLine);
17214
+ const node = graph.hasNode(ev.affectedNode) ? graph.getNodeAttributes(ev.affectedNode) : void 0;
17215
+ return {
17216
+ file,
17217
+ ...Number.isFinite(line) ? { lineStart: line, lineEnd: line } : {},
17218
+ ...node?.name ? { symbol: node.name } : {},
17219
+ service: node?.service ?? ev.service,
17220
+ provenance: import_types59.Provenance.OBSERVED
17221
+ };
17222
+ }
17223
+ function locusFromNode(graph, nodeId) {
17224
+ if (!graph.hasNode(nodeId)) return null;
17225
+ const n = graph.getNodeAttributes(nodeId);
17226
+ const file = n.relPath ?? n.path;
17227
+ if (typeof file !== "string" || file.length === 0) return null;
17228
+ const start = n.span?.startLine;
17229
+ const end = n.span?.endLine;
17230
+ return {
17231
+ file,
17232
+ ...typeof start === "number" ? { lineStart: start } : {},
17233
+ ...typeof end === "number" ? { lineEnd: end } : {},
17234
+ ...n.qualname ? { symbol: shortLabel(graph, nodeId) } : {},
17235
+ ...n.service ? { service: n.service } : {},
17236
+ provenance: import_types59.Provenance.INFERRED
17237
+ };
17238
+ }
17239
+ function promoteCauseLocus(graph, causeNode, incidents) {
17240
+ const native = incidents.find(
17241
+ (e) => e.affectedNode === causeNode && typeof e.attributes?.[CODE_FILEPATH_ATTR2] === "string"
17242
+ );
17243
+ if (native) {
17244
+ const l = locusOf(graph, native);
17245
+ if (l) return { ...l, symbol: shortLabel(graph, causeNode), provenance: import_types59.Provenance.INFERRED };
17246
+ }
17247
+ return locusFromNode(graph, causeNode);
17248
+ }
17249
+ function divergenceSummary(d) {
17250
+ const column = "column" in d && d.column ? `.${d.column}` : "";
17251
+ const label = "table" in d && d.table ? `${d.table}${column}` : `${d.source} \u2192 ${d.target}`;
17252
+ return label;
17253
+ }
17254
+ function baseName(p) {
17255
+ const parts = p.split(/[\\/]/);
17256
+ return parts[parts.length - 1] || p;
17257
+ }
17258
+ function shortLabel(graph, nodeId) {
17259
+ if (graph.hasNode(nodeId)) {
17260
+ const name = graph.getNodeAttributes(nodeId).name;
17261
+ if (typeof name === "string" && name.length > 0) return name;
17262
+ }
17263
+ const afterHash = nodeId.includes("#") ? nodeId.slice(nodeId.lastIndexOf("#") + 1) : nodeId;
17264
+ return afterHash.includes(":") ? afterHash.slice(afterHash.lastIndexOf(":") + 1) : afterHash;
17265
+ }
17266
+ function renderHeadline(graph, ev, locus, causeNode) {
17267
+ const what = ev.exceptionType ? `raised ${ev.exceptionType}` : ev.errorMessage || "failed";
17268
+ const causeLabel = causeNode && causeNode !== ev.affectedNode ? shortLabel(graph, causeNode) : "";
17269
+ if (locus) {
17270
+ const base = baseName(locus.file);
17271
+ const lines = locus.lineStart != null ? locus.lineEnd && locus.lineEnd !== locus.lineStart ? `LINES ${locus.lineStart}-${locus.lineEnd}` : `LINE ${locus.lineStart}` : "";
17272
+ const symbol = locus.symbol ?? (grainOf2(graph, ev.affectedNode) === "symbol" ? shortLabel(graph, ev.affectedNode) : void 0);
17273
+ const subject = symbol ? `SYMBOL ${symbol}` : `FILE ${base}`;
17274
+ const at = symbol ? `${lines ? `${lines} in ` : ""}${base}` : lines;
17275
+ const where = at ? ` at ${at}` : "";
17276
+ const svc = locus.service ?? ev.service;
17277
+ const cause2 = causeLabel && causeLabel !== symbol ? ` \u2192 root cause ${causeLabel}` : "";
17278
+ return `${subject}${where} (SERVICE ${svc}) ${what} at ${ev.timestamp}${cause2}`;
17279
+ }
17280
+ const cause = causeLabel ? ` \u2192 root cause ${causeLabel}` : "";
17281
+ return `SERVICE ${ev.service} ${what} at ${ev.timestamp}${cause}`;
17282
+ }
17283
+ function buildIncidentCard(graph, errorEvent, incidents, policies) {
17284
+ const affected = errorEvent.affectedNode;
17285
+ let locus = locusOf(graph, errorEvent);
17286
+ const inGraph = graph.hasNode(affected);
17287
+ const rc = inGraph ? getRootCause(graph, affected, errorEvent, incidents) : null;
17288
+ let rootCause = null;
17289
+ if (rc) {
17290
+ const provs = rc.edgeProvenances ?? [];
17291
+ const chain = (rc.traversalPath ?? []).map((node, i) => ({
17292
+ node,
17293
+ grain: grainOf2(graph, node),
17294
+ provenance: provs[i] ?? provs[provs.length - 1] ?? import_types59.Provenance.INFERRED
17295
+ }));
17296
+ rootCause = {
17297
+ node: rc.rootCauseNode,
17298
+ ...rc.candidates?.[0]?.classification ? { classification: rc.candidates[0].classification } : {},
17299
+ reason: rc.rootCauseReason,
17300
+ confidence: rc.confidence,
17301
+ fix: rc.fixRecommendation ?? null,
17302
+ chain
17303
+ };
17304
+ }
17305
+ if (locus === null && rootCause) {
17306
+ locus = promoteCauseLocus(graph, rootCause.node, incidents);
17307
+ }
17308
+ const blast = inGraph ? getBlastRadius(graph, affected) : { origin: affected, affectedNodes: [], totalAffected: 0 };
17309
+ const nearest = [...blast.affectedNodes].sort((a, b) => a.distance - b.distance).slice(0, BLAST_NEAREST_LIMIT).map((n) => ({ node: n.nodeId, distance: n.distance, provenance: n.edgeProvenance }));
17310
+ const applicable = inGraph ? selectApplicablePolicies(graph, policies, affected) : [];
17311
+ const policyCards = applicable.map((p) => ({
17312
+ policyName: p.policyName,
17313
+ severity: p.severity,
17314
+ message: p.reason
17315
+ }));
17316
+ const divergences = inGraph ? computeDivergences(graph, { node: affected, incidents }).divergences.map((d) => ({
17317
+ type: d.type,
17318
+ summary: divergenceSummary(d)
17319
+ })) : [];
17320
+ const card = {
17321
+ kind: "incident",
17322
+ id: errorEvent.id,
17323
+ at: errorEvent.timestamp,
17324
+ incidentKind: (0, import_types59.incidentKindOf)(errorEvent),
17325
+ service: errorEvent.service,
17326
+ affectedNode: affected,
17327
+ message: errorEvent.errorMessage,
17328
+ ...errorEvent.exceptionType ? { exceptionType: errorEvent.exceptionType } : {},
17329
+ ...errorEvent.httpStatusCode !== void 0 ? { httpStatusCode: errorEvent.httpStatusCode } : {},
17330
+ ...errorEvent.incidentCount !== void 0 ? { count: errorEvent.incidentCount } : {},
17331
+ ...errorEvent.firstTimestamp && errorEvent.lastTimestamp ? { window: { first: errorEvent.firstTimestamp, last: errorEvent.lastTimestamp } } : {},
17332
+ ...errorEvent.traceId ? { traceId: errorEvent.traceId } : {},
17333
+ ...errorEvent.spanId ? { spanId: errorEvent.spanId } : {},
17334
+ locus,
17335
+ rootCause,
17336
+ ...nearest.length > 0 ? { blastRadius: { totalAffected: blast.totalAffected, nearest } } : {},
17337
+ ...policyCards.length > 0 ? { policies: policyCards } : {},
17338
+ ...divergences.length > 0 ? { divergence: divergences } : {},
17339
+ headline: renderHeadline(graph, errorEvent, locus, rootCause?.node ?? null)
17340
+ };
17341
+ return import_types59.IncidentCardSchema.parse(card);
17342
+ }
17343
+
17344
+ // src/ask.ts
17345
+ init_cjs_shims();
17346
+ var import_types60 = require("@neat.is/types");
17180
17347
  var DEFAULT_MAX_NODES = 3;
17181
17348
  var MAX_FACTS_PER_SECTION = 6;
17182
17349
  var INTENT_RULES = [
@@ -17404,7 +17571,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17404
17571
  };
17405
17572
  graph.forEachNode((id, attrs) => {
17406
17573
  const node = attrs;
17407
- if (node.type === import_types59.NodeType.FrontierNode) return;
17574
+ if (node.type === import_types60.NodeType.FrontierNode) return;
17408
17575
  const name = nodeName(node);
17409
17576
  const body = idBody(id);
17410
17577
  const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
@@ -17423,7 +17590,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17423
17590
  const res = await searchIndex.search(question, 10);
17424
17591
  if (res.provider !== "substring") {
17425
17592
  for (const m of res.matches) {
17426
- if (m.node.type === import_types59.NodeType.FrontierNode) continue;
17593
+ if (m.node.type === import_types60.NodeType.FrontierNode) continue;
17427
17594
  const already = best.get(m.node.id);
17428
17595
  if (!already && m.score < EMBED_MIN_SCORE) continue;
17429
17596
  consider({
@@ -17461,7 +17628,7 @@ function edgeSignalNote(e) {
17461
17628
  function buildRootCauseSection(graph, node, incidents, now) {
17462
17629
  const result = getRootCause(graph, node, void 0, incidents, { now });
17463
17630
  if (!result) return null;
17464
- const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types59.Provenance.OBSERVED;
17631
+ const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types60.Provenance.OBSERVED;
17465
17632
  const facts = [
17466
17633
  {
17467
17634
  text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
@@ -17511,7 +17678,7 @@ function buildObservedSection(graph, node) {
17511
17678
  if (result.observed && result.inboundObservedCount > 0) {
17512
17679
  facts.push({
17513
17680
  text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
17514
- provenance: import_types59.Provenance.OBSERVED
17681
+ provenance: import_types60.Provenance.OBSERVED
17515
17682
  });
17516
17683
  } else {
17517
17684
  return null;
@@ -17539,7 +17706,7 @@ function buildIncidentsSection(node, incidents) {
17539
17706
  const facts = ordered.map((ev) => ({
17540
17707
  text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
17541
17708
  // ErrorEvents are observation records — OBSERVED by definition.
17542
- provenance: import_types59.Provenance.OBSERVED
17709
+ provenance: import_types60.Provenance.OBSERVED
17543
17710
  }));
17544
17711
  return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
17545
17712
  }
@@ -17599,7 +17766,7 @@ function buildGlobalIncidentsSection(incidents) {
17599
17766
  }
17600
17767
  const byKey = /* @__PURE__ */ new Map();
17601
17768
  for (const ev of incidents) {
17602
- const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
17769
+ const key = ev.affectedNode || (0, import_types60.serviceId)(ev.service);
17603
17770
  const cur = byKey.get(key);
17604
17771
  if (!cur) {
17605
17772
  byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
@@ -17614,7 +17781,7 @@ function buildGlobalIncidentsSection(incidents) {
17614
17781
  const rows = [...byKey.values()].sort((a, b) => b.count - a.count || b.latest.localeCompare(a.latest) || a.key.localeCompare(b.key)).slice(0, MAX_FACTS_PER_SECTION);
17615
17782
  const facts = rows.map((r) => ({
17616
17783
  text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
17617
- provenance: import_types59.Provenance.OBSERVED
17784
+ provenance: import_types60.Provenance.OBSERVED
17618
17785
  }));
17619
17786
  return {
17620
17787
  heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
@@ -17627,7 +17794,7 @@ function buildOverviewSections(graph, incidents) {
17627
17794
  graph.forEachNode((_id, attrs) => {
17628
17795
  const node = attrs;
17629
17796
  nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
17630
- if (node.type === import_types59.NodeType.ServiceNode) services.push(node.id);
17797
+ if (node.type === import_types60.NodeType.ServiceNode) services.push(node.id);
17631
17798
  });
17632
17799
  const edgeByProv = /* @__PURE__ */ new Map();
17633
17800
  graph.forEachEdge((_id, attrs) => {
@@ -17639,10 +17806,10 @@ function buildOverviewSections(graph, incidents) {
17639
17806
  const shapeFacts = [
17640
17807
  { text: `${graph.order} nodes, ${graph.size} edges` },
17641
17808
  {
17642
- text: `${count(import_types59.NodeType.ServiceNode)} services, ${count(import_types59.NodeType.FileNode)} files, ${count(import_types59.NodeType.SymbolNode)} symbols, ${count(import_types59.NodeType.DatabaseNode)} databases`
17809
+ text: `${count(import_types60.NodeType.ServiceNode)} services, ${count(import_types60.NodeType.FileNode)} files, ${count(import_types60.NodeType.SymbolNode)} symbols, ${count(import_types60.NodeType.DatabaseNode)} databases`
17643
17810
  }
17644
17811
  ];
17645
- for (const p of [import_types59.Provenance.EXTRACTED, import_types59.Provenance.OBSERVED, import_types59.Provenance.INFERRED, import_types59.Provenance.STALE]) {
17812
+ for (const p of [import_types60.Provenance.EXTRACTED, import_types60.Provenance.OBSERVED, import_types60.Provenance.INFERRED, import_types60.Provenance.STALE]) {
17646
17813
  const n = edgeByProv.get(p) ?? 0;
17647
17814
  if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
17648
17815
  }
@@ -17659,7 +17826,7 @@ function buildOverviewSections(graph, incidents) {
17659
17826
  if (incidents && incidents.length > 0) {
17660
17827
  const incCount = /* @__PURE__ */ new Map();
17661
17828
  for (const ev of incidents) {
17662
- const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
17829
+ const key = ev.affectedNode || (0, import_types60.serviceId)(ev.service);
17663
17830
  incCount.set(key, (incCount.get(key) ?? 0) + 1);
17664
17831
  }
17665
17832
  const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
@@ -17667,7 +17834,7 @@ function buildOverviewSections(graph, incidents) {
17667
17834
  heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
17668
17835
  facts: top.map(([k, n]) => ({
17669
17836
  text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
17670
- provenance: import_types59.Provenance.OBSERVED
17837
+ provenance: import_types60.Provenance.OBSERVED
17671
17838
  }))
17672
17839
  });
17673
17840
  }
@@ -17803,7 +17970,7 @@ async function askGraph(graph, question, opts = {}) {
17803
17970
  const provSet = /* @__PURE__ */ new Set();
17804
17971
  for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
17805
17972
  const confidence = sections[0]?.facts[0]?.confidence;
17806
- return import_types59.AskResultSchema.parse({
17973
+ return import_types60.AskResultSchema.parse({
17807
17974
  question,
17808
17975
  intent,
17809
17976
  matched,
@@ -17898,7 +18065,7 @@ init_cjs_shims();
17898
18065
  var import_node_fs37 = require("fs");
17899
18066
  var import_node_os3 = __toESM(require("os"), 1);
17900
18067
  var import_node_path72 = __toESM(require("path"), 1);
17901
- var import_types60 = require("@neat.is/types");
18068
+ var import_types61 = require("@neat.is/types");
17902
18069
  var LOCK_TIMEOUT_MS = 5e3;
17903
18070
  var LOCK_RETRY_MS = 50;
17904
18071
  function neatHome() {
@@ -18100,10 +18267,10 @@ async function readRegistry() {
18100
18267
  throw err;
18101
18268
  }
18102
18269
  const parsed = JSON.parse(raw);
18103
- return import_types60.RegistryFileSchema.parse(parsed);
18270
+ return import_types61.RegistryFileSchema.parse(parsed);
18104
18271
  }
18105
18272
  async function writeRegistry(reg) {
18106
- const validated = import_types60.RegistryFileSchema.parse(reg);
18273
+ const validated = import_types61.RegistryFileSchema.parse(reg);
18107
18274
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
18108
18275
  }
18109
18276
  async function getProject(name) {
@@ -18454,15 +18621,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
18454
18621
 
18455
18622
  // src/connectors/index.ts
18456
18623
  init_cjs_shims();
18457
- var import_types61 = require("@neat.is/types");
18624
+ var import_types62 = require("@neat.is/types");
18458
18625
  var NO_ENV = "unknown";
18459
18626
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
18460
18627
  if (!graph.hasNode(targetNodeId)) return void 0;
18461
18628
  const sites = [];
18462
18629
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
18463
18630
  const edge = graph.getEdgeAttributes(edgeId);
18464
- if (edge.provenance !== import_types61.Provenance.EXTRACTED) continue;
18465
- const parsed = (0, import_types61.parseFileId)(edge.source);
18631
+ if (edge.provenance !== import_types62.Provenance.EXTRACTED) continue;
18632
+ const parsed = (0, import_types62.parseFileId)(edge.source);
18466
18633
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
18467
18634
  const site = { relPath: edge.evidence.file };
18468
18635
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -18473,7 +18640,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
18473
18640
  function routeCallSiteFor(graph, targetNodeId) {
18474
18641
  if (!graph.hasNode(targetNodeId)) return void 0;
18475
18642
  const attrs = graph.getNodeAttributes(targetNodeId);
18476
- if (attrs.type !== import_types61.NodeType.RouteNode || !attrs.path) return void 0;
18643
+ if (attrs.type !== import_types62.NodeType.RouteNode || !attrs.path) return void 0;
18477
18644
  const site = { relPath: attrs.path };
18478
18645
  if (attrs.line !== void 0) site.line = attrs.line;
18479
18646
  return site;
@@ -18503,7 +18670,7 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
18503
18670
  errorMessage: signal.incident.errorMessage,
18504
18671
  ...signal.incident.attributes ? { attributes: signal.incident.attributes } : {},
18505
18672
  affectedNode: resolved.targetNodeId
18506
- });
18673
+ }, ctx.project);
18507
18674
  continue;
18508
18675
  }
18509
18676
  if (resolved.ensureInfraNode) {
@@ -19085,23 +19252,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
19085
19252
 
19086
19253
  // src/connectors/supabase/resolve.ts
19087
19254
  init_cjs_shims();
19088
- var import_types63 = require("@neat.is/types");
19255
+ var import_types64 = require("@neat.is/types");
19089
19256
  function createSupabaseResolveTarget(graph, config) {
19090
19257
  return (signal, _ctx) => {
19091
19258
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
19092
19259
  return null;
19093
19260
  }
19094
- const subResourceId = (0, import_types63.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
19261
+ const subResourceId = (0, import_types64.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
19095
19262
  if (graph.hasNode(subResourceId)) {
19096
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
19263
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
19097
19264
  }
19098
- const bareResourceId = (0, import_types63.infraId)(signal.targetKind, signal.targetName);
19265
+ const bareResourceId = (0, import_types64.infraId)(signal.targetKind, signal.targetName);
19099
19266
  if (graph.hasNode(bareResourceId)) {
19100
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
19267
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
19101
19268
  }
19102
- const projectLevelId = (0, import_types63.infraId)("supabase", config.nodeRef);
19269
+ const projectLevelId = (0, import_types64.infraId)("supabase", config.nodeRef);
19103
19270
  if (graph.hasNode(projectLevelId)) {
19104
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
19271
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
19105
19272
  }
19106
19273
  return null;
19107
19274
  };
@@ -19194,7 +19361,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
19194
19361
 
19195
19362
  // src/connectors/railway/index.ts
19196
19363
  init_cjs_shims();
19197
- var import_types67 = require("@neat.is/types");
19364
+ var import_types68 = require("@neat.is/types");
19198
19365
 
19199
19366
  // src/connectors/railway/client.ts
19200
19367
  init_cjs_shims();
@@ -19345,7 +19512,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
19345
19512
  const out = [];
19346
19513
  graph.forEachNode((_id, attrs) => {
19347
19514
  const node = attrs;
19348
- if (node.type !== import_types67.NodeType.RouteNode) return;
19515
+ if (node.type !== import_types68.NodeType.RouteNode) return;
19349
19516
  const route = attrs;
19350
19517
  if (route.service !== serviceName) return;
19351
19518
  out.push({
@@ -19449,12 +19616,12 @@ function createRailwayResolveTarget(config) {
19449
19616
  const serviceName = config.serviceNameById[config.serviceId];
19450
19617
  if (!serviceName) return null;
19451
19618
  if (signal.targetKind === ROUTE_TARGET_KIND) {
19452
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types67.EdgeType.CALLS };
19619
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types68.EdgeType.CALLS };
19453
19620
  }
19454
19621
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
19455
19622
  const peerName = config.serviceNameById[signal.targetName];
19456
19623
  if (!peerName) return null;
19457
- return { targetNodeId: (0, import_types67.serviceId)(peerName), serviceName, edgeType: import_types67.EdgeType.CONNECTS_TO };
19624
+ return { targetNodeId: (0, import_types68.serviceId)(peerName), serviceName, edgeType: import_types68.EdgeType.CONNECTS_TO };
19458
19625
  }
19459
19626
  return null;
19460
19627
  };
@@ -19642,7 +19809,7 @@ function mapLogEntriesToSignals(entries) {
19642
19809
 
19643
19810
  // src/connectors/firebase/resolve.ts
19644
19811
  init_cjs_shims();
19645
- var import_types68 = require("@neat.is/types");
19812
+ var import_types69 = require("@neat.is/types");
19646
19813
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
19647
19814
  switch (resourceType) {
19648
19815
  case "cloud_function":
@@ -19657,7 +19824,7 @@ function routeEntriesFor(graph, serviceName) {
19657
19824
  const entries = [];
19658
19825
  graph.forEachNode((_id, attrs) => {
19659
19826
  const node = attrs;
19660
- if (node.type !== import_types68.NodeType.RouteNode) return;
19827
+ if (node.type !== import_types69.NodeType.RouteNode) return;
19661
19828
  const route = attrs;
19662
19829
  if (route.service !== serviceName) return;
19663
19830
  entries.push({
@@ -19689,7 +19856,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
19689
19856
  return {
19690
19857
  targetNodeId: match.routeNodeId,
19691
19858
  serviceName,
19692
- edgeType: import_types68.EdgeType.CALLS
19859
+ edgeType: import_types69.EdgeType.CALLS
19693
19860
  };
19694
19861
  };
19695
19862
  }
@@ -19716,7 +19883,7 @@ init_cjs_shims();
19716
19883
 
19717
19884
  // src/connectors/cloudflare/connector.ts
19718
19885
  init_cjs_shims();
19719
- var import_types70 = require("@neat.is/types");
19886
+ var import_types71 = require("@neat.is/types");
19720
19887
 
19721
19888
  // src/connectors/cloudflare/client.ts
19722
19889
  init_cjs_shims();
@@ -19880,7 +20047,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
19880
20047
  graph.forEachNode((id, attrs) => {
19881
20048
  if (found) return;
19882
20049
  const a = attrs;
19883
- if (a.type === import_types70.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
20050
+ if (a.type === import_types71.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
19884
20051
  found = id;
19885
20052
  }
19886
20053
  });
@@ -19892,7 +20059,7 @@ function findMatchingRouteNode(graph, serviceName, method, path78) {
19892
20059
  graph.forEachNode((id, attrs) => {
19893
20060
  if (found) return;
19894
20061
  const a = attrs;
19895
- if (a.type !== import_types70.NodeType.RouteNode || a.service !== serviceName) return;
20062
+ if (a.type !== import_types71.NodeType.RouteNode || a.service !== serviceName) return;
19896
20063
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
19897
20064
  const routeMethod = (a.method ?? "").toUpperCase();
19898
20065
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -19911,11 +20078,11 @@ function createCloudflareResolveTarget(config, graph) {
19911
20078
  };
19912
20079
  const mapping = config.workers?.[scriptName];
19913
20080
  if (mapping) {
19914
- const wholeFileId = (0, import_types70.fileId)(mapping.service, mapping.entryFile);
20081
+ const wholeFileId = (0, import_types71.fileId)(mapping.service, mapping.entryFile);
19915
20082
  return {
19916
20083
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
19917
20084
  serviceName: mapping.service,
19918
- edgeType: import_types70.EdgeType.CALLS
20085
+ edgeType: import_types71.EdgeType.CALLS
19919
20086
  };
19920
20087
  }
19921
20088
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -19924,13 +20091,13 @@ function createCloudflareResolveTarget(config, graph) {
19924
20091
  return {
19925
20092
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
19926
20093
  serviceName: fileNode.service,
19927
- edgeType: import_types70.EdgeType.CALLS
20094
+ edgeType: import_types71.EdgeType.CALLS
19928
20095
  };
19929
20096
  }
19930
20097
  return {
19931
- targetNodeId: (0, import_types70.infraId)("cloudflare-worker", scriptName),
20098
+ targetNodeId: (0, import_types71.infraId)("cloudflare-worker", scriptName),
19932
20099
  serviceName: scriptName,
19933
- edgeType: import_types70.EdgeType.CALLS,
20100
+ edgeType: import_types71.EdgeType.CALLS,
19934
20101
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
19935
20102
  };
19936
20103
  };
@@ -20126,14 +20293,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
20126
20293
 
20127
20294
  // src/connectors/neon/resolve.ts
20128
20295
  init_cjs_shims();
20129
- var import_types74 = require("@neat.is/types");
20296
+ var import_types75 = require("@neat.is/types");
20130
20297
  function createNeonResolveTarget(config) {
20131
20298
  return (signal) => {
20132
20299
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
20133
20300
  return {
20134
- targetNodeId: (0, import_types74.infraId)("sql-table", signal.targetName),
20301
+ targetNodeId: (0, import_types75.infraId)("sql-table", signal.targetName),
20135
20302
  serviceName: config.serviceName,
20136
- edgeType: import_types74.EdgeType.CALLS,
20303
+ edgeType: import_types75.EdgeType.CALLS,
20137
20304
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
20138
20305
  };
20139
20306
  };
@@ -20314,14 +20481,14 @@ function mapLogEntriesToSignals2(entries) {
20314
20481
 
20315
20482
  // src/connectors/cloud-run/resolve.ts
20316
20483
  init_cjs_shims();
20317
- var import_types78 = require("@neat.is/types");
20484
+ var import_types79 = require("@neat.is/types");
20318
20485
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
20319
20486
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
20320
20487
  let found = null;
20321
20488
  graph.forEachNode((_id, attrs) => {
20322
20489
  if (found) return;
20323
20490
  const node = attrs;
20324
- if (node.type !== import_types78.NodeType.RouteNode) return;
20491
+ if (node.type !== import_types79.NodeType.RouteNode) return;
20325
20492
  const route = attrs;
20326
20493
  if (route.service !== serviceName || !route.pathTemplate) return;
20327
20494
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -20346,13 +20513,13 @@ function createCloudRunResolveTarget(graph, config) {
20346
20513
  normalizePathTemplate(path78)
20347
20514
  );
20348
20515
  if (routeNodeId) {
20349
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types78.EdgeType.CALLS };
20516
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types79.EdgeType.CALLS };
20350
20517
  }
20351
20518
  }
20352
20519
  return {
20353
- targetNodeId: (0, import_types78.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
20520
+ targetNodeId: (0, import_types79.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
20354
20521
  serviceName: mappedService ?? gcpServiceName,
20355
- edgeType: import_types78.EdgeType.CALLS,
20522
+ edgeType: import_types79.EdgeType.CALLS,
20356
20523
  ensureInfraNode: {
20357
20524
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
20358
20525
  name: gcpServiceName,
@@ -20534,14 +20701,14 @@ function mapLogEntriesToSignals3(entries) {
20534
20701
 
20535
20702
  // src/connectors/gcp-lb/resolve.ts
20536
20703
  init_cjs_shims();
20537
- var import_types82 = require("@neat.is/types");
20704
+ var import_types83 = require("@neat.is/types");
20538
20705
  var GCP_LB_BACKEND_INFRA_KIND = "gcp-lb-backend";
20539
20706
  function findMatchingRouteNode3(graph, serviceName, method, normalizedPath) {
20540
20707
  let found = null;
20541
20708
  graph.forEachNode((_id, attrs) => {
20542
20709
  if (found) return;
20543
20710
  const node = attrs;
20544
- if (node.type !== import_types82.NodeType.RouteNode) return;
20711
+ if (node.type !== import_types83.NodeType.RouteNode) return;
20545
20712
  const route = attrs;
20546
20713
  if (route.service !== serviceName || !route.pathTemplate) return;
20547
20714
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -20566,13 +20733,13 @@ function createGcpLbResolveTarget(graph, config) {
20566
20733
  normalizePathTemplate(path78)
20567
20734
  );
20568
20735
  if (routeNodeId) {
20569
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types82.EdgeType.CALLS };
20736
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types83.EdgeType.CALLS };
20570
20737
  }
20571
20738
  }
20572
20739
  return {
20573
- targetNodeId: (0, import_types82.infraId)(GCP_LB_BACKEND_INFRA_KIND, backendServiceName),
20740
+ targetNodeId: (0, import_types83.infraId)(GCP_LB_BACKEND_INFRA_KIND, backendServiceName),
20574
20741
  serviceName: mappedService ?? backendServiceName,
20575
- edgeType: import_types82.EdgeType.CALLS,
20742
+ edgeType: import_types83.EdgeType.CALLS,
20576
20743
  ensureInfraNode: {
20577
20744
  kind: GCP_LB_BACKEND_INFRA_KIND,
20578
20745
  name: backendServiceName,
@@ -20613,7 +20780,7 @@ function createGcpLbConnector(graph, config = {}) {
20613
20780
 
20614
20781
  // src/connectors/render/index.ts
20615
20782
  init_cjs_shims();
20616
- var import_types85 = require("@neat.is/types");
20783
+ var import_types86 = require("@neat.is/types");
20617
20784
 
20618
20785
  // src/connectors/render/types.ts
20619
20786
  init_cjs_shims();
@@ -20691,7 +20858,7 @@ function buildRenderRouteIndex(graph, serviceName) {
20691
20858
  const out = [];
20692
20859
  graph.forEachNode((_id, attrs) => {
20693
20860
  const node = attrs;
20694
- if (node.type !== import_types85.NodeType.RouteNode) return;
20861
+ if (node.type !== import_types86.NodeType.RouteNode) return;
20695
20862
  const route = attrs;
20696
20863
  if (route.service !== serviceName) return;
20697
20864
  out.push({
@@ -20776,7 +20943,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
20776
20943
  function createRenderResolveTarget(config) {
20777
20944
  return (signal) => {
20778
20945
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
20779
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types85.EdgeType.CALLS };
20946
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types86.EdgeType.CALLS };
20780
20947
  }
20781
20948
  return null;
20782
20949
  };
@@ -20914,21 +21081,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
20914
21081
 
20915
21082
  // src/connectors/planetscale/resolve.ts
20916
21083
  init_cjs_shims();
20917
- var import_types89 = require("@neat.is/types");
21084
+ var import_types90 = require("@neat.is/types");
20918
21085
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
20919
21086
  function createPlanetscaleResolveTarget(graph, config) {
20920
21087
  const databaseName = `${config.organization}/${config.database}`;
20921
21088
  return (signal, _ctx) => {
20922
21089
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
20923
- const tableId = (0, import_types89.infraId)("sql-table", signal.targetName);
21090
+ const tableId = (0, import_types90.infraId)("sql-table", signal.targetName);
20924
21091
  if (graph.hasNode(tableId)) {
20925
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types89.EdgeType.CALLS };
21092
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types90.EdgeType.CALLS };
20926
21093
  }
20927
- const providerId = (0, import_types89.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
21094
+ const providerId = (0, import_types90.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
20928
21095
  return {
20929
21096
  targetNodeId: providerId,
20930
21097
  serviceName: config.serviceName,
20931
- edgeType: import_types89.EdgeType.CALLS,
21098
+ edgeType: import_types90.EdgeType.CALLS,
20932
21099
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
20933
21100
  };
20934
21101
  };
@@ -21193,7 +21360,7 @@ function mapBuildsToSignals(builds, serviceName) {
21193
21360
 
21194
21361
  // src/connectors/eas/resolve.ts
21195
21362
  init_cjs_shims();
21196
- var import_types94 = require("@neat.is/types");
21363
+ var import_types95 = require("@neat.is/types");
21197
21364
  var NO_ENV2 = "unknown";
21198
21365
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
21199
21366
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -21209,8 +21376,8 @@ function configBasenamesForPhase(phase) {
21209
21376
  function configNodeService(graph, configNodeId) {
21210
21377
  for (const edgeId of graph.inboundEdges(configNodeId)) {
21211
21378
  const edge = graph.getEdgeAttributes(edgeId);
21212
- if (edge.type !== import_types94.EdgeType.CONFIGURED_BY) continue;
21213
- const parsed = (0, import_types94.parseFileId)(edge.source);
21379
+ if (edge.type !== import_types95.EdgeType.CONFIGURED_BY) continue;
21380
+ const parsed = (0, import_types95.parseFileId)(edge.source);
21214
21381
  if (parsed) return parsed.service;
21215
21382
  }
21216
21383
  return null;
@@ -21221,7 +21388,7 @@ function findConfigNode(graph, basenames, serviceName) {
21221
21388
  graph.forEachNode((id, attrs) => {
21222
21389
  if (scoped) return;
21223
21390
  const node = attrs;
21224
- if (node.type !== import_types94.NodeType.ConfigNode) return;
21391
+ if (node.type !== import_types95.NodeType.ConfigNode) return;
21225
21392
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
21226
21393
  if (anyMatch === null) anyMatch = id;
21227
21394
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -21238,13 +21405,13 @@ function createEasResolveTarget(graph) {
21238
21405
  if (basenames.length > 0) {
21239
21406
  const configNodeId = findConfigNode(graph, basenames, serviceName);
21240
21407
  if (configNodeId) {
21241
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types94.EdgeType.CALLS };
21408
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types95.EdgeType.CALLS };
21242
21409
  }
21243
21410
  }
21244
21411
  return {
21245
21412
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
21246
21413
  serviceName,
21247
- edgeType: import_types94.EdgeType.CALLS
21414
+ edgeType: import_types95.EdgeType.CALLS
21248
21415
  };
21249
21416
  };
21250
21417
  }
@@ -21831,6 +21998,7 @@ async function startConnectorPolling(input) {
21831
21998
  registration.connector,
21832
21999
  {
21833
22000
  projectDir: input.projectDir,
22001
+ project: input.project,
21834
22002
  credentials: registration.credentials,
21835
22003
  ...input.errorsPath ? { errorsPath: input.errorsPath } : {}
21836
22004
  },
@@ -22005,11 +22173,11 @@ function registerRoutes(scope, ctx) {
22005
22173
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
22006
22174
  const parsed = [];
22007
22175
  for (const c of candidates) {
22008
- const r = import_types97.DivergenceTypeSchema.safeParse(c);
22176
+ const r = import_types98.DivergenceTypeSchema.safeParse(c);
22009
22177
  if (!r.success) {
22010
22178
  return reply.code(400).send({
22011
22179
  error: `unknown divergence type "${c}"`,
22012
- allowed: import_types97.DivergenceTypeSchema.options
22180
+ allowed: import_types98.DivergenceTypeSchema.options
22013
22181
  });
22014
22182
  }
22015
22183
  parsed.push(r.data);
@@ -22126,6 +22294,7 @@ function registerRoutes(scope, ctx) {
22126
22294
  reg.connector,
22127
22295
  {
22128
22296
  projectDir: proj.scanPath ?? "",
22297
+ project: proj.name,
22129
22298
  credentials: reg.credentials,
22130
22299
  ...incidentsPath ? { errorsPath: incidentsPath } : {}
22131
22300
  },
@@ -22189,6 +22358,39 @@ function registerRoutes(scope, ctx) {
22189
22358
  if (!result) return reply.code(404).send({ error: "no root cause found", id: nodeId });
22190
22359
  return result;
22191
22360
  });
22361
+ scope.get("/graph/incident-card/:nodeId", async (req2, reply) => {
22362
+ const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
22363
+ if (!proj) return;
22364
+ const { nodeId } = req2.params;
22365
+ if (!proj.graph.hasNode(nodeId)) {
22366
+ return reply.code(404).send({ error: "node not found", id: nodeId });
22367
+ }
22368
+ const epath = errorsPathFor(proj);
22369
+ const incidents = epath ? await readErrorEvents(epath) : [];
22370
+ let errorEvent;
22371
+ if (req2.query.errorId) {
22372
+ errorEvent = incidents.find((e) => e.id === req2.query.errorId);
22373
+ if (!errorEvent) {
22374
+ return reply.code(404).send({ error: "error event not found", id: req2.query.errorId });
22375
+ }
22376
+ } else {
22377
+ const svc = nodeId.replace(/^service:/, "");
22378
+ errorEvent = [...incidents].filter((e) => e.affectedNode === nodeId || e.service === svc).sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""))[0];
22379
+ if (!errorEvent) {
22380
+ return reply.code(404).send({ error: "no incident found for node", id: nodeId });
22381
+ }
22382
+ }
22383
+ const policyPath = ctx.policyFilePathFor(proj);
22384
+ let policies = [];
22385
+ if (policyPath) {
22386
+ try {
22387
+ policies = await loadPolicyFile(policyPath);
22388
+ } catch {
22389
+ policies = [];
22390
+ }
22391
+ }
22392
+ return buildIncidentCard(proj.graph, errorEvent, incidents, policies);
22393
+ });
22192
22394
  scope.get("/graph/blast-radius/:nodeId", async (req2, reply) => {
22193
22395
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
22194
22396
  if (!proj) return;
@@ -22371,7 +22573,7 @@ function registerRoutes(scope, ctx) {
22371
22573
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
22372
22574
  let violations = await log.readAll();
22373
22575
  if (req2.query.severity) {
22374
- const sev = import_types97.PolicySeveritySchema.safeParse(req2.query.severity);
22576
+ const sev = import_types98.PolicySeveritySchema.safeParse(req2.query.severity);
22375
22577
  if (!sev.success) {
22376
22578
  return reply.code(400).send({
22377
22579
  error: "invalid severity",
@@ -22410,7 +22612,7 @@ function registerRoutes(scope, ctx) {
22410
22612
  scope.post("/policies/check", async (req2, reply) => {
22411
22613
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
22412
22614
  if (!proj) return;
22413
- const parsed = import_types97.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
22615
+ const parsed = import_types98.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
22414
22616
  if (!parsed.success) {
22415
22617
  return reply.code(400).send({
22416
22618
  error: "invalid /policies/check body",
@@ -22751,7 +22953,7 @@ function unroutedErrorsPath(neatHome4) {
22751
22953
  }
22752
22954
 
22753
22955
  // src/daemon.ts
22754
- var import_types98 = require("@neat.is/types");
22956
+ var import_types99 = require("@neat.is/types");
22755
22957
  function daemonJsonPath(scanPath) {
22756
22958
  return import_node_path75.default.join(scanPath, "neat-out", "daemon.json");
22757
22959
  }
@@ -22890,7 +23092,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
22890
23092
  if (!serviceName) return true;
22891
23093
  if (serviceNameMatchesProject(serviceName, project)) return true;
22892
23094
  return graph.someNode(
22893
- (_id, attrs) => attrs.type === import_types98.NodeType.ServiceNode && attrs.name === serviceName
23095
+ (_id, attrs) => attrs.type === import_types99.NodeType.ServiceNode && attrs.name === serviceName
22894
23096
  );
22895
23097
  }
22896
23098
  async function bootstrapProject(entry2, connectors = [], neatHome4) {
@@ -23345,7 +23547,12 @@ async function startDaemon(opts = {}) {
23345
23547
  onErrorSpanSync: async (span) => {
23346
23548
  const slot = await resolveTargetSlot(span.service, span.traceId);
23347
23549
  if (!slot) return;
23348
- await makeErrorSpanWriter(slot.paths.errorsPath, slot.graph, slot.entry.path)(span);
23550
+ await makeErrorSpanWriter(
23551
+ slot.paths.errorsPath,
23552
+ slot.graph,
23553
+ slot.entry.path,
23554
+ slot.entry.name
23555
+ )(span);
23349
23556
  },
23350
23557
  // Project-scoped route (issue #367) — the URL already named the
23351
23558
  // project. Resolution is a direct slot lookup; service.name resolves
@@ -23368,7 +23575,12 @@ async function startDaemon(opts = {}) {
23368
23575
  onProjectErrorSpanSync: async (project, span) => {
23369
23576
  const slot = await resolveSlotByName(project, span.service, span.traceId);
23370
23577
  if (!slot) return;
23371
- await makeErrorSpanWriter(slot.paths.errorsPath, slot.graph, slot.entry.path)(span);
23578
+ await makeErrorSpanWriter(
23579
+ slot.paths.errorsPath,
23580
+ slot.graph,
23581
+ slot.entry.path,
23582
+ slot.entry.name
23583
+ )(span);
23372
23584
  },
23373
23585
  // #881 — 404 a project-scoped POST for a project this daemon doesn't
23374
23586
  // host, rather than accepting it and dropping the batch. `slots` covers