@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/cli.cjs CHANGED
@@ -6366,11 +6366,25 @@ function upsertInferredEdge(graph, type, source, target, ts) {
6366
6366
  };
6367
6367
  graph.addEdgeWithKey(id, source, target, edge);
6368
6368
  }
6369
+ function emitIncidentEvent(project, ev) {
6370
+ emitNeatEvent({
6371
+ type: "incident",
6372
+ project,
6373
+ payload: {
6374
+ incidentId: ev.id,
6375
+ affectedNode: ev.affectedNode,
6376
+ service: ev.service,
6377
+ incidentKind: (0, import_types8.incidentKindOf)(ev),
6378
+ at: ev.timestamp
6379
+ }
6380
+ });
6381
+ }
6369
6382
  async function appendErrorEvent(ctx, ev) {
6370
6383
  await import_node_fs8.promises.mkdir(import_node_path9.default.dirname(ctx.errorsPath), { recursive: true });
6371
6384
  await import_node_fs8.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
6385
+ emitIncidentEvent(ctx.project ?? DEFAULT_PROJECT, ev);
6372
6386
  }
6373
- async function appendConnectorIncident(errorsPath, input) {
6387
+ async function appendConnectorIncident(errorsPath, input, project) {
6374
6388
  const ev = {
6375
6389
  id: input.id,
6376
6390
  timestamp: input.timestamp,
@@ -6384,6 +6398,7 @@ async function appendConnectorIncident(errorsPath, input) {
6384
6398
  };
6385
6399
  await import_node_fs8.promises.mkdir(import_node_path9.default.dirname(errorsPath), { recursive: true });
6386
6400
  await import_node_fs8.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
6401
+ if (project) emitIncidentEvent(project, ev);
6387
6402
  }
6388
6403
  function landIncidentCallSite(span, callSite, trusted, graph) {
6389
6404
  const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
@@ -6477,12 +6492,13 @@ function buildErrorEventForReceiver(span, graph, scanPath) {
6477
6492
  affectedNode: locus.affectedNode
6478
6493
  };
6479
6494
  }
6480
- function makeErrorSpanWriter(errorsPath, graph, scanPath) {
6495
+ function makeErrorSpanWriter(errorsPath, graph, scanPath, project = DEFAULT_PROJECT) {
6481
6496
  return async (span) => {
6482
6497
  const ev = buildErrorEventForReceiver(span, graph, scanPath);
6483
6498
  if (!ev) return;
6484
6499
  await import_node_fs8.promises.mkdir(import_node_path9.default.dirname(errorsPath), { recursive: true });
6485
6500
  await import_node_fs8.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
6501
+ emitIncidentEvent(project, ev);
6486
6502
  };
6487
6503
  }
6488
6504
  async function recordFailingResponseIncident(ctx, span, affectedNode, timestamp, statusCode, count, firstTimestamp) {
@@ -16943,7 +16959,7 @@ var import_chokidar = __toESM(require("chokidar"), 1);
16943
16959
  init_cjs_shims();
16944
16960
  var import_fastify2 = __toESM(require("fastify"), 1);
16945
16961
  var import_cors = __toESM(require("@fastify/cors"), 1);
16946
- var import_types98 = require("@neat.is/types");
16962
+ var import_types99 = require("@neat.is/types");
16947
16963
 
16948
16964
  // src/extend/index.ts
16949
16965
  init_cjs_shims();
@@ -17333,9 +17349,160 @@ function queryLogEntries(opts) {
17333
17349
  return merged;
17334
17350
  }
17335
17351
 
17336
- // src/ask.ts
17352
+ // src/goodybag.ts
17337
17353
  init_cjs_shims();
17338
17354
  var import_types60 = require("@neat.is/types");
17355
+ var CODE_FILEPATH_ATTR2 = "code.filepath";
17356
+ var CODE_LINENO_ATTR2 = "code.lineno";
17357
+ var BLAST_NEAREST_LIMIT = 5;
17358
+ function grainOf2(graph, nodeId) {
17359
+ if (graph.hasNode(nodeId)) {
17360
+ const t = graph.getNodeAttributes(nodeId).type;
17361
+ if (typeof t === "string" && t.length > 0) {
17362
+ return (t.endsWith("Node") ? t.slice(0, -4) : t).toLowerCase();
17363
+ }
17364
+ }
17365
+ const colon = nodeId.indexOf(":");
17366
+ return colon > 0 ? nodeId.slice(0, colon) : "unknown";
17367
+ }
17368
+ function locusOf(graph, ev) {
17369
+ const file = ev.attributes?.[CODE_FILEPATH_ATTR2];
17370
+ if (typeof file !== "string" || file.length === 0) return null;
17371
+ const rawLine = ev.attributes?.[CODE_LINENO_ATTR2];
17372
+ const line = typeof rawLine === "number" ? rawLine : Number(rawLine);
17373
+ const node = graph.hasNode(ev.affectedNode) ? graph.getNodeAttributes(ev.affectedNode) : void 0;
17374
+ return {
17375
+ file,
17376
+ ...Number.isFinite(line) ? { lineStart: line, lineEnd: line } : {},
17377
+ ...node?.name ? { symbol: node.name } : {},
17378
+ service: node?.service ?? ev.service,
17379
+ provenance: import_types60.Provenance.OBSERVED
17380
+ };
17381
+ }
17382
+ function locusFromNode(graph, nodeId) {
17383
+ if (!graph.hasNode(nodeId)) return null;
17384
+ const n = graph.getNodeAttributes(nodeId);
17385
+ const file = n.relPath ?? n.path;
17386
+ if (typeof file !== "string" || file.length === 0) return null;
17387
+ const start = n.span?.startLine;
17388
+ const end = n.span?.endLine;
17389
+ return {
17390
+ file,
17391
+ ...typeof start === "number" ? { lineStart: start } : {},
17392
+ ...typeof end === "number" ? { lineEnd: end } : {},
17393
+ ...n.qualname ? { symbol: shortLabel(graph, nodeId) } : {},
17394
+ ...n.service ? { service: n.service } : {},
17395
+ provenance: import_types60.Provenance.INFERRED
17396
+ };
17397
+ }
17398
+ function promoteCauseLocus(graph, causeNode, incidents) {
17399
+ const native = incidents.find(
17400
+ (e) => e.affectedNode === causeNode && typeof e.attributes?.[CODE_FILEPATH_ATTR2] === "string"
17401
+ );
17402
+ if (native) {
17403
+ const l = locusOf(graph, native);
17404
+ if (l) return { ...l, symbol: shortLabel(graph, causeNode), provenance: import_types60.Provenance.INFERRED };
17405
+ }
17406
+ return locusFromNode(graph, causeNode);
17407
+ }
17408
+ function divergenceSummary(d) {
17409
+ const column = "column" in d && d.column ? `.${d.column}` : "";
17410
+ const label = "table" in d && d.table ? `${d.table}${column}` : `${d.source} \u2192 ${d.target}`;
17411
+ return label;
17412
+ }
17413
+ function baseName(p) {
17414
+ const parts = p.split(/[\\/]/);
17415
+ return parts[parts.length - 1] || p;
17416
+ }
17417
+ function shortLabel(graph, nodeId) {
17418
+ if (graph.hasNode(nodeId)) {
17419
+ const name = graph.getNodeAttributes(nodeId).name;
17420
+ if (typeof name === "string" && name.length > 0) return name;
17421
+ }
17422
+ const afterHash = nodeId.includes("#") ? nodeId.slice(nodeId.lastIndexOf("#") + 1) : nodeId;
17423
+ return afterHash.includes(":") ? afterHash.slice(afterHash.lastIndexOf(":") + 1) : afterHash;
17424
+ }
17425
+ function renderHeadline(graph, ev, locus, causeNode) {
17426
+ const what = ev.exceptionType ? `raised ${ev.exceptionType}` : ev.errorMessage || "failed";
17427
+ const causeLabel = causeNode && causeNode !== ev.affectedNode ? shortLabel(graph, causeNode) : "";
17428
+ if (locus) {
17429
+ const base = baseName(locus.file);
17430
+ const lines = locus.lineStart != null ? locus.lineEnd && locus.lineEnd !== locus.lineStart ? `LINES ${locus.lineStart}-${locus.lineEnd}` : `LINE ${locus.lineStart}` : "";
17431
+ const symbol = locus.symbol ?? (grainOf2(graph, ev.affectedNode) === "symbol" ? shortLabel(graph, ev.affectedNode) : void 0);
17432
+ const subject = symbol ? `SYMBOL ${symbol}` : `FILE ${base}`;
17433
+ const at = symbol ? `${lines ? `${lines} in ` : ""}${base}` : lines;
17434
+ const where = at ? ` at ${at}` : "";
17435
+ const svc = locus.service ?? ev.service;
17436
+ const cause2 = causeLabel && causeLabel !== symbol ? ` \u2192 root cause ${causeLabel}` : "";
17437
+ return `${subject}${where} (SERVICE ${svc}) ${what} at ${ev.timestamp}${cause2}`;
17438
+ }
17439
+ const cause = causeLabel ? ` \u2192 root cause ${causeLabel}` : "";
17440
+ return `SERVICE ${ev.service} ${what} at ${ev.timestamp}${cause}`;
17441
+ }
17442
+ function buildIncidentCard(graph, errorEvent, incidents, policies) {
17443
+ const affected = errorEvent.affectedNode;
17444
+ let locus = locusOf(graph, errorEvent);
17445
+ const inGraph = graph.hasNode(affected);
17446
+ const rc = inGraph ? getRootCause(graph, affected, errorEvent, incidents) : null;
17447
+ let rootCause = null;
17448
+ if (rc) {
17449
+ const provs = rc.edgeProvenances ?? [];
17450
+ const chain = (rc.traversalPath ?? []).map((node, i) => ({
17451
+ node,
17452
+ grain: grainOf2(graph, node),
17453
+ provenance: provs[i] ?? provs[provs.length - 1] ?? import_types60.Provenance.INFERRED
17454
+ }));
17455
+ rootCause = {
17456
+ node: rc.rootCauseNode,
17457
+ ...rc.candidates?.[0]?.classification ? { classification: rc.candidates[0].classification } : {},
17458
+ reason: rc.rootCauseReason,
17459
+ confidence: rc.confidence,
17460
+ fix: rc.fixRecommendation ?? null,
17461
+ chain
17462
+ };
17463
+ }
17464
+ if (locus === null && rootCause) {
17465
+ locus = promoteCauseLocus(graph, rootCause.node, incidents);
17466
+ }
17467
+ const blast = inGraph ? getBlastRadius(graph, affected) : { origin: affected, affectedNodes: [], totalAffected: 0 };
17468
+ 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 }));
17469
+ const applicable = inGraph ? selectApplicablePolicies(graph, policies, affected) : [];
17470
+ const policyCards = applicable.map((p) => ({
17471
+ policyName: p.policyName,
17472
+ severity: p.severity,
17473
+ message: p.reason
17474
+ }));
17475
+ const divergences = inGraph ? computeDivergences(graph, { node: affected, incidents }).divergences.map((d) => ({
17476
+ type: d.type,
17477
+ summary: divergenceSummary(d)
17478
+ })) : [];
17479
+ const card = {
17480
+ kind: "incident",
17481
+ id: errorEvent.id,
17482
+ at: errorEvent.timestamp,
17483
+ incidentKind: (0, import_types60.incidentKindOf)(errorEvent),
17484
+ service: errorEvent.service,
17485
+ affectedNode: affected,
17486
+ message: errorEvent.errorMessage,
17487
+ ...errorEvent.exceptionType ? { exceptionType: errorEvent.exceptionType } : {},
17488
+ ...errorEvent.httpStatusCode !== void 0 ? { httpStatusCode: errorEvent.httpStatusCode } : {},
17489
+ ...errorEvent.incidentCount !== void 0 ? { count: errorEvent.incidentCount } : {},
17490
+ ...errorEvent.firstTimestamp && errorEvent.lastTimestamp ? { window: { first: errorEvent.firstTimestamp, last: errorEvent.lastTimestamp } } : {},
17491
+ ...errorEvent.traceId ? { traceId: errorEvent.traceId } : {},
17492
+ ...errorEvent.spanId ? { spanId: errorEvent.spanId } : {},
17493
+ locus,
17494
+ rootCause,
17495
+ ...nearest.length > 0 ? { blastRadius: { totalAffected: blast.totalAffected, nearest } } : {},
17496
+ ...policyCards.length > 0 ? { policies: policyCards } : {},
17497
+ ...divergences.length > 0 ? { divergence: divergences } : {},
17498
+ headline: renderHeadline(graph, errorEvent, locus, rootCause?.node ?? null)
17499
+ };
17500
+ return import_types60.IncidentCardSchema.parse(card);
17501
+ }
17502
+
17503
+ // src/ask.ts
17504
+ init_cjs_shims();
17505
+ var import_types61 = require("@neat.is/types");
17339
17506
  var DEFAULT_MAX_NODES = 3;
17340
17507
  var MAX_FACTS_PER_SECTION = 6;
17341
17508
  var INTENT_RULES = [
@@ -17563,7 +17730,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17563
17730
  };
17564
17731
  graph.forEachNode((id, attrs) => {
17565
17732
  const node = attrs;
17566
- if (node.type === import_types60.NodeType.FrontierNode) return;
17733
+ if (node.type === import_types61.NodeType.FrontierNode) return;
17567
17734
  const name = nodeName(node);
17568
17735
  const body = idBody(id);
17569
17736
  const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
@@ -17582,7 +17749,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17582
17749
  const res = await searchIndex.search(question, 10);
17583
17750
  if (res.provider !== "substring") {
17584
17751
  for (const m of res.matches) {
17585
- if (m.node.type === import_types60.NodeType.FrontierNode) continue;
17752
+ if (m.node.type === import_types61.NodeType.FrontierNode) continue;
17586
17753
  const already = best.get(m.node.id);
17587
17754
  if (!already && m.score < EMBED_MIN_SCORE) continue;
17588
17755
  consider({
@@ -17620,7 +17787,7 @@ function edgeSignalNote(e) {
17620
17787
  function buildRootCauseSection(graph, node, incidents, now) {
17621
17788
  const result = getRootCause(graph, node, void 0, incidents, { now });
17622
17789
  if (!result) return null;
17623
- const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types60.Provenance.OBSERVED;
17790
+ const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types61.Provenance.OBSERVED;
17624
17791
  const facts = [
17625
17792
  {
17626
17793
  text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
@@ -17670,7 +17837,7 @@ function buildObservedSection(graph, node) {
17670
17837
  if (result.observed && result.inboundObservedCount > 0) {
17671
17838
  facts.push({
17672
17839
  text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
17673
- provenance: import_types60.Provenance.OBSERVED
17840
+ provenance: import_types61.Provenance.OBSERVED
17674
17841
  });
17675
17842
  } else {
17676
17843
  return null;
@@ -17698,7 +17865,7 @@ function buildIncidentsSection(node, incidents) {
17698
17865
  const facts = ordered.map((ev) => ({
17699
17866
  text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
17700
17867
  // ErrorEvents are observation records — OBSERVED by definition.
17701
- provenance: import_types60.Provenance.OBSERVED
17868
+ provenance: import_types61.Provenance.OBSERVED
17702
17869
  }));
17703
17870
  return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
17704
17871
  }
@@ -17758,7 +17925,7 @@ function buildGlobalIncidentsSection(incidents) {
17758
17925
  }
17759
17926
  const byKey = /* @__PURE__ */ new Map();
17760
17927
  for (const ev of incidents) {
17761
- const key = ev.affectedNode || (0, import_types60.serviceId)(ev.service);
17928
+ const key = ev.affectedNode || (0, import_types61.serviceId)(ev.service);
17762
17929
  const cur = byKey.get(key);
17763
17930
  if (!cur) {
17764
17931
  byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
@@ -17773,7 +17940,7 @@ function buildGlobalIncidentsSection(incidents) {
17773
17940
  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);
17774
17941
  const facts = rows.map((r) => ({
17775
17942
  text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
17776
- provenance: import_types60.Provenance.OBSERVED
17943
+ provenance: import_types61.Provenance.OBSERVED
17777
17944
  }));
17778
17945
  return {
17779
17946
  heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
@@ -17786,7 +17953,7 @@ function buildOverviewSections(graph, incidents) {
17786
17953
  graph.forEachNode((_id, attrs) => {
17787
17954
  const node = attrs;
17788
17955
  nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
17789
- if (node.type === import_types60.NodeType.ServiceNode) services.push(node.id);
17956
+ if (node.type === import_types61.NodeType.ServiceNode) services.push(node.id);
17790
17957
  });
17791
17958
  const edgeByProv = /* @__PURE__ */ new Map();
17792
17959
  graph.forEachEdge((_id, attrs) => {
@@ -17798,10 +17965,10 @@ function buildOverviewSections(graph, incidents) {
17798
17965
  const shapeFacts = [
17799
17966
  { text: `${graph.order} nodes, ${graph.size} edges` },
17800
17967
  {
17801
- 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`
17968
+ text: `${count(import_types61.NodeType.ServiceNode)} services, ${count(import_types61.NodeType.FileNode)} files, ${count(import_types61.NodeType.SymbolNode)} symbols, ${count(import_types61.NodeType.DatabaseNode)} databases`
17802
17969
  }
17803
17970
  ];
17804
- for (const p of [import_types60.Provenance.EXTRACTED, import_types60.Provenance.OBSERVED, import_types60.Provenance.INFERRED, import_types60.Provenance.STALE]) {
17971
+ for (const p of [import_types61.Provenance.EXTRACTED, import_types61.Provenance.OBSERVED, import_types61.Provenance.INFERRED, import_types61.Provenance.STALE]) {
17805
17972
  const n = edgeByProv.get(p) ?? 0;
17806
17973
  if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
17807
17974
  }
@@ -17818,7 +17985,7 @@ function buildOverviewSections(graph, incidents) {
17818
17985
  if (incidents && incidents.length > 0) {
17819
17986
  const incCount = /* @__PURE__ */ new Map();
17820
17987
  for (const ev of incidents) {
17821
- const key = ev.affectedNode || (0, import_types60.serviceId)(ev.service);
17988
+ const key = ev.affectedNode || (0, import_types61.serviceId)(ev.service);
17822
17989
  incCount.set(key, (incCount.get(key) ?? 0) + 1);
17823
17990
  }
17824
17991
  const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
@@ -17826,7 +17993,7 @@ function buildOverviewSections(graph, incidents) {
17826
17993
  heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
17827
17994
  facts: top.map(([k, n]) => ({
17828
17995
  text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
17829
- provenance: import_types60.Provenance.OBSERVED
17996
+ provenance: import_types61.Provenance.OBSERVED
17830
17997
  }))
17831
17998
  });
17832
17999
  }
@@ -17962,7 +18129,7 @@ async function askGraph(graph, question, opts = {}) {
17962
18129
  const provSet = /* @__PURE__ */ new Set();
17963
18130
  for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
17964
18131
  const confidence = sections[0]?.facts[0]?.confidence;
17965
- return import_types60.AskResultSchema.parse({
18132
+ return import_types61.AskResultSchema.parse({
17966
18133
  question,
17967
18134
  intent,
17968
18135
  matched,
@@ -18109,7 +18276,7 @@ init_cjs_shims();
18109
18276
  var import_node_fs39 = require("fs");
18110
18277
  var import_node_os3 = __toESM(require("os"), 1);
18111
18278
  var import_node_path74 = __toESM(require("path"), 1);
18112
- var import_types61 = require("@neat.is/types");
18279
+ var import_types62 = require("@neat.is/types");
18113
18280
  var LOCK_TIMEOUT_MS = 5e3;
18114
18281
  var LOCK_RETRY_MS = 50;
18115
18282
  function neatHome() {
@@ -18363,10 +18530,10 @@ async function readRegistry() {
18363
18530
  throw err;
18364
18531
  }
18365
18532
  const parsed = JSON.parse(raw);
18366
- return import_types61.RegistryFileSchema.parse(parsed);
18533
+ return import_types62.RegistryFileSchema.parse(parsed);
18367
18534
  }
18368
18535
  async function writeRegistry(reg) {
18369
- const validated = import_types61.RegistryFileSchema.parse(reg);
18536
+ const validated = import_types62.RegistryFileSchema.parse(reg);
18370
18537
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
18371
18538
  }
18372
18539
  var ProjectNameCollisionError = class extends Error {
@@ -18879,15 +19046,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
18879
19046
 
18880
19047
  // src/connectors/index.ts
18881
19048
  init_cjs_shims();
18882
- var import_types62 = require("@neat.is/types");
19049
+ var import_types63 = require("@neat.is/types");
18883
19050
  var NO_ENV = "unknown";
18884
19051
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
18885
19052
  if (!graph.hasNode(targetNodeId)) return void 0;
18886
19053
  const sites = [];
18887
19054
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
18888
19055
  const edge = graph.getEdgeAttributes(edgeId);
18889
- if (edge.provenance !== import_types62.Provenance.EXTRACTED) continue;
18890
- const parsed = (0, import_types62.parseFileId)(edge.source);
19056
+ if (edge.provenance !== import_types63.Provenance.EXTRACTED) continue;
19057
+ const parsed = (0, import_types63.parseFileId)(edge.source);
18891
19058
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
18892
19059
  const site = { relPath: edge.evidence.file };
18893
19060
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -18898,7 +19065,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
18898
19065
  function routeCallSiteFor(graph, targetNodeId) {
18899
19066
  if (!graph.hasNode(targetNodeId)) return void 0;
18900
19067
  const attrs = graph.getNodeAttributes(targetNodeId);
18901
- if (attrs.type !== import_types62.NodeType.RouteNode || !attrs.path) return void 0;
19068
+ if (attrs.type !== import_types63.NodeType.RouteNode || !attrs.path) return void 0;
18902
19069
  const site = { relPath: attrs.path };
18903
19070
  if (attrs.line !== void 0) site.line = attrs.line;
18904
19071
  return site;
@@ -18928,7 +19095,7 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
18928
19095
  errorMessage: signal.incident.errorMessage,
18929
19096
  ...signal.incident.attributes ? { attributes: signal.incident.attributes } : {},
18930
19097
  affectedNode: resolved.targetNodeId
18931
- });
19098
+ }, ctx.project);
18932
19099
  continue;
18933
19100
  }
18934
19101
  if (resolved.ensureInfraNode) {
@@ -19510,23 +19677,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
19510
19677
 
19511
19678
  // src/connectors/supabase/resolve.ts
19512
19679
  init_cjs_shims();
19513
- var import_types64 = require("@neat.is/types");
19680
+ var import_types65 = require("@neat.is/types");
19514
19681
  function createSupabaseResolveTarget(graph, config) {
19515
19682
  return (signal, _ctx) => {
19516
19683
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
19517
19684
  return null;
19518
19685
  }
19519
- const subResourceId = (0, import_types64.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
19686
+ const subResourceId = (0, import_types65.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
19520
19687
  if (graph.hasNode(subResourceId)) {
19521
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
19688
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types65.EdgeType.CALLS };
19522
19689
  }
19523
- const bareResourceId = (0, import_types64.infraId)(signal.targetKind, signal.targetName);
19690
+ const bareResourceId = (0, import_types65.infraId)(signal.targetKind, signal.targetName);
19524
19691
  if (graph.hasNode(bareResourceId)) {
19525
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
19692
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types65.EdgeType.CALLS };
19526
19693
  }
19527
- const projectLevelId = (0, import_types64.infraId)("supabase", config.nodeRef);
19694
+ const projectLevelId = (0, import_types65.infraId)("supabase", config.nodeRef);
19528
19695
  if (graph.hasNode(projectLevelId)) {
19529
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
19696
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types65.EdgeType.CALLS };
19530
19697
  }
19531
19698
  return null;
19532
19699
  };
@@ -19619,7 +19786,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
19619
19786
 
19620
19787
  // src/connectors/railway/index.ts
19621
19788
  init_cjs_shims();
19622
- var import_types68 = require("@neat.is/types");
19789
+ var import_types69 = require("@neat.is/types");
19623
19790
 
19624
19791
  // src/connectors/railway/client.ts
19625
19792
  init_cjs_shims();
@@ -19770,7 +19937,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
19770
19937
  const out = [];
19771
19938
  graph.forEachNode((_id, attrs) => {
19772
19939
  const node = attrs;
19773
- if (node.type !== import_types68.NodeType.RouteNode) return;
19940
+ if (node.type !== import_types69.NodeType.RouteNode) return;
19774
19941
  const route = attrs;
19775
19942
  if (route.service !== serviceName) return;
19776
19943
  out.push({
@@ -19874,12 +20041,12 @@ function createRailwayResolveTarget(config) {
19874
20041
  const serviceName = config.serviceNameById[config.serviceId];
19875
20042
  if (!serviceName) return null;
19876
20043
  if (signal.targetKind === ROUTE_TARGET_KIND) {
19877
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types68.EdgeType.CALLS };
20044
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types69.EdgeType.CALLS };
19878
20045
  }
19879
20046
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
19880
20047
  const peerName = config.serviceNameById[signal.targetName];
19881
20048
  if (!peerName) return null;
19882
- return { targetNodeId: (0, import_types68.serviceId)(peerName), serviceName, edgeType: import_types68.EdgeType.CONNECTS_TO };
20049
+ return { targetNodeId: (0, import_types69.serviceId)(peerName), serviceName, edgeType: import_types69.EdgeType.CONNECTS_TO };
19883
20050
  }
19884
20051
  return null;
19885
20052
  };
@@ -20067,7 +20234,7 @@ function mapLogEntriesToSignals(entries) {
20067
20234
 
20068
20235
  // src/connectors/firebase/resolve.ts
20069
20236
  init_cjs_shims();
20070
- var import_types69 = require("@neat.is/types");
20237
+ var import_types70 = require("@neat.is/types");
20071
20238
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
20072
20239
  switch (resourceType) {
20073
20240
  case "cloud_function":
@@ -20082,7 +20249,7 @@ function routeEntriesFor(graph, serviceName) {
20082
20249
  const entries = [];
20083
20250
  graph.forEachNode((_id, attrs) => {
20084
20251
  const node = attrs;
20085
- if (node.type !== import_types69.NodeType.RouteNode) return;
20252
+ if (node.type !== import_types70.NodeType.RouteNode) return;
20086
20253
  const route = attrs;
20087
20254
  if (route.service !== serviceName) return;
20088
20255
  entries.push({
@@ -20114,7 +20281,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
20114
20281
  return {
20115
20282
  targetNodeId: match.routeNodeId,
20116
20283
  serviceName,
20117
- edgeType: import_types69.EdgeType.CALLS
20284
+ edgeType: import_types70.EdgeType.CALLS
20118
20285
  };
20119
20286
  };
20120
20287
  }
@@ -20141,7 +20308,7 @@ init_cjs_shims();
20141
20308
 
20142
20309
  // src/connectors/cloudflare/connector.ts
20143
20310
  init_cjs_shims();
20144
- var import_types71 = require("@neat.is/types");
20311
+ var import_types72 = require("@neat.is/types");
20145
20312
 
20146
20313
  // src/connectors/cloudflare/client.ts
20147
20314
  init_cjs_shims();
@@ -20305,7 +20472,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
20305
20472
  graph.forEachNode((id, attrs) => {
20306
20473
  if (found) return;
20307
20474
  const a = attrs;
20308
- if (a.type === import_types71.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
20475
+ if (a.type === import_types72.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
20309
20476
  found = id;
20310
20477
  }
20311
20478
  });
@@ -20317,7 +20484,7 @@ function findMatchingRouteNode(graph, serviceName, method, path94) {
20317
20484
  graph.forEachNode((id, attrs) => {
20318
20485
  if (found) return;
20319
20486
  const a = attrs;
20320
- if (a.type !== import_types71.NodeType.RouteNode || a.service !== serviceName) return;
20487
+ if (a.type !== import_types72.NodeType.RouteNode || a.service !== serviceName) return;
20321
20488
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
20322
20489
  const routeMethod = (a.method ?? "").toUpperCase();
20323
20490
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -20336,11 +20503,11 @@ function createCloudflareResolveTarget(config, graph) {
20336
20503
  };
20337
20504
  const mapping = config.workers?.[scriptName];
20338
20505
  if (mapping) {
20339
- const wholeFileId = (0, import_types71.fileId)(mapping.service, mapping.entryFile);
20506
+ const wholeFileId = (0, import_types72.fileId)(mapping.service, mapping.entryFile);
20340
20507
  return {
20341
20508
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
20342
20509
  serviceName: mapping.service,
20343
- edgeType: import_types71.EdgeType.CALLS
20510
+ edgeType: import_types72.EdgeType.CALLS
20344
20511
  };
20345
20512
  }
20346
20513
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -20349,13 +20516,13 @@ function createCloudflareResolveTarget(config, graph) {
20349
20516
  return {
20350
20517
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
20351
20518
  serviceName: fileNode.service,
20352
- edgeType: import_types71.EdgeType.CALLS
20519
+ edgeType: import_types72.EdgeType.CALLS
20353
20520
  };
20354
20521
  }
20355
20522
  return {
20356
- targetNodeId: (0, import_types71.infraId)("cloudflare-worker", scriptName),
20523
+ targetNodeId: (0, import_types72.infraId)("cloudflare-worker", scriptName),
20357
20524
  serviceName: scriptName,
20358
- edgeType: import_types71.EdgeType.CALLS,
20525
+ edgeType: import_types72.EdgeType.CALLS,
20359
20526
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
20360
20527
  };
20361
20528
  };
@@ -20551,14 +20718,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
20551
20718
 
20552
20719
  // src/connectors/neon/resolve.ts
20553
20720
  init_cjs_shims();
20554
- var import_types75 = require("@neat.is/types");
20721
+ var import_types76 = require("@neat.is/types");
20555
20722
  function createNeonResolveTarget(config) {
20556
20723
  return (signal) => {
20557
20724
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
20558
20725
  return {
20559
- targetNodeId: (0, import_types75.infraId)("sql-table", signal.targetName),
20726
+ targetNodeId: (0, import_types76.infraId)("sql-table", signal.targetName),
20560
20727
  serviceName: config.serviceName,
20561
- edgeType: import_types75.EdgeType.CALLS,
20728
+ edgeType: import_types76.EdgeType.CALLS,
20562
20729
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
20563
20730
  };
20564
20731
  };
@@ -20739,14 +20906,14 @@ function mapLogEntriesToSignals2(entries) {
20739
20906
 
20740
20907
  // src/connectors/cloud-run/resolve.ts
20741
20908
  init_cjs_shims();
20742
- var import_types79 = require("@neat.is/types");
20909
+ var import_types80 = require("@neat.is/types");
20743
20910
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
20744
20911
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
20745
20912
  let found = null;
20746
20913
  graph.forEachNode((_id, attrs) => {
20747
20914
  if (found) return;
20748
20915
  const node = attrs;
20749
- if (node.type !== import_types79.NodeType.RouteNode) return;
20916
+ if (node.type !== import_types80.NodeType.RouteNode) return;
20750
20917
  const route = attrs;
20751
20918
  if (route.service !== serviceName || !route.pathTemplate) return;
20752
20919
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -20771,13 +20938,13 @@ function createCloudRunResolveTarget(graph, config) {
20771
20938
  normalizePathTemplate(path94)
20772
20939
  );
20773
20940
  if (routeNodeId) {
20774
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types79.EdgeType.CALLS };
20941
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types80.EdgeType.CALLS };
20775
20942
  }
20776
20943
  }
20777
20944
  return {
20778
- targetNodeId: (0, import_types79.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
20945
+ targetNodeId: (0, import_types80.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
20779
20946
  serviceName: mappedService ?? gcpServiceName,
20780
- edgeType: import_types79.EdgeType.CALLS,
20947
+ edgeType: import_types80.EdgeType.CALLS,
20781
20948
  ensureInfraNode: {
20782
20949
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
20783
20950
  name: gcpServiceName,
@@ -20959,14 +21126,14 @@ function mapLogEntriesToSignals3(entries) {
20959
21126
 
20960
21127
  // src/connectors/gcp-lb/resolve.ts
20961
21128
  init_cjs_shims();
20962
- var import_types83 = require("@neat.is/types");
21129
+ var import_types84 = require("@neat.is/types");
20963
21130
  var GCP_LB_BACKEND_INFRA_KIND = "gcp-lb-backend";
20964
21131
  function findMatchingRouteNode3(graph, serviceName, method, normalizedPath) {
20965
21132
  let found = null;
20966
21133
  graph.forEachNode((_id, attrs) => {
20967
21134
  if (found) return;
20968
21135
  const node = attrs;
20969
- if (node.type !== import_types83.NodeType.RouteNode) return;
21136
+ if (node.type !== import_types84.NodeType.RouteNode) return;
20970
21137
  const route = attrs;
20971
21138
  if (route.service !== serviceName || !route.pathTemplate) return;
20972
21139
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -20991,13 +21158,13 @@ function createGcpLbResolveTarget(graph, config) {
20991
21158
  normalizePathTemplate(path94)
20992
21159
  );
20993
21160
  if (routeNodeId) {
20994
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types83.EdgeType.CALLS };
21161
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types84.EdgeType.CALLS };
20995
21162
  }
20996
21163
  }
20997
21164
  return {
20998
- targetNodeId: (0, import_types83.infraId)(GCP_LB_BACKEND_INFRA_KIND, backendServiceName),
21165
+ targetNodeId: (0, import_types84.infraId)(GCP_LB_BACKEND_INFRA_KIND, backendServiceName),
20999
21166
  serviceName: mappedService ?? backendServiceName,
21000
- edgeType: import_types83.EdgeType.CALLS,
21167
+ edgeType: import_types84.EdgeType.CALLS,
21001
21168
  ensureInfraNode: {
21002
21169
  kind: GCP_LB_BACKEND_INFRA_KIND,
21003
21170
  name: backendServiceName,
@@ -21038,7 +21205,7 @@ function createGcpLbConnector(graph, config = {}) {
21038
21205
 
21039
21206
  // src/connectors/render/index.ts
21040
21207
  init_cjs_shims();
21041
- var import_types86 = require("@neat.is/types");
21208
+ var import_types87 = require("@neat.is/types");
21042
21209
 
21043
21210
  // src/connectors/render/types.ts
21044
21211
  init_cjs_shims();
@@ -21116,7 +21283,7 @@ function buildRenderRouteIndex(graph, serviceName) {
21116
21283
  const out = [];
21117
21284
  graph.forEachNode((_id, attrs) => {
21118
21285
  const node = attrs;
21119
- if (node.type !== import_types86.NodeType.RouteNode) return;
21286
+ if (node.type !== import_types87.NodeType.RouteNode) return;
21120
21287
  const route = attrs;
21121
21288
  if (route.service !== serviceName) return;
21122
21289
  out.push({
@@ -21201,7 +21368,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
21201
21368
  function createRenderResolveTarget(config) {
21202
21369
  return (signal) => {
21203
21370
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
21204
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types86.EdgeType.CALLS };
21371
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types87.EdgeType.CALLS };
21205
21372
  }
21206
21373
  return null;
21207
21374
  };
@@ -21339,21 +21506,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
21339
21506
 
21340
21507
  // src/connectors/planetscale/resolve.ts
21341
21508
  init_cjs_shims();
21342
- var import_types90 = require("@neat.is/types");
21509
+ var import_types91 = require("@neat.is/types");
21343
21510
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
21344
21511
  function createPlanetscaleResolveTarget(graph, config) {
21345
21512
  const databaseName = `${config.organization}/${config.database}`;
21346
21513
  return (signal, _ctx) => {
21347
21514
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
21348
- const tableId = (0, import_types90.infraId)("sql-table", signal.targetName);
21515
+ const tableId = (0, import_types91.infraId)("sql-table", signal.targetName);
21349
21516
  if (graph.hasNode(tableId)) {
21350
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types90.EdgeType.CALLS };
21517
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types91.EdgeType.CALLS };
21351
21518
  }
21352
- const providerId = (0, import_types90.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
21519
+ const providerId = (0, import_types91.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
21353
21520
  return {
21354
21521
  targetNodeId: providerId,
21355
21522
  serviceName: config.serviceName,
21356
- edgeType: import_types90.EdgeType.CALLS,
21523
+ edgeType: import_types91.EdgeType.CALLS,
21357
21524
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
21358
21525
  };
21359
21526
  };
@@ -21618,7 +21785,7 @@ function mapBuildsToSignals(builds, serviceName) {
21618
21785
 
21619
21786
  // src/connectors/eas/resolve.ts
21620
21787
  init_cjs_shims();
21621
- var import_types95 = require("@neat.is/types");
21788
+ var import_types96 = require("@neat.is/types");
21622
21789
  var NO_ENV2 = "unknown";
21623
21790
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
21624
21791
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -21634,8 +21801,8 @@ function configBasenamesForPhase(phase) {
21634
21801
  function configNodeService(graph, configNodeId) {
21635
21802
  for (const edgeId of graph.inboundEdges(configNodeId)) {
21636
21803
  const edge = graph.getEdgeAttributes(edgeId);
21637
- if (edge.type !== import_types95.EdgeType.CONFIGURED_BY) continue;
21638
- const parsed = (0, import_types95.parseFileId)(edge.source);
21804
+ if (edge.type !== import_types96.EdgeType.CONFIGURED_BY) continue;
21805
+ const parsed = (0, import_types96.parseFileId)(edge.source);
21639
21806
  if (parsed) return parsed.service;
21640
21807
  }
21641
21808
  return null;
@@ -21646,7 +21813,7 @@ function findConfigNode(graph, basenames, serviceName) {
21646
21813
  graph.forEachNode((id, attrs) => {
21647
21814
  if (scoped) return;
21648
21815
  const node = attrs;
21649
- if (node.type !== import_types95.NodeType.ConfigNode) return;
21816
+ if (node.type !== import_types96.NodeType.ConfigNode) return;
21650
21817
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
21651
21818
  if (anyMatch === null) anyMatch = id;
21652
21819
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -21663,13 +21830,13 @@ function createEasResolveTarget(graph) {
21663
21830
  if (basenames.length > 0) {
21664
21831
  const configNodeId = findConfigNode(graph, basenames, serviceName);
21665
21832
  if (configNodeId) {
21666
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types95.EdgeType.CALLS };
21833
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types96.EdgeType.CALLS };
21667
21834
  }
21668
21835
  }
21669
21836
  return {
21670
21837
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
21671
21838
  serviceName,
21672
- edgeType: import_types95.EdgeType.CALLS
21839
+ edgeType: import_types96.EdgeType.CALLS
21673
21840
  };
21674
21841
  };
21675
21842
  }
@@ -22287,6 +22454,7 @@ async function startConnectorPolling(input) {
22287
22454
  registration.connector,
22288
22455
  {
22289
22456
  projectDir: input.projectDir,
22457
+ project: input.project,
22290
22458
  credentials: registration.credentials,
22291
22459
  ...input.errorsPath ? { errorsPath: input.errorsPath } : {}
22292
22460
  },
@@ -22509,11 +22677,11 @@ function registerRoutes(scope, ctx) {
22509
22677
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
22510
22678
  const parsed = [];
22511
22679
  for (const c of candidates) {
22512
- const r = import_types98.DivergenceTypeSchema.safeParse(c);
22680
+ const r = import_types99.DivergenceTypeSchema.safeParse(c);
22513
22681
  if (!r.success) {
22514
22682
  return reply.code(400).send({
22515
22683
  error: `unknown divergence type "${c}"`,
22516
- allowed: import_types98.DivergenceTypeSchema.options
22684
+ allowed: import_types99.DivergenceTypeSchema.options
22517
22685
  });
22518
22686
  }
22519
22687
  parsed.push(r.data);
@@ -22630,6 +22798,7 @@ function registerRoutes(scope, ctx) {
22630
22798
  reg.connector,
22631
22799
  {
22632
22800
  projectDir: proj.scanPath ?? "",
22801
+ project: proj.name,
22633
22802
  credentials: reg.credentials,
22634
22803
  ...incidentsPath ? { errorsPath: incidentsPath } : {}
22635
22804
  },
@@ -22693,6 +22862,39 @@ function registerRoutes(scope, ctx) {
22693
22862
  if (!result) return reply.code(404).send({ error: "no root cause found", id: nodeId });
22694
22863
  return result;
22695
22864
  });
22865
+ scope.get("/graph/incident-card/:nodeId", async (req, reply) => {
22866
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
22867
+ if (!proj) return;
22868
+ const { nodeId } = req.params;
22869
+ if (!proj.graph.hasNode(nodeId)) {
22870
+ return reply.code(404).send({ error: "node not found", id: nodeId });
22871
+ }
22872
+ const epath = errorsPathFor(proj);
22873
+ const incidents = epath ? await readErrorEvents(epath) : [];
22874
+ let errorEvent;
22875
+ if (req.query.errorId) {
22876
+ errorEvent = incidents.find((e) => e.id === req.query.errorId);
22877
+ if (!errorEvent) {
22878
+ return reply.code(404).send({ error: "error event not found", id: req.query.errorId });
22879
+ }
22880
+ } else {
22881
+ const svc = nodeId.replace(/^service:/, "");
22882
+ errorEvent = [...incidents].filter((e) => e.affectedNode === nodeId || e.service === svc).sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""))[0];
22883
+ if (!errorEvent) {
22884
+ return reply.code(404).send({ error: "no incident found for node", id: nodeId });
22885
+ }
22886
+ }
22887
+ const policyPath = ctx.policyFilePathFor(proj);
22888
+ let policies = [];
22889
+ if (policyPath) {
22890
+ try {
22891
+ policies = await loadPolicyFile(policyPath);
22892
+ } catch {
22893
+ policies = [];
22894
+ }
22895
+ }
22896
+ return buildIncidentCard(proj.graph, errorEvent, incidents, policies);
22897
+ });
22696
22898
  scope.get("/graph/blast-radius/:nodeId", async (req, reply) => {
22697
22899
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
22698
22900
  if (!proj) return;
@@ -22875,7 +23077,7 @@ function registerRoutes(scope, ctx) {
22875
23077
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
22876
23078
  let violations = await log.readAll();
22877
23079
  if (req.query.severity) {
22878
- const sev = import_types98.PolicySeveritySchema.safeParse(req.query.severity);
23080
+ const sev = import_types99.PolicySeveritySchema.safeParse(req.query.severity);
22879
23081
  if (!sev.success) {
22880
23082
  return reply.code(400).send({
22881
23083
  error: "invalid severity",
@@ -22914,7 +23116,7 @@ function registerRoutes(scope, ctx) {
22914
23116
  scope.post("/policies/check", async (req, reply) => {
22915
23117
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
22916
23118
  if (!proj) return;
22917
- const parsed = import_types98.PoliciesCheckBodySchema.safeParse(req.body ?? {});
23119
+ const parsed = import_types99.PoliciesCheckBodySchema.safeParse(req.body ?? {});
22918
23120
  if (!parsed.success) {
22919
23121
  return reply.code(400).send({
22920
23122
  error: "invalid /policies/check body",
@@ -23247,7 +23449,7 @@ var import_node_fs41 = require("fs");
23247
23449
  var import_node_path76 = __toESM(require("path"), 1);
23248
23450
 
23249
23451
  // src/daemon.ts
23250
- var import_types99 = require("@neat.is/types");
23452
+ var import_types100 = require("@neat.is/types");
23251
23453
  function daemonJsonPath(scanPath) {
23252
23454
  return import_node_path77.default.join(scanPath, "neat-out", "daemon.json");
23253
23455
  }
@@ -23957,7 +24159,7 @@ async function startWatch(graph, opts) {
23957
24159
  writeErrorEventInline: false,
23958
24160
  onPolicyTrigger
23959
24161
  });
23960
- const onErrorSpanSync = makeErrorSpanWriter(opts.errorsPath, graph, opts.scanPath);
24162
+ const onErrorSpanSync = makeErrorSpanWriter(opts.errorsPath, graph, opts.scanPath, projectName);
23961
24163
  const otelHttp = await buildOtelReceiver({ onSpan, onErrorSpanSync });
23962
24164
  const otelAddress = await listenSteppingOtlp(otelHttp, otelPort, host);
23963
24165
  const boundOtelPort = portFromListenAddress(otelAddress, otelPort);
@@ -28264,7 +28466,7 @@ var import_node_path87 = __toESM(require("path"), 1);
28264
28466
 
28265
28467
  // src/cli-client.ts
28266
28468
  init_cjs_shims();
28267
- var import_types100 = require("@neat.is/types");
28469
+ var import_types101 = require("@neat.is/types");
28268
28470
  var HttpError = class extends Error {
28269
28471
  constructor(status2, message, responseBody = "") {
28270
28472
  super(message);
@@ -28406,7 +28608,7 @@ async function runBlastRadius(client, input) {
28406
28608
  }
28407
28609
  }
28408
28610
  function formatBlastEntry(n) {
28409
- const tag = n.edgeProvenance === import_types100.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
28611
+ const tag = n.edgeProvenance === import_types101.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
28410
28612
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
28411
28613
  }
28412
28614
  async function runDependencies(client, input) {
@@ -28463,7 +28665,7 @@ async function runObservedDependencies(client, input) {
28463
28665
  if (result.observed) {
28464
28666
  return {
28465
28667
  summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
28466
- provenance: import_types100.Provenance.OBSERVED
28668
+ provenance: import_types101.Provenance.OBSERVED
28467
28669
  };
28468
28670
  }
28469
28671
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -28473,7 +28675,7 @@ async function runObservedDependencies(client, input) {
28473
28675
  return {
28474
28676
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
28475
28677
  block: blockLines.join("\n"),
28476
- provenance: import_types100.Provenance.OBSERVED
28678
+ provenance: import_types101.Provenance.OBSERVED
28477
28679
  };
28478
28680
  } catch (err) {
28479
28681
  if (err instanceof HttpError && err.status === 404) {
@@ -28527,7 +28729,7 @@ async function runIncidents(client, input) {
28527
28729
  return {
28528
28730
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
28529
28731
  block: blockLines.join("\n"),
28530
- provenance: import_types100.Provenance.OBSERVED
28732
+ provenance: import_types101.Provenance.OBSERVED
28531
28733
  };
28532
28734
  } catch (err) {
28533
28735
  if (err instanceof HttpError && err.status === 404) {
@@ -28636,7 +28838,7 @@ async function runStaleEdges(client, input) {
28636
28838
  return {
28637
28839
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
28638
28840
  block: blockLines.join("\n"),
28639
- provenance: import_types100.Provenance.STALE
28841
+ provenance: import_types101.Provenance.STALE
28640
28842
  };
28641
28843
  }
28642
28844
  async function runPolicies(client, input) {
@@ -29927,12 +30129,12 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
29927
30129
 
29928
30130
  // src/monitor.ts
29929
30131
  init_cjs_shims();
29930
- var import_types101 = require("@neat.is/types");
30132
+ var import_types102 = require("@neat.is/types");
29931
30133
  var OBSERVED_DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
29932
- import_types101.EdgeType.CALLS,
29933
- import_types101.EdgeType.CONNECTS_TO,
29934
- import_types101.EdgeType.PUBLISHES_TO,
29935
- import_types101.EdgeType.CONSUMES_FROM
30134
+ import_types102.EdgeType.CALLS,
30135
+ import_types102.EdgeType.CONNECTS_TO,
30136
+ import_types102.EdgeType.PUBLISHES_TO,
30137
+ import_types102.EdgeType.CONSUMES_FROM
29936
30138
  ]);
29937
30139
  function divergenceKey(d) {
29938
30140
  const column = "column" in d && d.column ? d.column : "";
@@ -29993,7 +30195,7 @@ function formatDivergenceLine2(d) {
29993
30195
  }
29994
30196
  }
29995
30197
  function formatStaleLine(edgeId) {
29996
- const parsed = (0, import_types101.parseEdgeId)(edgeId);
30198
+ const parsed = (0, import_types102.parseEdgeId)(edgeId);
29997
30199
  if (parsed) {
29998
30200
  return `\u22EF stale ${parsed.source} \u2192 ${parsed.target} (observed edge went quiet)`;
29999
30201
  }
@@ -30006,7 +30208,7 @@ function divergenceJson(d) {
30006
30208
  return JSON.stringify({ kind: "divergence", ...d });
30007
30209
  }
30008
30210
  function staleJson(edgeId) {
30009
- const parsed = (0, import_types101.parseEdgeId)(edgeId);
30211
+ const parsed = (0, import_types102.parseEdgeId)(edgeId);
30010
30212
  return JSON.stringify({
30011
30213
  kind: "stale",
30012
30214
  edgeId,
@@ -30041,6 +30243,19 @@ function formatPolicyLine(v) {
30041
30243
  function policyJson(v) {
30042
30244
  return JSON.stringify({ kind: "policy", ...v });
30043
30245
  }
30246
+ function formatIncidentLine(card) {
30247
+ let tag = "";
30248
+ if (card.rootCause) {
30249
+ const rc = card.rootCause;
30250
+ const provs = rc.chain.map((h) => h.provenance).join("\xB7");
30251
+ const cls = rc.classification ? `${rc.classification} ` : "";
30252
+ tag = ` \xB7 ${cls}${rc.confidence.toFixed(2)}${provs ? ` [${provs}]` : ""}`;
30253
+ }
30254
+ return `\u2716 incident [${card.incidentKind}] ${card.headline}${tag}`;
30255
+ }
30256
+ function incidentJson(card) {
30257
+ return JSON.stringify(card);
30258
+ }
30044
30259
  var MonitorEmitter = class {
30045
30260
  constructor(opts) {
30046
30261
  this.opts = opts;
@@ -30076,7 +30291,7 @@ var MonitorEmitter = class {
30076
30291
  // ignores non-OBSERVED edges and non-dependency edge types (structural
30077
30292
  // ownership), so only real runtime dependencies reach stdout.
30078
30293
  emitObservedEdge(edge) {
30079
- if (edge.provenance !== import_types101.Provenance.OBSERVED) return false;
30294
+ if (edge.provenance !== import_types102.Provenance.OBSERVED) return false;
30080
30295
  if (!OBSERVED_DEP_EDGE_TYPES.has(edge.type)) return false;
30081
30296
  const key = `edge|${edge.id}`;
30082
30297
  if (this.seen.has(key)) return false;
@@ -30098,6 +30313,17 @@ var MonitorEmitter = class {
30098
30313
  }
30099
30314
  return emitted;
30100
30315
  }
30316
+ // Emit one incident card once, keyed on the incident id. Unlike the other
30317
+ // facts this is event-driven, not a re-read of derivable state, so there is no
30318
+ // baseline dump on connect — only incidents that arrive live after the monitor
30319
+ // is watching reach stdout.
30320
+ emitIncident(card) {
30321
+ const key = `incident|${card.id}`;
30322
+ if (this.seen.has(key)) return false;
30323
+ this.seen.add(key);
30324
+ this.out(this.opts.json ? incidentJson(card) : formatIncidentLine(card));
30325
+ return true;
30326
+ }
30101
30327
  };
30102
30328
  function parseFrame(raw) {
30103
30329
  let event = "message";
@@ -30219,6 +30445,18 @@ async function runMonitor(opts) {
30219
30445
  const result = await client.get(policiesPath);
30220
30446
  emitter.emitPolicies(result);
30221
30447
  }, debounceMs);
30448
+ const readIncidentCard = async (incidentId, affectedNode) => {
30449
+ try {
30450
+ const card = await client.get(
30451
+ projectPath2(
30452
+ opts.project,
30453
+ `/graph/incident-card/${encodeURIComponent(affectedNode)}?errorId=${encodeURIComponent(incidentId)}`
30454
+ )
30455
+ );
30456
+ emitter.emitIncident(card);
30457
+ } catch {
30458
+ }
30459
+ };
30222
30460
  const onFrame = (frame) => {
30223
30461
  switch (frame.event) {
30224
30462
  case "extraction-complete":
@@ -30234,7 +30472,7 @@ async function runMonitor(opts) {
30234
30472
  case "edge-added": {
30235
30473
  const payload = safeParse(frame.data);
30236
30474
  const edge = payload?.edge;
30237
- if (edge && edge.provenance === import_types101.Provenance.OBSERVED) {
30475
+ if (edge && edge.provenance === import_types102.Provenance.OBSERVED) {
30238
30476
  emitter.emitObservedEdge(edge);
30239
30477
  divergences.schedule();
30240
30478
  }
@@ -30243,6 +30481,13 @@ async function runMonitor(opts) {
30243
30481
  case "policy-violation":
30244
30482
  policies.schedule();
30245
30483
  break;
30484
+ case "incident": {
30485
+ const payload = safeParse(frame.data);
30486
+ const incidentId = payload && typeof payload.incidentId === "string" ? payload.incidentId : void 0;
30487
+ const affectedNode = payload && typeof payload.affectedNode === "string" ? payload.affectedNode : void 0;
30488
+ if (incidentId && affectedNode) void readIncidentCard(incidentId, affectedNode);
30489
+ break;
30490
+ }
30246
30491
  default:
30247
30492
  break;
30248
30493
  }
@@ -30477,7 +30722,7 @@ async function runSync(opts) {
30477
30722
  }
30478
30723
 
30479
30724
  // src/cli.ts
30480
- var import_types102 = require("@neat.is/types");
30725
+ var import_types103 = require("@neat.is/types");
30481
30726
  function isNpxInvocation() {
30482
30727
  if (process.env.npm_command === "exec") return true;
30483
30728
  const execpath = process.env.npm_execpath ?? "";
@@ -31561,10 +31806,10 @@ async function runQueryVerb(cmd, parsed) {
31561
31806
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
31562
31807
  const out = [];
31563
31808
  for (const p of parts) {
31564
- const r = import_types102.DivergenceTypeSchema.safeParse(p);
31809
+ const r = import_types103.DivergenceTypeSchema.safeParse(p);
31565
31810
  if (!r.success) {
31566
31811
  console.error(
31567
- `neat divergences: unknown --type "${p}". allowed: ${import_types102.DivergenceTypeSchema.options.join(", ")}`
31812
+ `neat divergences: unknown --type "${p}". allowed: ${import_types103.DivergenceTypeSchema.options.join(", ")}`
31568
31813
  );
31569
31814
  return 2;
31570
31815
  }