@neat.is/core 0.4.28-dev.20260707 → 0.4.28-dev.20260708

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mountBearerAuth,
3
3
  readAuthEnv
4
- } from "./chunk-A3322JYS.js";
4
+ } from "./chunk-CFDPIMRP.js";
5
5
 
6
6
  // src/graph.ts
7
7
  import GraphDefault from "graphology";
@@ -1373,6 +1373,8 @@ var PolicyViolationsLog = class {
1373
1373
  // src/ingest.ts
1374
1374
  import {
1375
1375
  EdgeType as EdgeType3,
1376
+ GraphEdgeSchema,
1377
+ GraphNodeSchema,
1376
1378
  NodeType as NodeType3,
1377
1379
  Provenance as Provenance2,
1378
1380
  confidenceForObservedSignal,
@@ -2529,24 +2531,58 @@ function dedupeIncidents(events) {
2529
2531
  return !hasRealFailure.has(groupKey(ev));
2530
2532
  });
2531
2533
  }
2534
+ var SnapshotValidationError = class extends Error {
2535
+ constructor(issues) {
2536
+ super(`snapshot failed validation (${issues.length} invalid ${issues.length === 1 ? "entry" : "entries"})`);
2537
+ this.issues = issues;
2538
+ this.name = "SnapshotValidationError";
2539
+ }
2540
+ issues;
2541
+ };
2542
+ function describeZodIssues(error) {
2543
+ return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
2544
+ }
2532
2545
  function mergeSnapshot(graph, snapshot) {
2533
2546
  const exported = snapshot.graph;
2547
+ const incomingNodes = Array.isArray(exported.nodes) ? exported.nodes : [];
2548
+ const incomingEdges = Array.isArray(exported.edges) ? exported.edges : [];
2549
+ const issues = [];
2550
+ const validNodes = [];
2551
+ const validEdges = [];
2552
+ for (const node of incomingNodes) {
2553
+ if (node.attributes === void 0) continue;
2554
+ const parsed = GraphNodeSchema.safeParse(node.attributes);
2555
+ if (!parsed.success) {
2556
+ issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
2557
+ continue;
2558
+ }
2559
+ validNodes.push({ key: node.key, attributes: parsed.data });
2560
+ }
2561
+ for (const edge of incomingEdges) {
2562
+ if (edge.attributes === void 0) continue;
2563
+ const parsed = GraphEdgeSchema.safeParse(edge.attributes);
2564
+ if (!parsed.success) {
2565
+ const label = edge.key ?? `${edge.source}->${edge.target}`;
2566
+ issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
2567
+ continue;
2568
+ }
2569
+ const id = edge.key ?? parsed.data.id;
2570
+ validEdges.push({ key: id, source: edge.source, target: edge.target, attributes: parsed.data });
2571
+ }
2572
+ if (issues.length > 0) {
2573
+ throw new SnapshotValidationError(issues);
2574
+ }
2534
2575
  let nodesAdded = 0;
2535
2576
  let edgesAdded = 0;
2536
- for (const node of exported.nodes ?? []) {
2577
+ for (const node of validNodes) {
2537
2578
  if (graph.hasNode(node.key)) continue;
2538
- if (!node.attributes) continue;
2539
2579
  graph.addNode(node.key, node.attributes);
2540
2580
  nodesAdded++;
2541
2581
  }
2542
- for (const edge of exported.edges ?? []) {
2543
- const attrs = edge.attributes;
2544
- if (!attrs) continue;
2545
- const id = edge.key ?? attrs.id;
2546
- if (!id) continue;
2547
- if (graph.hasEdge(id)) continue;
2582
+ for (const edge of validEdges) {
2583
+ if (graph.hasEdge(edge.key)) continue;
2548
2584
  if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue;
2549
- graph.addEdgeWithKey(id, edge.source, edge.target, attrs);
2585
+ graph.addEdgeWithKey(edge.key, edge.source, edge.target, edge.attributes);
2550
2586
  edgesAdded++;
2551
2587
  }
2552
2588
  return { nodesAdded, edgesAdded };
@@ -7484,6 +7520,50 @@ async function rollbackExtension(ctx, args) {
7484
7520
  };
7485
7521
  }
7486
7522
 
7523
+ // src/logs-store.ts
7524
+ var LOGS_STORE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
7525
+ var LOGS_QUERY_DEFAULT_LIMIT = 100;
7526
+ var LOGS_QUERY_MAX_LIMIT = 1e3;
7527
+ var buffers = /* @__PURE__ */ new Map();
7528
+ var KEY_SEP = "::";
7529
+ function bufferKey(projectName, source) {
7530
+ return `${projectName}${KEY_SEP}${source}`;
7531
+ }
7532
+ function sourcesForProject(projectName) {
7533
+ const prefix = `${projectName}${KEY_SEP}`;
7534
+ const sources = [];
7535
+ for (const key of buffers.keys()) {
7536
+ if (key.startsWith(prefix)) sources.push(key.slice(prefix.length));
7537
+ }
7538
+ return sources;
7539
+ }
7540
+ function queryLogEntries(opts) {
7541
+ const { projectName, source, service, since, limit } = opts;
7542
+ const sources = source && source.length > 0 ? source : sourcesForProject(projectName);
7543
+ let merged = [];
7544
+ for (const src of sources) {
7545
+ const buf = buffers.get(bufferKey(projectName, src));
7546
+ if (buf) merged = merged.concat(buf);
7547
+ }
7548
+ if (service) {
7549
+ merged = merged.filter((e) => e.serviceName === service);
7550
+ }
7551
+ if (since) {
7552
+ const sinceMs = Date.parse(since);
7553
+ if (!Number.isNaN(sinceMs)) {
7554
+ merged = merged.filter((e) => {
7555
+ const t = Date.parse(e.timestamp);
7556
+ return Number.isNaN(t) || t >= sinceMs;
7557
+ });
7558
+ }
7559
+ }
7560
+ merged.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
7561
+ if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) {
7562
+ return merged.slice(0, limit);
7563
+ }
7564
+ return merged;
7565
+ }
7566
+
7487
7567
  // src/streaming.ts
7488
7568
  var SSE_HEARTBEAT_MS = 3e4;
7489
7569
  var SSE_BACKPRESSURE_CAP = 1e3;
@@ -7742,6 +7822,23 @@ function registerRoutes(scope, ctx) {
7742
7822
  const sliced = ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50);
7743
7823
  return { count: sliced.length, total, events: sliced };
7744
7824
  });
