@neat.is/core 0.9.8-dev.20260828 → 0.9.8

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,128 @@ 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 divergenceSummary(d) {
17383
+ const column = "column" in d && d.column ? `.${d.column}` : "";
17384
+ const label = "table" in d && d.table ? `${d.table}${column}` : `${d.source} \u2192 ${d.target}`;
17385
+ return label;
17386
+ }
17387
+ function baseName(p) {
17388
+ const parts = p.split(/[\\/]/);
17389
+ return parts[parts.length - 1] || p;
17390
+ }
17391
+ function shortLabel(graph, nodeId) {
17392
+ if (graph.hasNode(nodeId)) {
17393
+ const name = graph.getNodeAttributes(nodeId).name;
17394
+ if (typeof name === "string" && name.length > 0) return name;
17395
+ }
17396
+ const afterHash = nodeId.includes("#") ? nodeId.slice(nodeId.lastIndexOf("#") + 1) : nodeId;
17397
+ return afterHash.includes(":") ? afterHash.slice(afterHash.lastIndexOf(":") + 1) : afterHash;
17398
+ }
17399
+ function renderHeadline(graph, ev, locus, causeNode) {
17400
+ const what = ev.exceptionType ? `raised ${ev.exceptionType}` : ev.errorMessage || "failed";
17401
+ const cause = causeNode && causeNode !== ev.affectedNode ? ` \u2192 root cause ${shortLabel(graph, causeNode)}` : "";
17402
+ if (locus) {
17403
+ const base = baseName(locus.file);
17404
+ const lines = locus.lineStart != null ? locus.lineEnd && locus.lineEnd !== locus.lineStart ? `LINES ${locus.lineStart}-${locus.lineEnd}` : `LINE ${locus.lineStart}` : "";
17405
+ const symbol = locus.symbol ?? (grainOf2(graph, ev.affectedNode) === "symbol" ? shortLabel(graph, ev.affectedNode) : void 0);
17406
+ const subject = symbol ? `SYMBOL ${symbol}` : `FILE ${base}`;
17407
+ const at = symbol ? `${lines ? `${lines} in ` : ""}${base}` : lines;
17408
+ const where = at ? ` at ${at}` : "";
17409
+ return `${subject}${where} (SERVICE ${ev.service}) ${what} at ${ev.timestamp}${cause}`;
17410
+ }
17411
+ return `SERVICE ${ev.service} ${what} at ${ev.timestamp}${cause}`;
17412
+ }
17413
+ function buildIncidentCard(graph, errorEvent, incidents, policies) {
17414
+ const affected = errorEvent.affectedNode;
17415
+ const locus = locusOf(graph, errorEvent);
17416
+ const inGraph = graph.hasNode(affected);
17417
+ const rc = inGraph ? getRootCause(graph, affected, errorEvent, incidents) : null;
17418
+ let rootCause = null;
17419
+ if (rc) {
17420
+ const provs = rc.edgeProvenances ?? [];
17421
+ const chain = (rc.traversalPath ?? []).map((node, i) => ({
17422
+ node,
17423
+ grain: grainOf2(graph, node),
17424
+ provenance: provs[i] ?? provs[provs.length - 1] ?? import_types60.Provenance.INFERRED
17425
+ }));
17426
+ rootCause = {
17427
+ node: rc.rootCauseNode,
17428
+ ...rc.candidates?.[0]?.classification ? { classification: rc.candidates[0].classification } : {},
17429
+ reason: rc.rootCauseReason,
17430
+ confidence: rc.confidence,
17431
+ fix: rc.fixRecommendation ?? null,
17432
+ chain
17433
+ };
17434
+ }
17435
+ const blast = inGraph ? getBlastRadius(graph, affected) : { origin: affected, affectedNodes: [], totalAffected: 0 };
17436
+ 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 }));
17437
+ const applicable = inGraph ? selectApplicablePolicies(graph, policies, affected) : [];
17438
+ const policyCards = applicable.map((p) => ({
17439
+ policyName: p.policyName,
17440
+ severity: p.severity,
17441
+ message: p.reason
17442
+ }));
17443
+ const divergences = inGraph ? computeDivergences(graph, { node: affected, incidents }).divergences.map((d) => ({
17444
+ type: d.type,
17445
+ summary: divergenceSummary(d)
17446
+ })) : [];
17447
+ const card = {
17448
+ kind: "incident",
17449
+ id: errorEvent.id,
17450
+ at: errorEvent.timestamp,
17451
+ incidentKind: (0, import_types60.incidentKindOf)(errorEvent),
17452
+ service: errorEvent.service,
17453
+ affectedNode: affected,
17454
+ message: errorEvent.errorMessage,
17455
+ ...errorEvent.exceptionType ? { exceptionType: errorEvent.exceptionType } : {},
17456
+ ...errorEvent.httpStatusCode !== void 0 ? { httpStatusCode: errorEvent.httpStatusCode } : {},
17457
+ ...errorEvent.incidentCount !== void 0 ? { count: errorEvent.incidentCount } : {},
17458
+ ...errorEvent.firstTimestamp && errorEvent.lastTimestamp ? { window: { first: errorEvent.firstTimestamp, last: errorEvent.lastTimestamp } } : {},
17459
+ ...errorEvent.traceId ? { traceId: errorEvent.traceId } : {},
17460
+ ...errorEvent.spanId ? { spanId: errorEvent.spanId } : {},
17461
+ locus,
17462
+ rootCause,
17463
+ ...nearest.length > 0 ? { blastRadius: { totalAffected: blast.totalAffected, nearest } } : {},
17464
+ ...policyCards.length > 0 ? { policies: policyCards } : {},
17465
+ ...divergences.length > 0 ? { divergence: divergences } : {},
17466
+ headline: renderHeadline(graph, errorEvent, locus, rootCause?.node ?? null)
17467
+ };
17468
+ return import_types60.IncidentCardSchema.parse(card);
17469
+ }
17470
+
17471
+ // src/ask.ts
17472
+ init_cjs_shims();
17473
+ var import_types61 = require("@neat.is/types");
17339
17474
  var DEFAULT_MAX_NODES = 3;
