@neat.is/core 0.7.8 → 0.7.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/index.cjs CHANGED
@@ -503,13 +503,18 @@ function loadProtobufResponseEncoder() {
503
503
  );
504
504
  return exportTraceServiceResponseType;
505
505
  }
506
- function encodeProtobufResponseBody() {
507
- if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
506
+ function encodeProtobufResponseBody(rejected, message) {
508
507
  const Type = loadProtobufResponseEncoder();
509
- const msg = Type.create({});
510
- const encoded = Type.encode(msg).finish();
511
- cachedProtobufResponseBody = Buffer.from(encoded);
512
- return cachedProtobufResponseBody;
508
+ if (!rejected) {
509
+ if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
510
+ const msg2 = Type.create({});
511
+ cachedProtobufResponseBody = Buffer.from(Type.encode(msg2).finish());
512
+ return cachedProtobufResponseBody;
513
+ }
514
+ const msg = Type.fromObject({
515
+ partial_success: { rejected_spans: rejected, error_message: message ?? "" }
516
+ });
517
+ return Buffer.from(Type.encode(msg).finish());
513
518
  }
514
519
  async function decodeProtobufBody(buf) {
515
520
  const Type = loadProtobufDecoder();
@@ -624,6 +629,13 @@ async function buildOtelReceiver(opts) {
624
629
  }
625
630
  return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
626
631
  }
632
+ function sendOtlpPartial(reply, flavor, rejected, message) {
633
+ if (flavor === "protobuf") {
634
+ const buf = encodeProtobufResponseBody(rejected, message);
635
+ return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
636
+ }
637
+ return reply.code(200).header("content-type", "application/json").send({ partialSuccess: { rejectedSpans: rejected, errorMessage: message } });
638
+ }
627
639
  app.addContentTypeParser(
628
640
  "application/x-protobuf",
629
641
  { parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
@@ -651,6 +663,10 @@ async function buildOtelReceiver(opts) {
651
663
  }
652
664
  }
653
665
  enqueue(spans);
666
+ if (opts.classifyBareRoutability) {
667
+ const { rejected, message } = opts.classifyBareRoutability(spans);
668
+ if (rejected > 0) return sendOtlpPartial(reply, result.flavor, rejected, message);
669
+ }
654
670
  return sendOtlpSuccess(reply, result.flavor);
655
671
  });
