@neat.is/core 0.4.27 → 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.
package/dist/neatd.js CHANGED
@@ -2,15 +2,15 @@
2
2
  import {
3
3
  reconcileDaemonRecordSync,
4
4
  startDaemon
5
- } from "./chunk-XV4D7A3Z.js";
5
+ } from "./chunk-NY5Q6NE2.js";
6
6
  import {
7
7
  listProjects,
8
8
  registryPath
9
- } from "./chunk-QM6BMPVJ.js";
9
+ } from "./chunk-O4MRDWD7.js";
10
10
  import {
11
11
  BindAuthorityError,
12
12
  __require
13
- } from "./chunk-A3322JYS.js";
13
+ } from "./chunk-CFDPIMRP.js";
14
14
 
15
15
  // src/neatd.ts
16
16
  import { promises as fs } from "fs";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  reshapeGrpcRequest,
3
3
  startOtelGrpcReceiver
4
- } from "./chunk-UV5WSM7M.js";
5
- import "./chunk-A3322JYS.js";
4
+ } from "./chunk-I72HTUOG.js";
5
+ import "./chunk-CFDPIMRP.js";
6
6
  export {
7
7
  reshapeGrpcRequest,
8
8
  startOtelGrpcReceiver
9
9
  };
10
- //# sourceMappingURL=otel-grpc-ET5Z6KI6.js.map
10
+ //# sourceMappingURL=otel-grpc-IDMIH6ZY.js.map
package/dist/server.cjs CHANGED
@@ -53,15 +53,16 @@ function mountBearerAuth(app, opts) {
53
53
  if (!opts.token || opts.token.length === 0) return;
54
54
  if (opts.trustProxy) return;
55
55
  const expected = Buffer.from(opts.token, "utf8");
56
- const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
56
+ const exactUnauthPaths = /* @__PURE__ */ new Set([
57
+ ...DEFAULT_UNAUTH_PATHS,
58
+ ...opts.extraUnauthenticatedSuffixes ?? []
59
+ ]);
57
60
  const publicRead = opts.publicRead === true;
58
61
  app.addHook("preHandler", (req, reply, done) => {
59
62
  const path46 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
60
- for (const suffix of suffixes) {
61
- if (path46 === suffix || path46.endsWith(suffix)) {
62
- done();
63
- return;
64
- }
63
+ if (exactUnauthPaths.has(path46) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path46)) {
64
+ done();
65
+ return;
65
66
  }
66
67
  if (publicRead && PUBLIC_READ_METHODS.has(req.method)) {
67
68
  done();
@@ -96,7 +97,7 @@ function readAuthEnv(env = process.env) {
96
97
  publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
97
98
  };
98
99
  }
99
- var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_SUFFIXES;
100
+ var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_PATHS, PROJECT_SCOPED_UNAUTH_PATTERN;
100
101
  var init_auth = __esm({
101
102
  "src/auth.ts"() {
102
103
  "use strict";
@@ -117,12 +118,15 @@ var init_auth = __esm({
117
118
  }
118
119
  };
119
120
  PUBLIC_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
120
- DEFAULT_UNAUTH_SUFFIXES = [
121
+ DEFAULT_UNAUTH_PATHS = [
121
122
  "/health",
122
123
  "/healthz",
123
124
  "/readyz",
124
125
  "/api/config"
125
126
  ];
127
+ PROJECT_SCOPED_UNAUTH_PATTERN = new RegExp(
128
+ `^/projects/[^/]+/(?:${DEFAULT_UNAUTH_PATHS.map((p) => p.slice(1)).join("|")})$`
129
+ );
126
130
  }
127
131
  });
128
132
 
@@ -2077,6 +2081,51 @@ function computeDivergences(graph, opts = {}) {
2077
2081
  });
2078
2082
  }
2079
2083
 
2084
+ // src/logs-store.ts
2085
+ init_cjs_shims();
2086
+ var LOGS_STORE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
2087
+ var LOGS_QUERY_DEFAULT_LIMIT = 100;
2088
+ var LOGS_QUERY_MAX_LIMIT = 1e3;
2089
+ var buffers = /* @__PURE__ */ new Map();
2090
+ var KEY_SEP = "::";
2091
+ function bufferKey(projectName, source) {
2092
+ return `${projectName}${KEY_SEP}${source}`;
2093
+ }
2094
+ function sourcesForProject(projectName) {
2095
+ const prefix = `${projectName}${KEY_SEP}`;
2096
+ const sources = [];
2097
+ for (const key of buffers.keys()) {
2098
+ if (key.startsWith(prefix)) sources.push(key.slice(prefix.length));
2099
+ }
2100
+ return sources;
2101
+ }
2102
+ function queryLogEntries(opts) {
2103
+ const { projectName, source, service, since, limit } = opts;
2104
+ const sources = source && source.length > 0 ? source : sourcesForProject(projectName);
2105
+ let merged = [];
2106
+ for (const src of sources) {
2107
+ const buf = buffers.get(bufferKey(projectName, src));
2108
+ if (buf) merged = merged.concat(buf);
2109
+ }
2110
+ if (service) {
2111
+ merged = merged.filter((e) => e.serviceName === service);
2112
+ }
2113
+ if (since) {
2114
+ const sinceMs = Date.parse(since);
2115
+ if (!Number.isNaN(sinceMs)) {
2116
+ merged = merged.filter((e) => {
2117
+ const t = Date.parse(e.timestamp);
2118
+ return Number.isNaN(t) || t >= sinceMs;
2119
+ });
2120
+ }
2121
+ }
2122
+ merged.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
2123
+ if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) {
2124
+ return merged.slice(0, limit);
2125
+ }
2126
+ return merged;
2127
+ }
2128
+
2080
2129
  // src/policy.ts
2081
2130
  init_cjs_shims();
2082
2131
  var import_node_fs4 = require("fs");
@@ -3663,24 +3712,58 @@ function dedupeIncidents(events) {
3663
3712
  return !hasRealFailure.has(groupKey(ev));
3664
3713
  });