7825
+ scope.get("/logs", async (req, reply) => {
7826
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
7827
+ if (!proj) return;
7828
+ const rawSource = req.query.source;
7829
+ const sources = rawSource === void 0 ? void 0 : Array.isArray(rawSource) ? rawSource : [rawSource];
7830
+ const filtered = queryLogEntries({
7831
+ projectName: proj.name,
7832
+ ...sources && sources.length > 0 ? { source: sources } : {},
7833
+ ...req.query.service ? { service: req.query.service } : {},
7834
+ ...req.query.since ? { since: req.query.since } : {}
7835
+ });
7836
+ const total = filtered.length;
7837
+ const limit = req.query.limit ? Number(req.query.limit) : LOGS_QUERY_DEFAULT_LIMIT;
7838
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, LOGS_QUERY_MAX_LIMIT) : LOGS_QUERY_DEFAULT_LIMIT;
7839
+ const sliced = filtered.slice(0, safeLimit);
7840
+ return { count: sliced.length, total, logs: sliced };
7841
+ });
7745
7842
  const incidentHistoryHandler = async (req, reply) => {
7746
7843
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
7747
7844
  if (!proj) return;
@@ -7837,8 +7934,16 @@ function registerRoutes(scope, ctx) {
7837
7934
  if (!against) {
7838
7935
  return reply.code(400).send({ error: "query parameter `against` is required" });
7839
7936
  }
7937
+ const targetProjectName = against === "self" ? proj.name : against;
7938
+ const targetProject = registry.get(targetProjectName);
7939
+ if (!targetProject) {
7940
+ return reply.code(400).send({
7941
+ error: "unknown snapshot id \u2014 `against` must be `self` or the name of a project this server has a snapshot for",
7942
+ against
7943
+ });
7944
+ }
7840
7945
  try {
7841
- const snapshot = await loadSnapshotForDiff(against);
7946
+ const snapshot = await loadSnapshotForDiff(targetProject.paths.snapshotPath);
7842
7947
  return computeGraphDiff(proj.graph, snapshot);
7843
7948
  } catch (err) {
7844
7949
  return reply.code(400).send({ error: "failed to load snapshot", against, detail: err.message });
@@ -7868,6 +7973,13 @@ function registerRoutes(scope, ctx) {
7868
7973
  edgeCount: proj.graph.size
7869
7974
  };
7870
7975
  } catch (err) {
7976
+ if (err instanceof SnapshotValidationError) {
7977
+ return reply.code(400).send({
7978
+ error: "snapshot merge failed",
7979
+ details: err.message,
7980
+ issues: err.issues
7981
+ });
7982
+ }
7871
7983
  return reply.code(400).send({
7872
7984
  error: "snapshot merge failed",
7873
7985
  details: err.message
@@ -8247,6 +8359,7 @@ export {
8247
8359
  addImports,
8248
8360
  addDatabasesAndCompat,
8249
8361
  addConfigNodes,
8362
+ normalizePathTemplate,
8250
8363
  addCallEdges,
8251
8364
  addInfra,
8252
8365
  retireEdgesByFile,
@@ -8282,4 +8395,4 @@ export {
8282
8395
  pruneRegistry,
8283
8396
  buildApi
8284
8397
  };
8285
- //# sourceMappingURL=chunk-QM6BMPVJ.js.map
8398
+ //# sourceMappingURL=chunk-O4MRDWD7.js.map