656
672
  app.post("/projects/:project/v1/traces", async (req, reply) => {
@@ -4865,10 +4881,15 @@ function resolveDistToSrc(absFilepath, line) {
4865
4881
  }
4866
4882
  if (!entry) return null;
4867
4883
  try {
4868
- const pos = entry.consumer.originalPositionFor({
4869
- line: line !== void 0 && Number.isFinite(line) ? line : 1,
4870
- column: 0
4871
- });
4884
+ const queryLine = line !== void 0 && Number.isFinite(line) ? line : 1;
4885
+ let pos = entry.consumer.originalPositionFor({ line: queryLine, column: 0 });
4886
+ if (!pos || !pos.source) {
4887
+ pos = entry.consumer.originalPositionFor({
4888
+ line: queryLine,
4889
+ column: 0,
4890
+ bias: sourceMapJs.SourceMapConsumer.LEAST_UPPER_BOUND
4891
+ });
4892
+ }
4872
4893
  if (!pos || !pos.source) return null;
4873
4894
  const root = entry.consumer.sourceRoot ?? "";
4874
4895
  const resolved = import_node_path8.default.resolve(entry.dir, root, pos.source);
@@ -4877,6 +4898,9 @@ function resolveDistToSrc(absFilepath, line) {
4877
4898
  return null;
4878
4899
  }
4879
4900
  }
4901
+ function hasAdjacentSourceMap(absFilepath) {
4902
+ return sourceMapCache.get(absFilepath) != null;
4903
+ }
4880
4904
  function callSiteFromSpan(span, serviceNode, scanPath) {
4881
4905
  const filepath = codeFilepathOf(span.attributes);
4882
4906
  if (filepath === void 0) return null;
@@ -4892,7 +4916,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
4892
4916
  }
4893
4917
  const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
4894
4918
  if (!relPath) return null;
4895
- if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
4919
+ if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && !hasAdjacentSourceMap(abs) && serviceNode?.name) {
4896
4920
  warnNoSourceMaps(serviceNode.name);
4897
4921
  }
4898
4922
  const fn = codeFunctionOf(span.attributes);
@@ -5168,7 +5192,7 @@ function resolveServiceId(graph, host, env) {
5168
5192
  function frontierIdFor(host) {
5169
5193
  return (0, import_types8.frontierId)(host);
5170
5194
  }
5171
- function ensureServiceNode(graph, serviceName, env) {
5195
+ function resolveFusedServiceId(graph, serviceName, env) {
5172
5196
  const id = (0, import_types8.serviceId)(serviceName, env);
5173
5197
  if (graph.hasNode(id)) return id;
5174
5198
  const wanted = serviceName.toLowerCase();
@@ -5178,17 +5202,21 @@ function ensureServiceNode(graph, serviceName, env) {
5178
5202
  if (svc.discoveredVia === "otel") return false;
5179
5203
  return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
5180
5204
  });
5181
- if (extractedId) return extractedId;
5205
+ return extractedId ?? id;
5206
+ }
5207
+ function ensureServiceNode(graph, serviceName, env) {
5208
+ const resolved = resolveFusedServiceId(graph, serviceName, env);
5209
+ if (graph.hasNode(resolved)) return resolved;
5182
5210
  const node = {
5183
- id,
5211
+ id: resolved,
5184
5212
  type: import_types8.NodeType.ServiceNode,
5185
5213
  name: serviceName,
5186
5214
  language: "unknown",
5187
5215
  discoveredVia: "otel",
5188
5216
  ...env !== "unknown" ? { env } : {}
5189
5217
  };
5190
- graph.addNode(id, node);
5191
- return id;
5218
+ graph.addNode(resolved, node);
5219
+ return resolved;
5192
5220
  }
5193
5221
  function ensureInfraNode(graph, kind, name, provider) {
5194
5222
  const id = (0, import_types8.infraId)(kind, name);
@@ -5382,7 +5410,7 @@ async function appendErrorEvent(ctx, ev) {
5382
5410
  await import_node_fs7.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
5383
5411
  }
5384
5412
  function incidentAffectedNode(span, graph, scanPath) {
5385
- const sid = (0, import_types8.serviceId)(span.service, span.env);
5413
+ const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types8.serviceId)(span.service, span.env);
5386
5414
  const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
5387
5415
  const callSite = callSiteFromSpan(span, serviceNode, scanPath);
5388
5416
  if (callSite) {
@@ -6784,7 +6812,7 @@ function disambiguate(defs) {
6784
6812
  }
6785
6813
  async function addSymbols(graph, services) {
6786
6814
  const parsers = /* @__PURE__ */ new Map();
6787
- const parserForExt4 = (ext) => {
6815
+ const parserForExt5 = (ext) => {
6788
6816
  const grammar = GRAMMAR_BY_EXT[ext];
6789
6817
  if (!grammar) return null;
6790
6818
  let parser = parsers.get(ext);
@@ -6800,7 +6828,7 @@ async function addSymbols(graph, services) {
6800
6828
  for (const service of services) {
6801
6829
  const files = await loadSourceFiles(service.dir);
6802
6830
  for (const file of files) {
6803
- const parser = parserForExt4(import_node_path17.default.extname(file.path));
6831
+ const parser = parserForExt5(import_node_path17.default.extname(file.path));
6804
6832
  if (!parser) continue;
6805
6833
  const relPath = toPosix(import_node_path17.default.relative(service.dir, file.path));
6806
6834
  let defs;
@@ -6952,7 +6980,7 @@ function stringInner(node) {
6952
6980
  }
6953
6981
  async function addSymbolEdges(graph, services) {
6954
6982
  const parsers = /* @__PURE__ */ new Map();
6955
- const parserForExt4 = (ext) => {
6983
+ const parserForExt5 = (ext) => {
6956
6984
  const grammar = GRAMMAR_BY_EXT[ext];
6957
6985
  if (!grammar) return null;
6958
6986
  let parser = parsers.get(ext);
@@ -6969,7 +6997,7 @@ async function addSymbolEdges(graph, services) {
6969
6997
  const tsPaths = await loadTsPathConfig(service.dir);
6970
6998
  const files = await loadSourceFiles(service.dir);
6971
6999
  for (const file of files) {
6972
- const parser = parserForExt4(import_node_path18.default.extname(file.path));
7000
+ const parser = parserForExt5(import_node_path18.default.extname(file.path));
6973
7001
  if (!parser) continue;
6974
7002
  const relPath = toPosix(import_node_path18.default.relative(service.dir, file.path));
6975
7003
  const fileDir = import_node_path18.default.dirname(file.path);
@@ -7231,7 +7259,7 @@ function firstReferenceLines(root, wanted) {
7231
7259
  }
7232
7260
  async function addServerActions(graph, services) {
7233
7261
  const parsers = /* @__PURE__ */ new Map();
7234
- const parserForExt4 = (ext) => {
7262
+ const parserForExt5 = (ext) => {
7235
7263
  const grammar = GRAMMAR_BY_EXT[ext];
7236
7264
  if (!grammar) return null;
7237
7265
  let parser = parsers.get(ext);
@@ -7254,7 +7282,7 @@ async function addServerActions(graph, services) {
7254
7282
  const files = await loadSourceFiles(service.dir);
7255
7283
  for (const file of files) {
7256
7284
  if (isTestPath(file.path)) continue;
7257
- const parser = parserForExt4(import_node_path19.default.extname(file.path));
7285
+ const parser = parserForExt5(import_node_path19.default.extname(file.path));
7258
7286
  if (!parser) continue;
7259
7287
  const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
7260
7288
  let root;
@@ -7317,7 +7345,7 @@ async function addServerActions(graph, services) {
7317
7345
  }
7318
7346
  for (const file of files) {
7319
7347
  if (isTestPath(file.path)) continue;
7320
- const parser = parserForExt4(import_node_path19.default.extname(file.path));
7348
+ const parser = parserForExt5(import_node_path19.default.extname(file.path));
7321
7349
  if (!parser) continue;
7322
7350
  const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
7323
7351
  const fileDir = import_node_path19.default.dirname(file.path);
@@ -8281,6 +8309,7 @@ init_cjs_shims();
8281
8309
  var import_node_path30 = __toESM(require("path"), 1);
8282
8310
  var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
8283
8311
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
8312
+ var import_tree_sitter_typescript2 = __toESM(require("tree-sitter-typescript"), 1);
8284
8313
  var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
8285
8314
  var import_types20 = require("@neat.is/types");
8286
8315
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
@@ -8331,19 +8360,27 @@ function callsFromSource(source, parser, knownHosts) {
8331
8360
  }
8332
8361
  return out;
8333
8362
  }
8334
- function makeJsParser3() {
8335
- const p = new import_tree_sitter6.default();
8336
- p.setLanguage(import_tree_sitter_javascript4.default);
8337
- return p;
8338
- }
8339
- function makePyParser3() {
8340
- const p = new import_tree_sitter6.default();
8341
- p.setLanguage(import_tree_sitter_python3.default);
8342
- return p;
8363
+ var GRAMMAR_BY_EXT2 = {
8364
+ ".ts": import_tree_sitter_typescript2.default.typescript,
8365
+ ".tsx": import_tree_sitter_typescript2.default.tsx,
8366
+ ".js": import_tree_sitter_javascript4.default,
8367
+ ".jsx": import_tree_sitter_javascript4.default,
8368
+ ".mjs": import_tree_sitter_javascript4.default,
8369
+ ".cjs": import_tree_sitter_javascript4.default,
8370
+ ".py": import_tree_sitter_python3.default
8371
+ };
8372
+ function parserForExt(ext, cache) {
8373
+ const grammar = GRAMMAR_BY_EXT2[ext] ?? import_tree_sitter_javascript4.default;
8374
+ let parser = cache.get(grammar);
8375
+ if (!parser) {
8376
+ parser = new import_tree_sitter6.default();
8377
+ parser.setLanguage(grammar);
8378
+ cache.set(grammar, parser);
8379
+ }
8380
+ return parser;
8343
8381
  }
8344
8382
  async function addHttpCallEdges(graph, services) {
8345
- const jsParser = makeJsParser3();
8346
- const pyParser = makePyParser3();
8383
+ const parserCache = /* @__PURE__ */ new Map();
8347
8384
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
8348
8385
  let nodesAdded = 0;
8349
8386
  let edgesAdded = 0;
@@ -8352,7 +8389,7 @@ async function addHttpCallEdges(graph, services) {
8352
8389
  const seen = /* @__PURE__ */ new Set();
8353
8390
  for (const file of files) {
8354
8391
  if (isTestPath(file.path)) continue;
8355
- const parser = import_node_path30.default.extname(file.path) === ".py" ? pyParser : jsParser;
8392
+ const parser = parserForExt(import_node_path30.default.extname(file.path), parserCache);
8356
8393
  let sites;
8357
8394
  try {
8358
8395
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -8425,7 +8462,7 @@ function parseSource5(parser, source) {
8425
8462
  (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
8426
8463
  );
8427
8464
  }
8428
- function makeJsParser4() {
8465
+ function makeJsParser3() {
8429
8466
  const p = new import_tree_sitter7.default();
8430
8467
  p.setLanguage(import_tree_sitter_javascript5.default);
8431
8468
  return p;
@@ -8603,7 +8640,7 @@ function findRoute(entries, method, normalizedPath) {
8603
8640
  );
8604
8641
  }
8605
8642
  async function addRouteCallEdges(graph, services) {
8606
- const jsParser = makeJsParser4();
8643
+ const jsParser = makeJsParser3();
8607
8644
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
8608
8645
  const routeIndex = buildRouteIndex(graph);
8609
8646
  if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
@@ -8995,7 +9032,7 @@ var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"),
8995
9032
  var import_types27 = require("@neat.is/types");
8996
9033
  var FIRESTORE_CLIENT_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase\/firestore['"`]/;
8997
9034
  var FIRESTORE_ADMIN_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase-admin(?:\/firestore)?['"`]/;
8998
- function parserForExt(ext) {
9035
+ function parserForExt2(ext) {
8999
9036
  const p = new import_tree_sitter8.default();
9000
9037
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript6.default);
9001
9038
  return p;
@@ -9160,7 +9197,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9160
9197
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
9161
9198
  if (!hasClient && !hasAdmin) return [];
9162
9199
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
9163
- const tree = parseSource3(parserForExt(import_node_path37.default.extname(file.path)), file.content);
9200
+ const tree = parseSource3(parserForExt2(import_node_path37.default.extname(file.path)), file.content);
9164
9201
  const clientVars = firestoreClientVars(tree.rootNode);
9165
9202
  const collLine = /* @__PURE__ */ new Map();
9166
9203
  const writes = /* @__PURE__ */ new Map();
@@ -9577,7 +9614,7 @@ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
9577
9614
  var import_types29 = require("@neat.is/types");
9578
9615
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
9579
9616
  var PARSE_CHUNK6 = 16384;
9580
- function makePyParser4() {
9617
+ function makePyParser3() {
9581
9618
  const p = new import_tree_sitter9.default();
9582
9619
  p.setLanguage(import_tree_sitter_python4.default);
9583
9620
  return p;
@@ -9684,7 +9721,7 @@ function foreignKeyParentTable(call) {
9684
9721
  }
9685
9722
  function sqlalchemyForeignKeys(file, serviceDir) {
9686
9723
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
9687
- const tree = parseSource6(makePyParser4(), file.content);
9724
+ const tree = parseSource6(makePyParser3(), file.content);
9688
9725
  const out = [];
9689
9726
  const seen = /* @__PURE__ */ new Set();
9690
9727
  walk3(tree.rootNode, (node) => {
@@ -9721,7 +9758,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
9721
9758
  }
9722
9759
  function sqlalchemyEndpointsFromFile(file, serviceDir) {
9723
9760
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
9724
- const tree = parseSource6(makePyParser4(), file.content);
9761
+ const tree = parseSource6(makePyParser3(), file.content);
9725
9762
  const out = [];
9726
9763
  const seen = /* @__PURE__ */ new Set();
9727
9764
  const push = (name, line, columns) => {
@@ -9773,7 +9810,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
9773
9810
  function buildSqlalchemyModelRegistry(files) {
9774
9811
  const table = /* @__PURE__ */ new Map();
9775
9812
  const ambiguous = /* @__PURE__ */ new Set();
9776
- const parser = makePyParser4();
9813
+ const parser = makePyParser3();
9777
9814
  for (const file of files) {
9778
9815
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) continue;
9779
9816
  const tree = parseSource6(parser, file.content);
@@ -9822,7 +9859,7 @@ function importsModelName(content, name) {
9822
9859
  function pythonOrmCrossFileEndpoints(files, serviceDir) {
9823
9860
  const registry = buildSqlalchemyModelRegistry(files);
9824
9861
  if (registry.size === 0) return [];
9825
- const parser = makePyParser4();
9862
+ const parser = makePyParser3();
9826
9863
  const out = [];
9827
9864
  const seen = /* @__PURE__ */ new Set();
9828
9865
  for (const file of files) {
@@ -9860,7 +9897,7 @@ var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
9860
9897
  var import_types30 = require("@neat.is/types");
9861
9898
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
9862
9899
  var PARSE_CHUNK7 = 16384;
9863
- function makePyParser5() {
9900
+ function makePyParser4() {
9864
9901
  const p = new import_tree_sitter10.default();
9865
9902
  p.setLanguage(import_tree_sitter_python5.default);
9866
9903
  return p;
@@ -9921,7 +9958,7 @@ function readMeta(body) {
9921
9958
  }
9922
9959
  function djangoOrmEndpointsFromFile(file, serviceDir) {
9923
9960
  if (!DJANGO_IMPORT_RE.test(file.content)) return [];
9924
- const tree = parseSource7(makePyParser5(), file.content);
9961
+ const tree = parseSource7(makePyParser4(), file.content);
9925
9962
  const out = [];
9926
9963
  const seen = /* @__PURE__ */ new Set();
9927
9964
  const defaultAppLabel = import_node_path40.default.basename(import_node_path40.default.dirname(file.path));
@@ -9956,7 +9993,7 @@ var import_tree_sitter_javascript7 = __toESM(require("tree-sitter-javascript"),
9956
9993
  var import_types31 = require("@neat.is/types");
9957
9994
  var DRIZZLE_IMPORT_RE = /drizzle-orm/;
9958
9995
  var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
9959
- function parserForExt2(ext) {
9996
+ function parserForExt3(ext) {
9960
9997
  const p = new import_tree_sitter11.default();
9961
9998
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript7.default);
9962
9999
  return p;
@@ -10028,7 +10065,7 @@ function columnsFromObject(obj) {
10028
10065
  }
10029
10066
  function drizzleEndpointsFromFile(file, serviceDir) {
10030
10067
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10031
- const tree = parseSource3(parserForExt2(import_node_path41.default.extname(file.path)), file.content);
10068
+ const tree = parseSource3(parserForExt3(import_node_path41.default.extname(file.path)), file.content);
10032
10069
  const out = [];
10033
10070
  const seen = /* @__PURE__ */ new Set();
10034
10071
  const walk9 = (node) => {
@@ -10117,7 +10154,7 @@ function referencesTargetVar(call) {
10117
10154
  }
10118
10155
  function drizzleForeignKeys(file, serviceDir) {
10119
10156
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10120
- const tree = parseSource3(parserForExt2(import_node_path41.default.extname(file.path)), file.content);
10157
+ const tree = parseSource3(parserForExt3(import_node_path41.default.extname(file.path)), file.content);
10121
10158
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
10122
10159
  const out = [];
10123
10160
  const seen = /* @__PURE__ */ new Set();
@@ -12824,7 +12861,7 @@ var import_tree_sitter_javascript8 = __toESM(require("tree-sitter-javascript"),
12824
12861
  var import_types47 = require("@neat.is/types");
12825
12862
  var ZOD_IMPORT_RE = /\bzod\b/;
12826
12863
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
12827
- function parserForExt3(ext) {
12864
+ function parserForExt4(ext) {
12828
12865
  const p = new import_tree_sitter16.default();
12829
12866
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript8.default);
12830
12867
  return p;
@@ -12913,7 +12950,7 @@ function topLevelSchemas(root) {
12913
12950
  }
12914
12951
  function zodShapesFromFile(file, serviceDir) {
12915
12952
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
12916
- const tree = parseSource3(parserForExt3(import_node_path57.default.extname(file.path)), file.content);
12953
+ const tree = parseSource3(parserForExt4(import_node_path57.default.extname(file.path)), file.content);
12917
12954
  const out = [];
12918
12955
  const seen = /* @__PURE__ */ new Set();
12919
12956
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -18931,6 +18968,19 @@ async function startDaemon(opts = {}) {
18931
18968
  let otlpAddress = "";
18932
18969
  let daemonRecord = null;
18933
18970
  if (bind) {
18971
+ let bareSpanIsRoutable2 = function(serviceName) {
18972
+ if (singleProject) {
18973
+ const slot = slots.get(singleProject);
18974
+ if (!slot) {
18975
+ return !serviceName || serviceNameMatchesProject(serviceName, singleProject);
18976
+ }
18977
+ return spanBelongsToSingleProject(slot.graph, singleProject, serviceName);
18978
+ }
18979
+ const entries = [...slots.values()].map((s) => s.entry);
18980
+ const target = routeSpanToProject(serviceName, entries);
18981
+ return slots.has(target) || slots.has(DEFAULT_PROJECT);
18982
+ };
18983
+ var bareSpanIsRoutable = bareSpanIsRoutable2;
18934
18984
  const auth = readAuthEnv();
18935
18985
  const host = resolveHost(opts, Boolean(auth.authToken));
18936
18986
  const restPort = resolveRestPort(opts);
@@ -19028,6 +19078,20 @@ async function startDaemon(opts = {}) {
19028
19078
  const liveEntries = await listProjects().catch(() => []);
19029
19079
  let slot = slots.get(project);
19030
19080
  if (!slot) {
19081
+ if (singleProject && project === singleProject) {
19082
+ slot = await tryRecoverSlot({
19083
+ name: singleProject,
19084
+ path: singleProjectPath,
19085
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
19086
+ languages: [],
19087
+ status: "active"
19088
+ });
19089
+ if (!slot || slot.status !== "active") {
19090
+ warnDroppedSpan(singleProject, slot?.errorReason ?? "unknown");
19091
+ return null;
19092
+ }
19093
+ return slot;
19094
+ }
19031
19095
  await recordUnroutedSpan(serviceName, traceId);
19032
19096
  return null;
19033
19097
  }
@@ -19094,7 +19158,30 @@ async function startDaemon(opts = {}) {
19094
19158
  // host, rather than accepting it and dropping the batch. `slots` covers
19095
19159
  // active/recovering projects, `bootstrapStatus` the ones still
19096
19160
  // extracting; a foreign or wrong-cased project name matches neither.
19097
- isProjectRegistered: (project) => slots.has(project) || bootstrapStatus.has(project)
19161
+ // A single-project daemon owns exactly one project by definition, so its
19162
+ // own name always counts as registered even before loadAll populates the
19163
+ // slot — otherwise a scoped span arriving during cold-start would 404 and
19164
+ // be lost (OTLP does not retry 4xx). resolveSlotByName then builds it (#879).
19165
+ isProjectRegistered: (project) => slots.has(project) || bootstrapStatus.has(project) || singleProject !== void 0 && project === singleProject,
19166
+ // #881 — the bare `/v1/traces` route replies before the span is routed
19167
+ // (off the queue), so tell the receiver, per batch, how many spans will
19168
+ // land on no project. It keeps the 200 but reports those as
19169
+ // partialSuccess.rejectedSpans instead of an empty partialSuccess that an
19170
+ // exporter reads as full acceptance. Pure — the drop + unrouted-ledger
19171
+ // write still happen on the async onSpan path.
19172
+ classifyBareRoutability: (spans) => {
19173
+ let rejected = 0;
19174
+ for (const span of spans) {
19175
+ if (!bareSpanIsRoutable2(span.service)) rejected++;
19176
+ }
19177
+ if (rejected === 0) return { rejected: 0 };
19178
+ const noun = rejected === 1 ? "span" : "spans";
19179
+ const verb = rejected === 1 ? "was" : "were";
19180
+ return {
19181
+ rejected,
19182
+ message: `${rejected} ${noun} matched no project on this daemon and ${verb} dropped. Export to /projects/<project>/v1/traces, or check the exporter's service.name.`
19183
+ };
19184
+ }
19098
19185
  });
19099
19186
  otlpAddress = await listenSteppingOtlp(otlpApp, otlpPort, host);
19100
19187
  console.log(`neatd: OTLP listening on ${otlpAddress}/v1/traces`);