3665
3714
  }
3715
+ var SnapshotValidationError = class extends Error {
3716
+ constructor(issues) {
3717
+ super(`snapshot failed validation (${issues.length} invalid ${issues.length === 1 ? "entry" : "entries"})`);
3718
+ this.issues = issues;
3719
+ this.name = "SnapshotValidationError";
3720
+ }
3721
+ issues;
3722
+ };
3723
+ function describeZodIssues(error) {
3724
+ return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
3725
+ }
3666
3726
  function mergeSnapshot(graph, snapshot) {
3667
3727
  const exported = snapshot.graph;
3728
+ const incomingNodes = Array.isArray(exported.nodes) ? exported.nodes : [];
3729
+ const incomingEdges = Array.isArray(exported.edges) ? exported.edges : [];
3730
+ const issues = [];
3731
+ const validNodes = [];
3732
+ const validEdges = [];
3733
+ for (const node of incomingNodes) {
3734
+ if (node.attributes === void 0) continue;
3735
+ const parsed = import_types4.GraphNodeSchema.safeParse(node.attributes);
3736
+ if (!parsed.success) {
3737
+ issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
3738
+ continue;
3739
+ }
3740
+ validNodes.push({ key: node.key, attributes: parsed.data });
3741
+ }
3742
+ for (const edge of incomingEdges) {
3743
+ if (edge.attributes === void 0) continue;
3744
+ const parsed = import_types4.GraphEdgeSchema.safeParse(edge.attributes);
3745
+ if (!parsed.success) {
3746
+ const label = edge.key ?? `${edge.source}->${edge.target}`;
3747
+ issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
3748
+ continue;
3749
+ }
3750
+ const id = edge.key ?? parsed.data.id;
3751
+ validEdges.push({ key: id, source: edge.source, target: edge.target, attributes: parsed.data });
3752
+ }
3753
+ if (issues.length > 0) {
3754
+ throw new SnapshotValidationError(issues);
3755
+ }
3668
3756
  let nodesAdded = 0;
3669
3757
  let edgesAdded = 0;
3670
- for (const node of exported.nodes ?? []) {
3758
+ for (const node of validNodes) {
3671
3759
  if (graph.hasNode(node.key)) continue;
3672
- if (!node.attributes) continue;
3673
3760
  graph.addNode(node.key, node.attributes);
3674
3761
  nodesAdded++;
3675
3762
  }
3676
- for (const edge of exported.edges ?? []) {
3677
- const attrs = edge.attributes;
3678
- if (!attrs) continue;
3679
- const id = edge.key ?? attrs.id;
3680
- if (!id) continue;
3681
- if (graph.hasEdge(id)) continue;
3763
+ for (const edge of validEdges) {
3764
+ if (graph.hasEdge(edge.key)) continue;
3682
3765
  if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue;
3683
- graph.addEdgeWithKey(id, edge.source, edge.target, attrs);
3766
+ graph.addEdgeWithKey(edge.key, edge.source, edge.target, edge.attributes);
3684
3767
  edgesAdded++;
3685
3768
  }
3686
3769
  return { nodesAdded, edgesAdded };
@@ -7892,6 +7975,23 @@ function registerRoutes(scope, ctx) {
7892
7975
  const sliced = ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50);
7893
7976
  return { count: sliced.length, total, events: sliced };
7894
7977
  });
7978
+ scope.get("/logs", async (req, reply) => {
7979
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
7980
+ if (!proj) return;
7981
+ const rawSource = req.query.source;
7982
+ const sources = rawSource === void 0 ? void 0 : Array.isArray(rawSource) ? rawSource : [rawSource];
7983
+ const filtered = queryLogEntries({
7984
+ projectName: proj.name,
7985
+ ...sources && sources.length > 0 ? { source: sources } : {},
7986
+ ...req.query.service ? { service: req.query.service } : {},
7987
+ ...req.query.since ? { since: req.query.since } : {}
7988
+ });
7989
+ const total = filtered.length;
7990
+ const limit = req.query.limit ? Number(req.query.limit) : LOGS_QUERY_DEFAULT_LIMIT;
7991
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, LOGS_QUERY_MAX_LIMIT) : LOGS_QUERY_DEFAULT_LIMIT;
7992
+ const sliced = filtered.slice(0, safeLimit);
7993
+ return { count: sliced.length, total, logs: sliced };
7994
+ });
7895
7995
  const incidentHistoryHandler = async (req, reply) => {
7896
7996
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
7897
7997
  if (!proj) return;
@@ -7987,8 +8087,16 @@ function registerRoutes(scope, ctx) {
7987
8087
  if (!against) {
7988
8088
  return reply.code(400).send({ error: "query parameter `against` is required" });
7989
8089
  }
8090
+ const targetProjectName = against === "self" ? proj.name : against;
8091
+ const targetProject = registry.get(targetProjectName);
8092
+ if (!targetProject) {
8093
+ return reply.code(400).send({
8094
+ error: "unknown snapshot id \u2014 `against` must be `self` or the name of a project this server has a snapshot for",
8095
+ against
8096
+ });
8097
+ }
7990
8098
  try {
7991
- const snapshot = await loadSnapshotForDiff(against);
8099
+ const snapshot = await loadSnapshotForDiff(targetProject.paths.snapshotPath);
7992
8100
  return computeGraphDiff(proj.graph, snapshot);
7993
8101
  } catch (err) {
7994
8102
  return reply.code(400).send({ error: "failed to load snapshot", against, detail: err.message });
@@ -8018,6 +8126,13 @@ function registerRoutes(scope, ctx) {
8018
8126
  edgeCount: proj.graph.size
8019
8127
  };
8020
8128
  } catch (err) {
8129
+ if (err instanceof SnapshotValidationError) {
8130
+ return reply.code(400).send({
8131
+ error: "snapshot merge failed",
8132
+ details: err.message,
8133
+ issues: err.issues
8134
+ });
8135
+ }
8021
8136
  return reply.code(400).send({
8022
8137
  error: "snapshot merge failed",
8023
8138
  details: err.message