17340
17475
  var MAX_FACTS_PER_SECTION = 6;
17341
17476
  var INTENT_RULES = [
@@ -17563,7 +17698,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17563
17698
  };
17564
17699
  graph.forEachNode((id, attrs) => {
17565
17700
  const node = attrs;
17566
- if (node.type === import_types60.NodeType.FrontierNode) return;
17701
+ if (node.type === import_types61.NodeType.FrontierNode) return;
17567
17702
  const name = nodeName(node);
17568
17703
  const body = idBody(id);
17569
17704
  const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
@@ -17582,7 +17717,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17582
17717
  const res = await searchIndex.search(question, 10);
17583
17718
  if (res.provider !== "substring") {
17584
17719
  for (const m of res.matches) {
17585
- if (m.node.type === import_types60.NodeType.FrontierNode) continue;
17720
+ if (m.node.type === import_types61.NodeType.FrontierNode) continue;
17586
17721
  const already = best.get(m.node.id);
17587
17722
  if (!already && m.score < EMBED_MIN_SCORE) continue;
17588
17723
  consider({
@@ -17620,7 +17755,7 @@ function edgeSignalNote(e) {
17620
17755
  function buildRootCauseSection(graph, node, incidents, now) {
17621
17756
  const result = getRootCause(graph, node, void 0, incidents, { now });
17622
17757
  if (!result) return null;
17623
- const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types60.Provenance.OBSERVED;
17758
+ const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types61.Provenance.OBSERVED;
17624
17759
  const facts = [
17625
17760
  {
17626
17761
  text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
@@ -17670,7 +17805,7 @@ function buildObservedSection(graph, node) {
17670
17805
  if (result.observed && result.inboundObservedCount > 0) {
17671
17806
  facts.push({
17672
17807
  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
17808
+ provenance: import_types61.Provenance.OBSERVED
17674
17809
  });
17675
17810
  } else {
17676
17811
  return null;
@@ -17698,7 +17833,7 @@ function buildIncidentsSection(node, incidents) {
17698
17833
  const facts = ordered.map((ev) => ({
17699
17834
  text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
17700
17835
  // ErrorEvents are observation records — OBSERVED by definition.
17701
- provenance: import_types60.Provenance.OBSERVED
17836
+ provenance: import_types61.Provenance.OBSERVED
17702
17837
  }));
17703
17838
  return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
17704
17839
  }
@@ -17758,7 +17893,7 @@ function buildGlobalIncidentsSection(incidents) {
17758
17893
  }
17759
17894
  const byKey = /* @__PURE__ */ new Map();
17760
17895
  for (const ev of incidents) {
17761
- const key = ev.affectedNode || (0, import_types60.serviceId)(ev.service);
17896
+ const key = ev.affectedNode || (0, import_types61.serviceId)(ev.service);
17762
17897
  const cur = byKey.get(key);
17763
17898
  if (!cur) {
17764
17899
  byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
@@ -17773,7 +17908,7 @@ function buildGlobalIncidentsSection(incidents) {
17773
17908
  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
17909
  const facts = rows.map((r) => ({
17775
17910
  text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
17776
- provenance: import_types60.Provenance.OBSERVED
17911
+ provenance: import_types61.Provenance.OBSERVED
17777
17912
  }));
17778
17913
  return {
17779
17914
  heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
@@ -17786,7 +17921,7 @@ function buildOverviewSections(graph, incidents) {
17786
17921
  graph.forEachNode((_id, attrs) => {
17787
17922
  const node = attrs;
17788
17923
  nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
17789
- if (node.type === import_types60.NodeType.ServiceNode) services.push(node.id);
17924
+ if (node.type === import_types61.NodeType.ServiceNode) services.push(node.id);
17790
17925
  });
17791
17926
  const edgeByProv = /* @__PURE__ */ new Map();
17792
17927
  graph.forEachEdge((_id, attrs) => {
@@ -17798,10 +17933,10 @@ function buildOverviewSections(graph, incidents) {
17798
17933
  const shapeFacts = [
17799
17934
  { text: `${graph.order} nodes, ${graph.size} edges` },
17800
17935
  {
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`
17936
+ 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
17937
  }
17803
17938
  ];
17804
- for (const p of [import_types60.Provenance.EXTRACTED, import_types60.Provenance.OBSERVED, import_types60.Provenance.INFERRED, import_types60.Provenance.STALE]) {
17939
+ for (const p of [import_types61.Provenance.EXTRACTED, import_types61.Provenance.OBSERVED, import_types61.Provenance.INFERRED, import_types61.Provenance.STALE]) {
17805
17940
  const n = edgeByProv.get(p) ?? 0;
17806
17941
  if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
17807
17942
  }
@@ -17818,7 +17953,7 @@ function buildOverviewSections(graph, incidents) {
17818
17953
  if (incidents && incidents.length > 0) {
17819
17954
  const incCount = /* @__PURE__ */ new Map();
17820
17955
  for (const ev of incidents) {
17821
- const key = ev.affectedNode || (0, import_types60.serviceId)(ev.service);
17956
+ const key = ev.affectedNode || (0, import_types61.serviceId)(ev.service);
17822
17957
  incCount.set(key, (incCount.get(key) ?? 0) + 1);
17823
17958
  }
17824
17959
  const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
@@ -17826,7 +17961,7 @@ function buildOverviewSections(graph, incidents) {
17826
17961
  heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
17827
17962
  facts: top.map(([k, n]) => ({
17828
17963
  text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
17829
- provenance: import_types60.Provenance.OBSERVED
17964
+ provenance: import_types61.Provenance.OBSERVED
17830
17965
  }))
17831
17966
  });
17832
17967
  }
@@ -17962,7 +18097,7 @@ async function askGraph(graph, question, opts = {}) {
17962
18097
  const provSet = /* @__PURE__ */ new Set();
17963
18098
  for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
17964
18099
  const confidence = sections[0]?.facts[0]?.confidence;
17965
- return import_types60.AskResultSchema.parse({
18100
+ return import_types61.AskResultSchema.parse({
17966
18101
  question,
17967
18102
  intent,
17968
18103
  matched,
@@ -18109,7 +18244,7 @@ init_cjs_shims();
18109
18244
  var import_node_fs39 = require("fs");
18110
18245
  var import_node_os3 = __toESM(require("os"), 1);
18111
18246
  var import_node_path74 = __toESM(require("path"), 1);
18112
- var import_types61 = require("@neat.is/types");
18247
+ var import_types62 = require("@neat.is/types");
18113
18248
  var LOCK_TIMEOUT_MS = 5e3;
18114
18249
  var LOCK_RETRY_MS = 50;
18115
18250
  function neatHome() {
@@ -18363,10 +18498,10 @@ async function readRegistry() {
18363
18498
  throw err;
18364
18499
  }
18365
18500
  const parsed = JSON.parse(raw);
18366
- return import_types61.RegistryFileSchema.parse(parsed);
18501
+ return import_types62.RegistryFileSchema.parse(parsed);
18367
18502
  }
18368
18503
  async function writeRegistry(reg) {
18369
- const validated = import_types61.RegistryFileSchema.parse(reg);
18504
+ const validated = import_types62.RegistryFileSchema.parse(reg);
18370
18505
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
18371
18506
  }
18372
18507
  var ProjectNameCollisionError = class extends Error {
@@ -18879,15 +19014,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
18879
19014
 
18880
19015
  // src/connectors/index.ts
18881
19016
  init_cjs_shims();
18882
- var import_types62 = require("@neat.is/types");
19017
+ var import_types63 = require("@neat.is/types");
18883
19018
  var NO_ENV = "unknown";
18884
19019
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
18885
19020
  if (!graph.hasNode(targetNodeId)) return void 0;
18886
19021
  const sites = [];
18887
19022
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
18888
19023
  const edge = graph.getEdgeAttributes(edgeId);
18889
- if (edge.provenance !== import_types62.Provenance.EXTRACTED) continue;
18890
- const parsed = (0, import_types62.parseFileId)(edge.source);
19024
+ if (edge.provenance !== import_types63.Provenance.EXTRACTED) continue;
19025
+ const parsed = (0, import_types63.parseFileId)(edge.source);
18891
19026
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
18892
19027
  const site = { relPath: edge.evidence.file };
18893
19028
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -18898,7 +19033,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
18898
19033
  function routeCallSiteFor(graph, targetNodeId) {
18899
19034
  if (!graph.hasNode(targetNodeId)) return void 0;
18900
19035
  const attrs = graph.getNodeAttributes(targetNodeId);
18901
- if (attrs.type !== import_types62.NodeType.RouteNode || !attrs.path) return void 0;
19036
+ if (attrs.type !== import_types63.NodeType.RouteNode || !attrs.path) return void 0;
18902
19037
  const site = { relPath: attrs.path };
18903
19038
  if (attrs.line !== void 0) site.line = attrs.line;
18904
19039
  return site;
@@ -18928,7 +19063,7 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
18928
19063
  errorMessage: signal.incident.errorMessage,
18929
19064
  ...signal.incident.attributes ? { attributes: signal.incident.attributes } : {},
18930
19065
  affectedNode: resolved.targetNodeId
18931
- });
19066
+ }, ctx.project);
18932
19067
  continue;
18933
19068
  }
18934
19069
  if (resolved.ensureInfraNode) {
@@ -19510,23 +19645,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
19510
19645
 
19511
19646
  // src/connectors/supabase/resolve.ts
19512
19647
  init_cjs_shims();
19513
- var import_types64 = require("@neat.is/types");
19648
+ var import_types65 = require("@neat.is/types");
19514
19649
  function createSupabaseResolveTarget(graph, config) {
19515
19650
  return (signal, _ctx) => {
19516
19651
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
19517
19652
  return null;
19518
19653
  }
19519
- const subResourceId = (0, import_types64.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
19654
+ const subResourceId = (0, import_types65.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
19520
19655
  if (graph.hasNode(subResourceId)) {
19521
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
19656
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types65.EdgeType.CALLS };
19522
19657
  }
19523
- const bareResourceId = (0, import_types64.infraId)(signal.targetKind, signal.targetName);
19658
+ const bareResourceId = (0, import_types65.infraId)(signal.targetKind, signal.targetName);
19524
19659
  if (graph.hasNode(bareResourceId)) {
19525
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
19660
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types65.EdgeType.CALLS };
19526
19661
  }
19527
- const projectLevelId = (0, import_types64.infraId)("supabase", config.nodeRef);
19662
+ const projectLevelId = (0, import_types65.infraId)("supabase", config.nodeRef);
19528
19663
  if (graph.hasNode(projectLevelId)) {
19529
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
19664
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types65.EdgeType.CALLS };
19530
19665
  }
19531
19666
  return null;
19532
19667
  };
@@ -19619,7 +19754,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
19619
19754
 
19620
19755
  // src/connectors/railway/index.ts
19621
19756
  init_cjs_shims();
19622
- var import_types68 = require("@neat.is/types");
19757
+ var import_types69 = require("@neat.is/types");
19623
19758
 
19624
19759
  // src/connectors/railway/client.ts
19625
19760
  init_cjs_shims();
@@ -19770,7 +19905,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
19770
19905
  const out = [];
19771
19906
  graph.forEachNode((_id, attrs) => {
19772
19907
  const node = attrs;
19773
- if (node.type !== import_types68.NodeType.RouteNode) return;
19908
+ if (node.type !== import_types69.NodeType.RouteNode) return;
19774
19909
  const route = attrs;
19775
19910
  if (route.service !== serviceName) return;
19776
19911
  out.push({
@@ -19874,12 +20009,12 @@ function createRailwayResolveTarget(config) {
19874
20009
  const serviceName = config.serviceNameById[config.serviceId];
19875
20010
  if (!serviceName) return null;
19876
20011
  if (signal.targetKind === ROUTE_TARGET_KIND) {
19877
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types68.EdgeType.CALLS };
20012
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types69.EdgeType.CALLS };
19878
20013
  }
19879
20014
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
19880
20015
  const peerName = config.serviceNameById[signal.targetName];
19881
20016
  if (!peerName) return null;
19882
- return { targetNodeId: (0, import_types68.serviceId)(peerName), serviceName, edgeType: import_types68.EdgeType.CONNECTS_TO };
20017
+ return { targetNodeId: (0, import_types69.serviceId)(peerName), serviceName, edgeType: import_types69.EdgeType.CONNECTS_TO };
19883
20018
  }
19884
20019
  return null;
19885
20020
  };
@@ -20067,7 +20202,7 @@ function mapLogEntriesToSignals(entries) {
20067
20202
 
20068
20203
  // src/connectors/firebase/resolve.ts
20069
20204
  init_cjs_shims();
20070
- var import_types69 = require("@neat.is/types");
20205
+ var import_types70 = require("@neat.is/types");
20071
20206
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
20072
20207
  switch (resourceType) {
20073
20208
  case "cloud_function":
@@ -20082,7 +20217,7 @@ function routeEntriesFor(graph, serviceName) {
20082
20217
  const entries = [];
20083
20218
  graph.forEachNode((_id, attrs) => {
20084
20219
  const node = attrs;
20085
- if (node.type !== import_types69.NodeType.RouteNode) return;
20220
+ if (node.type !== import_types70.NodeType.RouteNode) return;
20086
20221
  const route = attrs;
20087
20222
  if (route.service !== serviceName) return;
20088
20223
  entries.push({
@@ -20114,7 +20249,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
20114
20249
  return {
20115
20250
  targetNodeId: match.routeNodeId,
20116
20251
  serviceName,
20117
- edgeType: import_types69.EdgeType.CALLS
20252
+ edgeType: import_types70.EdgeType.CALLS
20118
20253
  };
20119
20254
  };
20120
20255
  }
@@ -20141,7 +20276,7 @@ init_cjs_shims();
20141
20276
 
20142
20277
  // src/connectors/cloudflare/connector.ts
20143
20278
  init_cjs_shims();
20144
- var import_types71 = require("@neat.is/types");
20279
+ var import_types72 = require("@neat.is/types");
20145
20280
 
20146
20281
  // src/connectors/cloudflare/client.ts
20147
20282
  init_cjs_shims();
@@ -20305,7 +20440,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
20305
20440
  graph.forEachNode((id, attrs) => {
20306
20441
  if (found) return;
20307
20442
  const a = attrs;
20308
- if (a.type === import_types71.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
20443
+ if (a.type === import_types72.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
20309
20444
  found = id;
20310
20445
  }
20311
20446
  });
@@ -20317,7 +20452,7 @@ function findMatchingRouteNode(graph, serviceName, method, path94) {
20317
20452
  graph.forEachNode((id, attrs) => {
20318
20453
  if (found) return;
20319
20454
  const a = attrs;
20320
- if (a.type !== import_types71.NodeType.RouteNode || a.service !== serviceName) return;
20455
+ if (a.type !== import_types72.NodeType.RouteNode || a.service !== serviceName) return;
20321
20456
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
20322
20457
  const routeMethod = (a.method ?? "").toUpperCase();
20323
20458
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -20336,11 +20471,11 @@ function createCloudflareResolveTarget(config, graph) {
20336
20471
  };
20337
20472
  const mapping = config.workers?.[scriptName];
20338
20473
  if (mapping) {
20339
- const wholeFileId = (0, import_types71.fileId)(mapping.service, mapping.entryFile);
20474
+ const wholeFileId = (0, import_types72.fileId)(mapping.service, mapping.entryFile);
20340
20475
  return {
20341
20476
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
20342
20477
  serviceName: mapping.service,
20343
- edgeType: import_types71.EdgeType.CALLS
20478
+ edgeType: import_types72.EdgeType.CALLS
20344
20479
  };
20345
20480
  }
20346
20481
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -20349,13 +20484,13 @@ function createCloudflareResolveTarget(config, graph) {
20349
20484
  return {
20350
20485
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
20351
20486
  serviceName: fileNode.service,
20352
- edgeType: import_types71.EdgeType.CALLS
20487
+ edgeType: import_types72.EdgeType.CALLS
20353
20488
  };
20354
20489
  }
20355
20490
  return {
20356
- targetNodeId: (0, import_types71.infraId)("cloudflare-worker", scriptName),
20491
+ targetNodeId: (0, import_types72.infraId)("cloudflare-worker", scriptName),
20357
20492
  serviceName: scriptName,
20358
- edgeType: import_types71.EdgeType.CALLS,
20493
+ edgeType: import_types72.EdgeType.CALLS,
20359
20494
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
20360
20495
  };
20361
20496
  };
@@ -20551,14 +20686,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
20551
20686
 
20552
20687
  // src/connectors/neon/resolve.ts
20553
20688
  init_cjs_shims();
20554
- var import_types75 = require("@neat.is/types");
20689
+ var import_types76 = require("@neat.is/types");
20555
20690
  function createNeonResolveTarget(config) {
20556
20691
  return (signal) => {
20557
20692
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
20558
20693
  return {
20559
- targetNodeId: (0, import_types75.infraId)("sql-table", signal.targetName),
20694
+ targetNodeId: (0, import_types76.infraId)("sql-table", signal.targetName),
20560
20695
  serviceName: config.serviceName,
20561
- edgeType: import_types75.EdgeType.CALLS,
20696
+ edgeType: import_types76.EdgeType.CALLS,
20562
20697
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
20563
20698
  };
20564
20699
  };
@@ -20739,14 +20874,14 @@ function mapLogEntriesToSignals2(entries) {
20739
20874
 
20740
20875
  // src/connectors/cloud-run/resolve.ts
20741
20876
  init_cjs_shims();
20742
- var import_types79 = require("@neat.is/types");
20877
+ var import_types80 = require("@neat.is/types");
20743
20878
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
20744
20879
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
20745
20880
  let found = null;
20746
20881
  graph.forEachNode((_id, attrs) => {
20747
20882
  if (found) return;
20748
20883
  const node = attrs;
20749
- if (node.type !== import_types79.NodeType.RouteNode) return;
20884
+ if (node.type !== import_types80.NodeType.RouteNode) return;
20750
20885
  const route = attrs;
20751
20886
  if (route.service !== serviceName || !route.pathTemplate) return;
20752
20887
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -20771,13 +20906,13 @@ function createCloudRunResolveTarget(graph, config) {
20771
20906
  normalizePathTemplate(path94)
20772
20907
  );
20773
20908
  if (routeNodeId) {
20774
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types79.EdgeType.CALLS };
20909
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types80.EdgeType.CALLS };
20775
20910
  }
20776
20911
  }
20777
20912
  return {
20778
- targetNodeId: (0, import_types79.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
20913
+ targetNodeId: (0, import_types80.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
20779
20914
  serviceName: mappedService ?? gcpServiceName,
20780
- edgeType: import_types79.EdgeType.CALLS,
20915
+ edgeType: import_types80.EdgeType.CALLS,
20781
20916
  ensureInfraNode: {
20782
20917
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
20783
20918
  name: gcpServiceName,
@@ -20959,14 +21094,14 @@ function mapLogEntriesToSignals3(entries) {
20959
21094
 
20960
21095
  // src/connectors/gcp-lb/resolve.ts
20961
21096
  init_cjs_shims();
20962
- var import_types83 = require("@neat.is/types");
21097
+ var import_types84 = require("@neat.is/types");
20963
21098
  var GCP_LB_BACKEND_INFRA_KIND = "gcp-lb-backend";
20964
21099
  function findMatchingRouteNode3(graph, serviceName, method, normalizedPath) {
20965
21100
  let found = null;
20966
21101
  graph.forEachNode((_id, attrs) => {
20967
21102
  if (found) return;
20968
21103
  const node = attrs;
20969
- if (node.type !== import_types83.NodeType.RouteNode) return;
21104
+ if (node.type !== import_types84.NodeType.RouteNode) return;
20970
21105
  const route = attrs;
20971
21106
  if (route.service !== serviceName || !route.pathTemplate) return;
20972
21107
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -20991,13 +21126,13 @@ function createGcpLbResolveTarget(graph, config) {
20991
21126
  normalizePathTemplate(path94)
20992
21127
  );
20993
21128
  if (routeNodeId) {
20994
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types83.EdgeType.CALLS };
21129
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types84.EdgeType.CALLS };
20995
21130
  }
20996
21131
  }
20997
21132
  return {
20998
- targetNodeId: (0, import_types83.infraId)(GCP_LB_BACKEND_INFRA_KIND, backendServiceName),
21133
+ targetNodeId: (0, import_types84.infraId)(GCP_LB_BACKEND_INFRA_KIND, backendServiceName),
20999
21134
  serviceName: mappedService ?? backendServiceName,
21000
- edgeType: import_types83.EdgeType.CALLS,
21135
+ edgeType: import_types84.EdgeType.CALLS,
21001
21136
  ensureInfraNode: {
21002
21137
  kind: GCP_LB_BACKEND_INFRA_KIND,
21003
21138
  name: backendServiceName,
@@ -21038,7 +21173,7 @@ function createGcpLbConnector(graph, config = {}) {
21038
21173
 
21039
21174
  // src/connectors/render/index.ts
21040
21175
  init_cjs_shims();
21041
- var import_types86 = require("@neat.is/types");
21176
+ var import_types87 = require("@neat.is/types");
21042
21177
 
21043
21178
  // src/connectors/render/types.ts
21044
21179
  init_cjs_shims();
@@ -21116,7 +21251,7 @@ function buildRenderRouteIndex(graph, serviceName) {
21116
21251
  const out = [];
21117
21252
  graph.forEachNode((_id, attrs) => {
21118
21253
  const node = attrs;
21119
- if (node.type !== import_types86.NodeType.RouteNode) return;
21254
+ if (node.type !== import_types87.NodeType.RouteNode) return;
21120
21255
  const route = attrs;
21121
21256
  if (route.service !== serviceName) return;
21122
21257
  out.push({
@@ -21201,7 +21336,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
21201
21336
  function createRenderResolveTarget(config) {
21202
21337
  return (signal) => {
21203
21338
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
21204
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types86.EdgeType.CALLS };
21339
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types87.EdgeType.CALLS };
21205
21340
  }
21206
21341
  return null;
21207
21342
  };
@@ -21339,21 +21474,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
21339
21474
 
21340
21475
  // src/connectors/planetscale/resolve.ts
21341
21476
  init_cjs_shims();
21342
- var import_types90 = require("@neat.is/types");
21477
+ var import_types91 = require("@neat.is/types");
21343
21478
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
21344
21479
  function createPlanetscaleResolveTarget(graph, config) {
21345
21480
  const databaseName = `${config.organization}/${config.database}`;
21346
21481
  return (signal, _ctx) => {
21347
21482
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
21348
- const tableId = (0, import_types90.infraId)("sql-table", signal.targetName);
21483
+ const tableId = (0, import_types91.infraId)("sql-table", signal.targetName);
21349
21484
  if (graph.hasNode(tableId)) {
21350
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types90.EdgeType.CALLS };
21485
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types91.EdgeType.CALLS };
21351
21486
  }
21352
- const providerId = (0, import_types90.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
21487
+ const providerId = (0, import_types91.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
21353
21488
  return {
21354
21489
  targetNodeId: providerId,
21355
21490
  serviceName: config.serviceName,
21356
- edgeType: import_types90.EdgeType.CALLS,
21491
+ edgeType: import_types91.EdgeType.CALLS,
21357
21492
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
21358
21493
  };
21359
21494
  };
@@ -21618,7 +21753,7 @@ function mapBuildsToSignals(builds, serviceName) {
21618
21753
 
21619
21754
  // src/connectors/eas/resolve.ts
21620
21755
  init_cjs_shims();
21621
- var import_types95 = require("@neat.is/types");
21756
+ var import_types96 = require("@neat.is/types");
21622
21757
  var NO_ENV2 = "unknown";
21623
21758
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
21624
21759
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -21634,8 +21769,8 @@ function configBasenamesForPhase(phase) {
21634
21769
  function configNodeService(graph, configNodeId) {
21635
21770
  for (const edgeId of graph.inboundEdges(configNodeId)) {
21636
21771
  const edge = graph.getEdgeAttributes(edgeId);
21637
- if (edge.type !== import_types95.EdgeType.CONFIGURED_BY) continue;
21638
- const parsed = (0, import_types95.parseFileId)(edge.source);
21772
+ if (edge.type !== import_types96.EdgeType.CONFIGURED_BY) continue;
21773
+ const parsed = (0, import_types96.parseFileId)(edge.source);
21639
21774
  if (parsed) return parsed.service;
21640
21775
  }
21641
21776
  return null;
@@ -21646,7 +21781,7 @@ function findConfigNode(graph, basenames, serviceName) {
21646
21781
  graph.forEachNode((id, attrs) => {
21647
21782
  if (scoped) return;
21648
21783
  const node = attrs;
21649
- if (node.type !== import_types95.NodeType.ConfigNode) return;
21784
+ if (node.type !== import_types96.NodeType.ConfigNode) return;
21650
21785
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
21651
21786
  if (anyMatch === null) anyMatch = id;
21652
21787
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -21663,13 +21798,13 @@ function createEasResolveTarget(graph) {
21663
21798
  if (basenames.length > 0) {
21664
21799
  const configNodeId = findConfigNode(graph, basenames, serviceName);
21665
21800
  if (configNodeId) {
21666
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types95.EdgeType.CALLS };
21801
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types96.EdgeType.CALLS };
21667
21802
  }
21668
21803
  }
21669
21804
  return {
21670
21805
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
21671
21806
  serviceName,
21672
- edgeType: import_types95.EdgeType.CALLS
21807
+ edgeType: import_types96.EdgeType.CALLS
21673
21808
  };
21674
21809
  };
21675
21810
  }
@@ -22287,6 +22422,7 @@ async function startConnectorPolling(input) {
22287
22422
  registration.connector,
22288
22423
  {
22289
22424
  projectDir: input.projectDir,
22425
+ project: input.project,
22290
22426
  credentials: registration.credentials,
22291
22427
  ...input.errorsPath ? { errorsPath: input.errorsPath } : {}
22292
22428
  },
@@ -22509,11 +22645,11 @@ function registerRoutes(scope, ctx) {
22509
22645
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
22510
22646
  const parsed = [];
22511
22647
  for (const c of candidates) {
22512
- const r = import_types98.DivergenceTypeSchema.safeParse(c);
22648
+ const r = import_types99.DivergenceTypeSchema.safeParse(c);
22513
22649
  if (!r.success) {
22514
22650
  return reply.code(400).send({
22515
22651
  error: `unknown divergence type "${c}"`,
22516
- allowed: import_types98.DivergenceTypeSchema.options
22652
+ allowed: import_types99.DivergenceTypeSchema.options
22517
22653
  });
22518
22654
  }
22519
22655
  parsed.push(r.data);
@@ -22630,6 +22766,7 @@ function registerRoutes(scope, ctx) {
22630
22766
  reg.connector,
22631
22767
  {
22632
22768
  projectDir: proj.scanPath ?? "",
22769
+ project: proj.name,
22633
22770
  credentials: reg.credentials,
22634
22771
  ...incidentsPath ? { errorsPath: incidentsPath } : {}
22635
22772
  },
@@ -22693,6 +22830,39 @@ function registerRoutes(scope, ctx) {
22693
22830
  if (!result) return reply.code(404).send({ error: "no root cause found", id: nodeId });
22694
22831
  return result;
22695
22832
  });
22833
+ scope.get("/graph/incident-card/:nodeId", async (req, reply) => {
22834
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
22835
+ if (!proj) return;
22836
+ const { nodeId } = req.params;
22837
+ if (!proj.graph.hasNode(nodeId)) {
22838
+ return reply.code(404).send({ error: "node not found", id: nodeId });
22839
+ }
22840
+ const epath = errorsPathFor(proj);
22841
+ const incidents = epath ? await readErrorEvents(epath) : [];
22842
+ let errorEvent;
22843
+ if (req.query.errorId) {
22844
+ errorEvent = incidents.find((e) => e.id === req.query.errorId);
22845
+ if (!errorEvent) {
22846
+ return reply.code(404).send({ error: "error event not found", id: req.query.errorId });
22847
+ }
22848
+ } else {
22849
+ const svc = nodeId.replace(/^service:/, "");
22850
+ errorEvent = [...incidents].filter((e) => e.affectedNode === nodeId || e.service === svc).sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""))[0];
22851
+ if (!errorEvent) {
22852
+ return reply.code(404).send({ error: "no incident found for node", id: nodeId });
22853
+ }
22854
+ }
22855
+ const policyPath = ctx.policyFilePathFor(proj);
22856
+ let policies = [];
22857
+ if (policyPath) {
22858
+ try {
22859
+ policies = await loadPolicyFile(policyPath);
22860
+ } catch {
22861
+ policies = [];
22862
+ }
22863
+ }
22864
+ return buildIncidentCard(proj.graph, errorEvent, incidents, policies);
22865
+ });
22696
22866
  scope.get("/graph/blast-radius/:nodeId", async (req, reply) => {
22697
22867
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
22698
22868
  if (!proj) return;
@@ -22875,7 +23045,7 @@ function registerRoutes(scope, ctx) {
22875
23045
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
22876
23046
  let violations = await log.readAll();
22877
23047
  if (req.query.severity) {
22878
- const sev = import_types98.PolicySeveritySchema.safeParse(req.query.severity);
23048
+ const sev = import_types99.PolicySeveritySchema.safeParse(req.query.severity);
22879
23049
  if (!sev.success) {
22880
23050
  return reply.code(400).send({
22881
23051
  error: "invalid severity",
@@ -22914,7 +23084,7 @@ function registerRoutes(scope, ctx) {
22914
23084
  scope.post("/policies/check", async (req, reply) => {
22915
23085
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
22916
23086
  if (!proj) return;
22917
- const parsed = import_types98.PoliciesCheckBodySchema.safeParse(req.body ?? {});
23087
+ const parsed = import_types99.PoliciesCheckBodySchema.safeParse(req.body ?? {});
22918
23088
  if (!parsed.success) {
22919
23089
  return reply.code(400).send({
22920
23090
  error: "invalid /policies/check body",
@@ -23247,7 +23417,7 @@ var import_node_fs41 = require("fs");
23247
23417
  var import_node_path76 = __toESM(require("path"), 1);
23248
23418
 
23249
23419
  // src/daemon.ts
23250
- var import_types99 = require("@neat.is/types");
23420
+ var import_types100 = require("@neat.is/types");
23251
23421
  function daemonJsonPath(scanPath) {
23252
23422
  return import_node_path77.default.join(scanPath, "neat-out", "daemon.json");
23253
23423
  }
@@ -23957,7 +24127,7 @@ async function startWatch(graph, opts) {
23957
24127
  writeErrorEventInline: false,
23958
24128
  onPolicyTrigger
23959
24129
  });
23960
- const onErrorSpanSync = makeErrorSpanWriter(opts.errorsPath, graph, opts.scanPath);
24130
+ const onErrorSpanSync = makeErrorSpanWriter(opts.errorsPath, graph, opts.scanPath, projectName);
23961
24131
  const otelHttp = await buildOtelReceiver({ onSpan, onErrorSpanSync });
23962
24132
  const otelAddress = await listenSteppingOtlp(otelHttp, otelPort, host);
23963
24133
  const boundOtelPort = portFromListenAddress(otelAddress, otelPort);
@@ -28264,7 +28434,7 @@ var import_node_path87 = __toESM(require("path"), 1);
28264
28434
 
28265
28435
  // src/cli-client.ts
28266
28436
  init_cjs_shims();
28267
- var import_types100 = require("@neat.is/types");
28437
+ var import_types101 = require("@neat.is/types");
28268
28438
  var HttpError = class extends Error {
28269
28439
  constructor(status2, message, responseBody = "") {
28270
28440
  super(message);
@@ -28406,7 +28576,7 @@ async function runBlastRadius(client, input) {
28406
28576
  }
28407
28577
  }
28408
28578
  function formatBlastEntry(n) {
28409
- const tag = n.edgeProvenance === import_types100.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
28579
+ const tag = n.edgeProvenance === import_types101.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
28410
28580
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
28411
28581
  }
28412
28582
  async function runDependencies(client, input) {
@@ -28463,7 +28633,7 @@ async function runObservedDependencies(client, input) {
28463
28633
  if (result.observed) {
28464
28634
  return {
28465
28635
  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
28636
+ provenance: import_types101.Provenance.OBSERVED
28467
28637
  };
28468
28638
  }
28469
28639
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -28473,7 +28643,7 @@ async function runObservedDependencies(client, input) {
28473
28643
  return {
28474
28644
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
28475
28645
  block: blockLines.join("\n"),
28476
- provenance: import_types100.Provenance.OBSERVED
28646
+ provenance: import_types101.Provenance.OBSERVED
28477
28647
  };
28478
28648
  } catch (err) {
28479
28649
  if (err instanceof HttpError && err.status === 404) {
@@ -28527,7 +28697,7 @@ async function runIncidents(client, input) {
28527
28697
  return {
28528
28698
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
28529
28699
  block: blockLines.join("\n"),
28530
- provenance: import_types100.Provenance.OBSERVED
28700
+ provenance: import_types101.Provenance.OBSERVED
28531
28701
  };
28532
28702
  } catch (err) {
28533
28703
  if (err instanceof HttpError && err.status === 404) {
@@ -28636,7 +28806,7 @@ async function runStaleEdges(client, input) {
28636
28806
  return {
28637
28807
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
28638
28808
  block: blockLines.join("\n"),
28639
- provenance: import_types100.Provenance.STALE
28809
+ provenance: import_types101.Provenance.STALE
28640
28810
  };
28641
28811
  }
28642
28812
  async function runPolicies(client, input) {
@@ -29927,12 +30097,12 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
29927
30097
 
29928
30098
  // src/monitor.ts
29929
30099
  init_cjs_shims();
29930
- var import_types101 = require("@neat.is/types");
30100
+ var import_types102 = require("@neat.is/types");
29931
30101
  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
30102
+ import_types102.EdgeType.CALLS,
30103
+ import_types102.EdgeType.CONNECTS_TO,
30104
+ import_types102.EdgeType.PUBLISHES_TO,
30105
+ import_types102.EdgeType.CONSUMES_FROM
29936
30106
  ]);
29937
30107
  function divergenceKey(d) {
29938
30108
  const column = "column" in d && d.column ? d.column : "";
@@ -29993,7 +30163,7 @@ function formatDivergenceLine2(d) {
29993
30163
  }
29994
30164
  }
29995
30165
  function formatStaleLine(edgeId) {
29996
- const parsed = (0, import_types101.parseEdgeId)(edgeId);
30166
+ const parsed = (0, import_types102.parseEdgeId)(edgeId);
29997
30167
  if (parsed) {
29998
30168
  return `\u22EF stale ${parsed.source} \u2192 ${parsed.target} (observed edge went quiet)`;
29999
30169
  }
@@ -30006,7 +30176,7 @@ function divergenceJson(d) {
30006
30176
  return JSON.stringify({ kind: "divergence", ...d });
30007
30177
  }
30008
30178
  function staleJson(edgeId) {
30009
- const parsed = (0, import_types101.parseEdgeId)(edgeId);
30179
+ const parsed = (0, import_types102.parseEdgeId)(edgeId);
30010
30180
  return JSON.stringify({
30011
30181
  kind: "stale",
30012
30182
  edgeId,
@@ -30041,6 +30211,19 @@ function formatPolicyLine(v) {
30041
30211
  function policyJson(v) {
30042
30212
  return JSON.stringify({ kind: "policy", ...v });
30043
30213
  }
30214
+ function formatIncidentLine(card) {
30215
+ let tag = "";
30216
+ if (card.rootCause) {
30217
+ const rc = card.rootCause;
30218
+ const provs = rc.chain.map((h) => h.provenance).join("\xB7");
30219
+ const cls = rc.classification ? `${rc.classification} ` : "";
30220
+ tag = ` \xB7 ${cls}${rc.confidence.toFixed(2)}${provs ? ` [${provs}]` : ""}`;
30221
+ }
30222
+ return `\u2716 incident [${card.incidentKind}] ${card.headline}${tag}`;
30223
+ }
30224
+ function incidentJson(card) {
30225
+ return JSON.stringify(card);
30226
+ }
30044
30227
  var MonitorEmitter = class {
30045
30228
  constructor(opts) {
30046
30229
  this.opts = opts;
@@ -30076,7 +30259,7 @@ var MonitorEmitter = class {
30076
30259
  // ignores non-OBSERVED edges and non-dependency edge types (structural
30077
30260
  // ownership), so only real runtime dependencies reach stdout.
30078
30261
  emitObservedEdge(edge) {
30079
- if (edge.provenance !== import_types101.Provenance.OBSERVED) return false;
30262
+ if (edge.provenance !== import_types102.Provenance.OBSERVED) return false;
30080
30263
  if (!OBSERVED_DEP_EDGE_TYPES.has(edge.type)) return false;
30081
30264
  const key = `edge|${edge.id}`;
30082
30265
  if (this.seen.has(key)) return false;
@@ -30098,6 +30281,17 @@ var MonitorEmitter = class {
30098
30281
  }
30099
30282
  return emitted;
30100
30283
  }
30284
+ // Emit one incident card once, keyed on the incident id. Unlike the other
30285
+ // facts this is event-driven, not a re-read of derivable state, so there is no
30286
+ // baseline dump on connect — only incidents that arrive live after the monitor
30287
+ // is watching reach stdout.
30288
+ emitIncident(card) {
30289
+ const key = `incident|${card.id}`;
30290
+ if (this.seen.has(key)) return false;
30291
+ this.seen.add(key);
30292
+ this.out(this.opts.json ? incidentJson(card) : formatIncidentLine(card));
30293
+ return true;
30294
+ }
30101
30295
  };
30102
30296
  function parseFrame(raw) {
30103
30297
  let event = "message";
@@ -30219,6 +30413,18 @@ async function runMonitor(opts) {
30219
30413
  const result = await client.get(policiesPath);
30220
30414
  emitter.emitPolicies(result);
30221
30415
  }, debounceMs);
30416
+ const readIncidentCard = async (incidentId, affectedNode) => {
30417
+ try {
30418
+ const card = await client.get(
30419
+ projectPath2(
30420
+ opts.project,
30421
+ `/graph/incident-card/${encodeURIComponent(affectedNode)}?errorId=${encodeURIComponent(incidentId)}`
30422
+ )
30423
+ );
30424
+ emitter.emitIncident(card);
30425
+ } catch {
30426
+ }
30427
+ };
30222
30428
  const onFrame = (frame) => {
30223
30429
  switch (frame.event) {
30224
30430
  case "extraction-complete":
@@ -30234,7 +30440,7 @@ async function runMonitor(opts) {
30234
30440
  case "edge-added": {
30235
30441
  const payload = safeParse(frame.data);
30236
30442
  const edge = payload?.edge;
30237
- if (edge && edge.provenance === import_types101.Provenance.OBSERVED) {
30443
+ if (edge && edge.provenance === import_types102.Provenance.OBSERVED) {
30238
30444
  emitter.emitObservedEdge(edge);
30239
30445
  divergences.schedule();
30240
30446
  }
@@ -30243,6 +30449,13 @@ async function runMonitor(opts) {
30243
30449
  case "policy-violation":
30244
30450
  policies.schedule();
30245
30451
  break;
30452
+ case "incident": {
30453
+ const payload = safeParse(frame.data);
30454
+ const incidentId = payload && typeof payload.incidentId === "string" ? payload.incidentId : void 0;
30455
+ const affectedNode = payload && typeof payload.affectedNode === "string" ? payload.affectedNode : void 0;
30456
+ if (incidentId && affectedNode) void readIncidentCard(incidentId, affectedNode);
30457
+ break;
30458
+ }
30246
30459
  default:
30247
30460
  break;
30248
30461
  }
@@ -30477,7 +30690,7 @@ async function runSync(opts) {
30477
30690
  }
30478
30691
 
30479
30692
  // src/cli.ts
30480
- var import_types102 = require("@neat.is/types");
30693
+ var import_types103 = require("@neat.is/types");
30481
30694
  function isNpxInvocation() {
30482
30695
  if (process.env.npm_command === "exec") return true;
30483
30696
  const execpath = process.env.npm_execpath ?? "";
@@ -31561,10 +31774,10 @@ async function runQueryVerb(cmd, parsed) {
31561
31774
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
31562
31775
  const out = [];
31563
31776
  for (const p of parts) {
31564
- const r = import_types102.DivergenceTypeSchema.safeParse(p);
31777
+ const r = import_types103.DivergenceTypeSchema.safeParse(p);
31565
31778
  if (!r.success) {
31566
31779
  console.error(
31567
- `neat divergences: unknown --type "${p}". allowed: ${import_types102.DivergenceTypeSchema.options.join(", ")}`
31780
+ `neat divergences: unknown --type "${p}". allowed: ${import_types103.DivergenceTypeSchema.options.join(", ")}`
31568
31781
  );
31569
31782
  return 2;
31570
31783
  }