@neat.is/core 0.3.8 → 0.4.2
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/{chunk-LQ3JFBTX.js → chunk-CB2UK4EH.js} +139 -41
- package/dist/chunk-CB2UK4EH.js.map +1 -0
- package/dist/{chunk-V4TU7OKZ.js → chunk-D5PIJFBE.js} +2 -2
- package/dist/{chunk-7TYESDAI.js → chunk-KYRIQIPG.js} +48 -11
- package/dist/chunk-KYRIQIPG.js.map +1 -0
- package/dist/{chunk-CZ3T6TE2.js → chunk-NTQHMXWE.js} +239 -71
- package/dist/chunk-NTQHMXWE.js.map +1 -0
- package/dist/cli.cjs +1209 -176
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +4 -1
- package/dist/cli.d.ts +4 -1
- package/dist/cli.js +940 -99
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +418 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +427 -131
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-S3AENOZ6.js → otel-grpc-QTX2YQJZ.js} +3 -3
- package/dist/server.cjs +401 -203
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +5 -4
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-7TYESDAI.js.map +0 -1
- package/dist/chunk-CZ3T6TE2.js.map +0 -1
- package/dist/chunk-LQ3JFBTX.js.map +0 -1
- /package/dist/{chunk-V4TU7OKZ.js.map → chunk-D5PIJFBE.js.map} +0 -0
- /package/dist/{otel-grpc-S3AENOZ6.js.map → otel-grpc-QTX2YQJZ.js.map} +0 -0
|
@@ -15,22 +15,44 @@ import {
|
|
|
15
15
|
startPersistLoop,
|
|
16
16
|
touchLastSeen,
|
|
17
17
|
writeAtomically
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-NTQHMXWE.js";
|
|
19
19
|
import {
|
|
20
20
|
assertBindAuthority,
|
|
21
21
|
buildOtelReceiver,
|
|
22
22
|
readAuthEnv
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-KYRIQIPG.js";
|
|
24
24
|
|
|
25
25
|
// src/daemon.ts
|
|
26
|
+
import { promises as fs2 } from "fs";
|
|
27
|
+
import path2 from "path";
|
|
28
|
+
|
|
29
|
+
// src/unrouted.ts
|
|
26
30
|
import { promises as fs } from "fs";
|
|
27
31
|
import path from "path";
|
|
32
|
+
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
33
|
+
return {
|
|
34
|
+
timestamp: now.toISOString(),
|
|
35
|
+
reason: "no-project-match",
|
|
36
|
+
service_name: serviceName ?? null,
|
|
37
|
+
traceId: traceId ?? null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function appendUnroutedSpan(neatHome, record) {
|
|
41
|
+
const target = path.join(neatHome, "errors.ndjson");
|
|
42
|
+
await fs.mkdir(neatHome, { recursive: true });
|
|
43
|
+
await fs.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
44
|
+
}
|
|
45
|
+
function unroutedErrorsPath(neatHome) {
|
|
46
|
+
return path.join(neatHome, "errors.ndjson");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/daemon.ts
|
|
28
50
|
function neatHomeFor(opts) {
|
|
29
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
51
|
+
if (opts.neatHome && opts.neatHome.length > 0) return path2.resolve(opts.neatHome);
|
|
30
52
|
const env = process.env.NEAT_HOME;
|
|
31
|
-
if (env && env.length > 0) return
|
|
53
|
+
if (env && env.length > 0) return path2.resolve(env);
|
|
32
54
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
33
|
-
return
|
|
55
|
+
return path2.join(home, ".neat");
|
|
34
56
|
}
|
|
35
57
|
function routeSpanToProject(serviceName, projects) {
|
|
36
58
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -65,9 +87,9 @@ function isTokenContained(needle, haystack) {
|
|
|
65
87
|
return tokens.includes(needle);
|
|
66
88
|
}
|
|
67
89
|
async function bootstrapProject(entry) {
|
|
68
|
-
const paths = pathsForProject(entry.name,
|
|
90
|
+
const paths = pathsForProject(entry.name, path2.join(entry.path, "neat-out"));
|
|
69
91
|
try {
|
|
70
|
-
const stat = await
|
|
92
|
+
const stat = await fs2.stat(entry.path);
|
|
71
93
|
if (!stat.isDirectory()) {
|
|
72
94
|
throw new Error(`registered path ${entry.path} is not a directory`);
|
|
73
95
|
}
|
|
@@ -122,27 +144,30 @@ function resolveOtlpPort(opts) {
|
|
|
122
144
|
}
|
|
123
145
|
return 4318;
|
|
124
146
|
}
|
|
125
|
-
function resolveHost(opts) {
|
|
147
|
+
function resolveHost(opts, authTokenSet) {
|
|
126
148
|
if (opts.host && opts.host.length > 0) return opts.host;
|
|
127
149
|
const env = process.env.HOST;
|
|
128
150
|
if (env && env.length > 0) return env;
|
|
151
|
+
if (!authTokenSet) return "127.0.0.1";
|
|
129
152
|
return "0.0.0.0";
|
|
130
153
|
}
|
|
131
154
|
async function startDaemon(opts = {}) {
|
|
132
155
|
const home = neatHomeFor(opts);
|
|
133
156
|
const regPath = registryPath();
|
|
134
157
|
try {
|
|
135
|
-
await
|
|
158
|
+
await fs2.access(regPath);
|
|
136
159
|
} catch {
|
|
137
160
|
throw new Error(
|
|
138
161
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
139
162
|
);
|
|
140
163
|
}
|
|
141
|
-
const pidPath =
|
|
164
|
+
const pidPath = path2.join(home, "neatd.pid");
|
|
142
165
|
await writeAtomically(pidPath, `${process.pid}
|
|
143
166
|
`);
|
|
144
167
|
const slots = /* @__PURE__ */ new Map();
|
|
145
168
|
const registry = new Projects();
|
|
169
|
+
const bootstrapStatus = /* @__PURE__ */ new Map();
|
|
170
|
+
const bootstrapStartedAt = /* @__PURE__ */ new Map();
|
|
146
171
|
const DROP_WARN_INTERVAL_MS = 6e4;
|
|
147
172
|
const lastDropWarnAt = /* @__PURE__ */ new Map();
|
|
148
173
|
function warnDroppedSpan(project, reason) {
|
|
@@ -154,6 +179,22 @@ async function startDaemon(opts = {}) {
|
|
|
154
179
|
`[neatd] dropping span for project "${project}" \u2014 project status: broken (${reason}). Run \`neatd reload\` to retry bootstrap.`
|
|
155
180
|
);
|
|
156
181
|
}
|
|
182
|
+
const unroutedPath = unroutedErrorsPath(home);
|
|
183
|
+
const lastUnroutedWarnAt = /* @__PURE__ */ new Map();
|
|
184
|
+
async function recordUnroutedSpan(serviceName, traceId) {
|
|
185
|
+
const key = serviceName ?? "<missing>";
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
try {
|
|
188
|
+
await appendUnroutedSpan(home, buildUnroutedSpanRecord(serviceName, traceId, new Date(now)));
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
const prev = lastUnroutedWarnAt.get(key) ?? 0;
|
|
192
|
+
if (now - prev < DROP_WARN_INTERVAL_MS) return;
|
|
193
|
+
lastUnroutedWarnAt.set(key, now);
|
|
194
|
+
console.warn(
|
|
195
|
+
`[neatd] dropping span \u2014 service.name "${key}" matches no registered project and no \`default\` project exists. See ${unroutedPath}.`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
157
198
|
function upsertRegistryFromSlot(slot) {
|
|
158
199
|
if (slot.status !== "active") return;
|
|
159
200
|
registry.set(slot.entry.name, {
|
|
@@ -182,34 +223,43 @@ async function startDaemon(opts = {}) {
|
|
|
182
223
|
return slots.get(entry.name);
|
|
183
224
|
}
|
|
184
225
|
}
|
|
226
|
+
async function bootstrapOne(entry) {
|
|
227
|
+
bootstrapStatus.set(entry.name, "bootstrapping");
|
|
228
|
+
bootstrapStartedAt.set(entry.name, Date.now());
|
|
229
|
+
try {
|
|
230
|
+
const slot = await bootstrapProject(entry);
|
|
231
|
+
slots.set(entry.name, slot);
|
|
232
|
+
upsertRegistryFromSlot(slot);
|
|
233
|
+
bootstrapStatus.set(entry.name, slot.status === "broken" ? "broken" : "active");
|
|
234
|
+
if (slot.status === "broken") {
|
|
235
|
+
console.warn(`neatd: project "${entry.name}" broken \u2014 ${slot.errorReason}`);
|
|
236
|
+
} else {
|
|
237
|
+
console.log(`neatd: project "${entry.name}" active (${entry.path})`);
|
|
238
|
+
}
|
|
239
|
+
} catch (err) {
|
|
240
|
+
bootstrapStatus.set(entry.name, "broken");
|
|
241
|
+
console.warn(
|
|
242
|
+
`neatd: project "${entry.name}" failed to bootstrap \u2014 ${err.message}`
|
|
243
|
+
);
|
|
244
|
+
await setStatus(entry.name, "broken").catch(() => {
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
185
248
|
async function loadAll() {
|
|
186
249
|
const projects = await listProjects();
|
|
187
250
|
const seen = /* @__PURE__ */ new Set();
|
|
251
|
+
const pending = [];
|
|
188
252
|
for (const entry of projects) {
|
|
189
253
|
seen.add(entry.name);
|
|
190
254
|
const existing = slots.get(entry.name);
|
|
191
255
|
if (existing) {
|
|
192
256
|
if (existing.status === "broken") {
|
|
193
|
-
|
|
257
|
+
pending.push(tryRecoverSlot(entry).then(() => {
|
|
258
|
+
}));
|
|
194
259
|
}
|
|
195
260
|
continue;
|
|
196
261
|
}
|
|
197
|
-
|
|
198
|
-
const slot = await bootstrapProject(entry);
|
|
199
|
-
slots.set(entry.name, slot);
|
|
200
|
-
upsertRegistryFromSlot(slot);
|
|
201
|
-
if (slot.status === "broken") {
|
|
202
|
-
console.warn(`neatd: project "${entry.name}" broken \u2014 ${slot.errorReason}`);
|
|
203
|
-
} else {
|
|
204
|
-
console.log(`neatd: project "${entry.name}" active (${entry.path})`);
|
|
205
|
-
}
|
|
206
|
-
} catch (err) {
|
|
207
|
-
console.warn(
|
|
208
|
-
`neatd: project "${entry.name}" failed to bootstrap \u2014 ${err.message}`
|
|
209
|
-
);
|
|
210
|
-
await setStatus(entry.name, "broken").catch(() => {
|
|
211
|
-
});
|
|
212
|
-
}
|
|
262
|
+
pending.push(bootstrapOne(entry));
|
|
213
263
|
}
|
|
214
264
|
for (const [name, slot] of [...slots.entries()]) {
|
|
215
265
|
if (seen.has(name)) continue;
|
|
@@ -218,26 +268,45 @@ async function startDaemon(opts = {}) {
|
|
|
218
268
|
} catch {
|
|
219
269
|
}
|
|
220
270
|
slots.delete(name);
|
|
271
|
+
bootstrapStatus.delete(name);
|
|
272
|
+
bootstrapStartedAt.delete(name);
|
|
221
273
|
console.log(`neatd: project "${name}" removed from registry \u2014 stopped`);
|
|
222
274
|
}
|
|
275
|
+
await Promise.allSettled(pending);
|
|
276
|
+
}
|
|
277
|
+
const initialEntries = await listProjects().catch(() => []);
|
|
278
|
+
for (const entry of initialEntries) {
|
|
279
|
+
bootstrapStatus.set(entry.name, "bootstrapping");
|
|
280
|
+
bootstrapStartedAt.set(entry.name, Date.now());
|
|
223
281
|
}
|
|
224
|
-
await loadAll();
|
|
225
282
|
const bind = opts.bindListeners !== false;
|
|
226
283
|
let restApp = null;
|
|
227
284
|
let otlpApp = null;
|
|
228
285
|
let restAddress = "";
|
|
229
286
|
let otlpAddress = "";
|
|
230
287
|
if (bind) {
|
|
231
|
-
const
|
|
288
|
+
const auth = readAuthEnv();
|
|
289
|
+
const host = resolveHost(opts, Boolean(auth.authToken));
|
|
232
290
|
const restPort = resolveRestPort(opts);
|
|
233
291
|
const otlpPort = resolveOtlpPort(opts);
|
|
234
|
-
const auth = readAuthEnv();
|
|
235
292
|
assertBindAuthority(host, auth.authToken);
|
|
236
293
|
try {
|
|
237
294
|
restApp = await buildApi({
|
|
238
295
|
projects: registry,
|
|
239
296
|
authToken: auth.authToken,
|
|
240
|
-
trustProxy: auth.trustProxy
|
|
297
|
+
trustProxy: auth.trustProxy,
|
|
298
|
+
publicRead: auth.publicRead,
|
|
299
|
+
bootstrap: {
|
|
300
|
+
status: (name) => bootstrapStatus.get(name),
|
|
301
|
+
list: () => {
|
|
302
|
+
const now = Date.now();
|
|
303
|
+
return [...bootstrapStatus.entries()].map(([name, status]) => ({
|
|
304
|
+
name,
|
|
305
|
+
status,
|
|
306
|
+
elapsedMs: now - (bootstrapStartedAt.get(name) ?? now)
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
241
310
|
});
|
|
242
311
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
243
312
|
console.log(`neatd: REST listening on ${restAddress}`);
|
|
@@ -250,17 +319,20 @@ async function startDaemon(opts = {}) {
|
|
|
250
319
|
}
|
|
251
320
|
if (restApp) await restApp.close().catch(() => {
|
|
252
321
|
});
|
|
253
|
-
await
|
|
322
|
+
await fs2.unlink(pidPath).catch(() => {
|
|
254
323
|
});
|
|
255
324
|
throw new Error(
|
|
256
325
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
257
326
|
);
|
|
258
327
|
}
|
|
259
|
-
async function resolveTargetSlot(serviceName) {
|
|
328
|
+
async function resolveTargetSlot(serviceName, traceId) {
|
|
260
329
|
const liveEntries = await listProjects().catch(() => []);
|
|
261
330
|
const target = routeSpanToProject(serviceName, liveEntries);
|
|
262
331
|
let slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT);
|
|
263
|
-
if (!slot)
|
|
332
|
+
if (!slot) {
|
|
333
|
+
await recordUnroutedSpan(serviceName, traceId);
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
264
336
|
if (slot.status === "broken") {
|
|
265
337
|
const entry = liveEntries.find((e) => e.name === slot.entry.name);
|
|
266
338
|
if (entry) {
|
|
@@ -278,7 +350,7 @@ async function startDaemon(opts = {}) {
|
|
|
278
350
|
authToken: auth.otelToken,
|
|
279
351
|
trustProxy: auth.trustProxy,
|
|
280
352
|
onSpan: async (span) => {
|
|
281
|
-
const slot = await resolveTargetSlot(span.service);
|
|
353
|
+
const slot = await resolveTargetSlot(span.service, span.traceId);
|
|
282
354
|
if (!slot) return;
|
|
283
355
|
await handleSpan(
|
|
284
356
|
{
|
|
@@ -292,7 +364,7 @@ async function startDaemon(opts = {}) {
|
|
|
292
364
|
);
|
|
293
365
|
},
|
|
294
366
|
onErrorSpanSync: async (span) => {
|
|
295
|
-
const slot = await resolveTargetSlot(span.service);
|
|
367
|
+
const slot = await resolveTargetSlot(span.service, span.traceId);
|
|
296
368
|
if (!slot) return;
|
|
297
369
|
await makeErrorSpanWriter(slot.paths.errorsPath)(span);
|
|
298
370
|
}
|
|
@@ -310,14 +382,17 @@ async function startDaemon(opts = {}) {
|
|
|
310
382
|
});
|
|
311
383
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
312
384
|
});
|
|
313
|
-
await
|
|
385
|
+
await fs2.unlink(pidPath).catch(() => {
|
|
314
386
|
});
|
|
315
387
|
throw new Error(
|
|
316
388
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
317
389
|
);
|
|
318
390
|
}
|
|
319
391
|
}
|
|
320
|
-
|
|
392
|
+
const initialBootstrap = loadAll().catch((err) => {
|
|
393
|
+
console.warn(`neatd: initial bootstrap pass failed \u2014 ${err.message}`);
|
|
394
|
+
});
|
|
395
|
+
let reloading = initialBootstrap;
|
|
321
396
|
const reload = async () => {
|
|
322
397
|
if (reloading) return reloading;
|
|
323
398
|
reloading = (async () => {
|
|
@@ -329,6 +404,20 @@ async function startDaemon(opts = {}) {
|
|
|
329
404
|
})();
|
|
330
405
|
return reloading;
|
|
331
406
|
};
|
|
407
|
+
void initialBootstrap.finally(() => {
|
|
408
|
+
if (reloading === initialBootstrap) reloading = null;
|
|
409
|
+
});
|
|
410
|
+
const tracker = {
|
|
411
|
+
status: (name) => bootstrapStatus.get(name),
|
|
412
|
+
list: () => {
|
|
413
|
+
const now = Date.now();
|
|
414
|
+
return [...bootstrapStatus.entries()].map(([name, status]) => ({
|
|
415
|
+
name,
|
|
416
|
+
status,
|
|
417
|
+
elapsedMs: now - (bootstrapStartedAt.get(name) ?? now)
|
|
418
|
+
}));
|
|
419
|
+
}
|
|
420
|
+
};
|
|
332
421
|
const sighupHandler = () => {
|
|
333
422
|
void reload().catch((err) => {
|
|
334
423
|
console.warn(`neatd: SIGHUP reload failed \u2014 ${err.message}`);
|
|
@@ -350,14 +439,23 @@ async function startDaemon(opts = {}) {
|
|
|
350
439
|
} catch {
|
|
351
440
|
}
|
|
352
441
|
}
|
|
353
|
-
await
|
|
442
|
+
await fs2.unlink(pidPath).catch(() => {
|
|
354
443
|
});
|
|
355
444
|
};
|
|
356
|
-
return {
|
|
445
|
+
return {
|
|
446
|
+
slots,
|
|
447
|
+
reload,
|
|
448
|
+
stop,
|
|
449
|
+
pidPath,
|
|
450
|
+
restAddress,
|
|
451
|
+
otlpAddress,
|
|
452
|
+
bootstrap: tracker,
|
|
453
|
+
initialBootstrap
|
|
454
|
+
};
|
|
357
455
|
}
|
|
358
456
|
|
|
359
457
|
export {
|
|
360
458
|
routeSpanToProject,
|
|
361
459
|
startDaemon
|
|
362
460
|
};
|
|
363
|
-
//# sourceMappingURL=chunk-
|
|
461
|
+
//# sourceMappingURL=chunk-CB2UK4EH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon.ts","../src/unrouted.ts"],"sourcesContent":["/**\n * Multi-project daemon (ADR-049).\n *\n * Single long-lived process watching every project in the machine-level registry.\n * Per-project graph isolation: each registered project owns its own\n * `MultiDirectedGraph` slot keyed by name (ADR-026), and a failure during\n * one project's bootstrap is logged + marked `broken` without taking down\n * the rest of the daemon.\n *\n * MVP scope (v0.2.5):\n * - Read registry; refuse to boot when it's missing.\n * - Write PID at `~/.neat/neatd.pid` for external supervisors.\n * - Per project: load any existing snapshot, run initial extraction,\n * start a per-project persist loop.\n * - SIGHUP triggers a reload — re-reads the registry, picks up new\n * projects, drops removed ones, leaves untouched ones in place.\n * - Provide `routeSpanToProject(serviceName, projects)` for OTel ingest\n * to dispatch by `service.name` across registered projects, falling\n * back to `default` for unknown services per ADR-033.\n *\n * Out of MVP scope (deferred):\n * - Live OTel listener wiring per project — daemon exposes the routing\n * primitive; the actual receiver attachment lands alongside v0.2.6.\n * - Policy reload on `policy.json` mtime — `startWatch` already does this\n * per-project; the daemon-level loop reuses that machinery in a follow-up.\n * - Auto-restart on crash. PID file is the supervisor handoff.\n */\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { FastifyInstance } from 'fastify'\nimport { DEFAULT_PROJECT, getGraph, resetGraph, type NeatGraph } from './graph.js'\nimport { extractFromDirectory } from './extract.js'\nimport { loadGraphFromDisk, startPersistLoop } from './persist.js'\nimport { Projects, pathsForProject, type ProjectPaths } from './projects.js'\nimport { buildApi } from './api.js'\nimport { buildOtelReceiver } from './otel.js'\nimport { handleSpan, makeErrorSpanWriter } from './ingest.js'\nimport {\n listProjects,\n registryPath,\n setStatus,\n touchLastSeen,\n writeAtomically,\n} from './registry.js'\nimport { assertBindAuthority, readAuthEnv } from './auth.js'\nimport {\n appendUnroutedSpan,\n buildUnroutedSpanRecord,\n unroutedErrorsPath,\n} from './unrouted.js'\nimport type { RegistryEntry } from '@neat.is/types'\n\nexport interface DaemonOptions {\n // Defaults to `~/.neat/`. Honors NEAT_HOME the same way registry.ts does.\n // Tests override via NEAT_HOME and don't pass this directly.\n neatHome?: string\n // ADR-063 — bind targets. Defaults to PORT (8080) / OTEL_PORT (4318) env\n // vars, matching server.ts. Tests pass 0 to get ephemeral ports.\n restPort?: number\n otlpPort?: number\n // ADR-063 — bind host. Defaults to HOST env (0.0.0.0).\n host?: string\n // ADR-063 — opt out of binding entirely (e.g. integration tests that\n // exercise daemon slots without needing the listeners). Production\n // `neatd start` never sets this.\n bindListeners?: boolean\n}\n\nexport interface ProjectSlot {\n entry: RegistryEntry\n graph: NeatGraph\n outPath: string\n paths: ProjectPaths\n stopPersist: () => void\n status: 'active' | 'broken'\n errorReason?: string\n}\n\n// Issue #340 — per-project bootstrap state surface. The REST listener\n// flips to live the moment `app.listen()` returns; per-project routes\n// branch on this rather than waiting for every registered project's\n// extractFromDirectory pass to finish.\nexport type BootstrapPhase = 'bootstrapping' | 'active' | 'broken'\n\nexport interface BootstrapTracker {\n status: (name: string) => BootstrapPhase | undefined\n list: () => Array<{ name: string; status: BootstrapPhase; elapsedMs: number }>\n}\n\nexport interface DaemonHandle {\n // The slots currently being managed, keyed by project name. Tests inspect\n // this to assert isolation properties.\n slots: Map<string, ProjectSlot>\n // Re-read the registry. New entries get bootstrapped, removed ones get\n // their persist loops stopped, existing ones stay running.\n reload: () => Promise<void>\n // Graceful shutdown — stop every project's persist loop and remove the\n // PID file.\n stop: () => Promise<void>\n // Path to the PID file the daemon owns. Useful for test assertions.\n pidPath: string\n // ADR-063 — addresses where consumers reach the daemon. Empty string when\n // bindListeners is false. REST is the Fastify app's listening address;\n // OTLP is the receiver's.\n restAddress: string\n otlpAddress: string\n // Issue #340 — per-project bootstrap status, surfaced for orchestrator\n // poll loops and tests.\n bootstrap: BootstrapTracker\n // Resolves when the daemon's initial bootstrap pass has settled. Tests\n // that probe project-scoped routes immediately after startDaemon await\n // this; production callers use /health.\n initialBootstrap: Promise<void>\n}\n\nfunction neatHomeFor(opts: DaemonOptions): string {\n if (opts.neatHome && opts.neatHome.length > 0) return path.resolve(opts.neatHome)\n const env = process.env.NEAT_HOME\n if (env && env.length > 0) return path.resolve(env)\n const home = process.env.HOME ?? process.env.USERPROFILE ?? ''\n return path.join(home, '.neat')\n}\n\n/**\n * Resolve which project's graph an OTel span belongs to. Looks up the\n * `service.name` against the registry and returns the matching project's\n * name, or `DEFAULT_PROJECT` for unknown services so the FrontierNode\n * auto-creation flow keeps working per ADR-033.\n *\n * Pure function. Daemon callers pass a snapshot of the registry to avoid\n * per-span fs reads.\n *\n * Matching order (ADR-072 — real-world `service.name` rarely equals project\n * name; monorepos publish per-package names like `brief-api` under a\n * project named `brief`):\n *\n * 1. Exact: `entry.name === serviceName`.\n * 2. Hyphen/underscore-separated prefix: `entry.name` is a leading token\n * of `serviceName` (`brief` matches `brief-api`, `brief_worker`).\n * Longest-match wins so `brief-api` beats `brief` when both are\n * registered.\n * 3. Containment as a separator-delimited token (`api` inside\n * `brief-api-staging`).\n *\n * Routing eligibility (ADR-071):\n * - `active` matches at every pass (the steady-state path).\n * - `broken` also matches — the daemon needs the span to reach the broken\n * slot so the ingest-time auto-recover path can attempt a bootstrap and\n * lift the project back to `active`. The router only chooses the target;\n * whether the span actually lands is the ingest handler's decision.\n * - `paused` is intentionally not routed; the operator paused it on\n * purpose, so the span falls through to the default-project flow.\n *\n * Falls back to `DEFAULT_PROJECT` when nothing matches.\n */\nexport function routeSpanToProject(\n serviceName: string | undefined,\n projects: ReadonlyArray<RegistryEntry>,\n): string {\n if (!serviceName) return DEFAULT_PROJECT\n // Pass 1 — exact match.\n for (const entry of projects) {\n if (entry.status === 'paused') continue\n if (entry.name === serviceName) return entry.name\n }\n // Pass 2 — hyphen/underscore-separated prefix. Longest project name wins\n // so a registered `brief-api` outranks a registered `brief` when the\n // span's service.name is `brief-api-staging`.\n const candidates: RegistryEntry[] = []\n for (const entry of projects) {\n if (entry.status === 'paused') continue\n if (isTokenPrefix(entry.name, serviceName)) candidates.push(entry)\n }\n if (candidates.length > 0) {\n candidates.sort((a, b) => b.name.length - a.name.length)\n return candidates[0]!.name\n }\n // Pass 3 — containment as a separator-delimited token. Last-resort match\n // for `api` inside `brief-api-staging` when only `api` is registered.\n for (const entry of projects) {\n if (entry.status === 'paused') continue\n if (isTokenContained(entry.name, serviceName)) return entry.name\n }\n return DEFAULT_PROJECT\n}\n\n// True when `prefix` matches the first hyphen/underscore-separated token(s)\n// of `full`. `brief` matches `brief-api`, `brief_worker`, but not `briefcase`.\nfunction isTokenPrefix(prefix: string, full: string): boolean {\n if (prefix.length >= full.length) return false\n if (!full.startsWith(prefix)) return false\n const sep = full.charAt(prefix.length)\n return sep === '-' || sep === '_'\n}\n\n// True when `needle` appears in `haystack` bordered by separators on both\n// sides (so it's a complete token, not a substring of a longer word).\nfunction isTokenContained(needle: string, haystack: string): boolean {\n if (!haystack.includes(needle)) return false\n const tokens = haystack.split(/[-_]/)\n return tokens.includes(needle)\n}\n\nasync function bootstrapProject(entry: RegistryEntry): Promise<ProjectSlot> {\n const paths = pathsForProject(entry.name, path.join(entry.path, 'neat-out'))\n\n // Path missing on disk → mark broken and surface the reason. Daemon\n // continues with the rest of the registry.\n try {\n const stat = await fs.stat(entry.path)\n if (!stat.isDirectory()) {\n throw new Error(`registered path ${entry.path} is not a directory`)\n }\n } catch (err) {\n await setStatus(entry.name, 'broken').catch(() => {})\n return {\n entry,\n // Empty graph is fine — `slots` keeps the entry visible in `status`\n // output; nothing routes to it because it's not 'active'.\n graph: getGraph(`__broken__:${entry.name}`),\n outPath: '',\n paths,\n stopPersist: () => {},\n status: 'broken',\n errorReason: (err as Error).message,\n }\n }\n\n // Use the project name as the in-memory graph key. Any prior contents\n // are wiped because the daemon owns the slot for the lifetime of this\n // bootstrap (ADR-030 — mutation authority).\n resetGraph(entry.name)\n const graph = getGraph(entry.name)\n const outPath = paths.snapshotPath\n\n await loadGraphFromDisk(graph, outPath)\n await extractFromDirectory(graph, entry.path)\n const stopPersist = startPersistLoop(graph, outPath)\n await touchLastSeen(entry.name).catch(() => {})\n\n return {\n entry,\n graph,\n outPath,\n paths,\n stopPersist,\n status: 'active',\n }\n}\n\nfunction resolveRestPort(opts: DaemonOptions): number {\n if (typeof opts.restPort === 'number') return opts.restPort\n const env = process.env.PORT\n if (env && env.length > 0) {\n const n = Number.parseInt(env, 10)\n if (Number.isFinite(n)) return n\n }\n return 8080\n}\n\nfunction resolveOtlpPort(opts: DaemonOptions): number {\n if (typeof opts.otlpPort === 'number') return opts.otlpPort\n const env = process.env.OTEL_PORT\n if (env && env.length > 0) {\n const n = Number.parseInt(env, 10)\n if (Number.isFinite(n)) return n\n }\n return 4318\n}\n\nfunction resolveHost(opts: DaemonOptions, authTokenSet: boolean): string {\n if (opts.host && opts.host.length > 0) return opts.host\n const env = process.env.HOST\n if (env && env.length > 0) return env\n // Issue #341 — loopback-only default when the operator hasn't set a token.\n // Public-bind on a clean install demanded one before binding could\n // succeed, so the npx-`neat .` first-touch path used to refuse to come up;\n // pinning to 127.0.0.1 lets that path bind cleanly. Anyone wanting a\n // public bind sets `NEAT_AUTH_TOKEN` (and `HOST=0.0.0.0` if they want it\n // spelled out). `assertBindAuthority` stays exactly as it is — the\n // contract is right; the default was wrong.\n if (!authTokenSet) return '127.0.0.1'\n return '0.0.0.0'\n}\n\nexport async function startDaemon(opts: DaemonOptions = {}): Promise<DaemonHandle> {\n const home = neatHomeFor(opts)\n const regPath = registryPath()\n // Graceful degradation per ADR-049 #6: missing registry refuses to boot\n // with a clear error rather than silently coming up empty.\n try {\n await fs.access(regPath)\n } catch {\n throw new Error(\n `neatd: registry not found at ${regPath}. Run \\`neat init <path>\\` to register a project before starting the daemon.`,\n )\n }\n\n const pidPath = path.join(home, 'neatd.pid')\n await writeAtomically(pidPath, `${process.pid}\\n`)\n\n const slots = new Map<string, ProjectSlot>()\n // Projects registry mirrors slots for the REST listener (ADR-063). buildApi\n // reads from this; we keep it in sync as slots come and go.\n const registry = new Projects()\n // Issue #340 — per-project bootstrap status. Populated from the registry\n // before the listener binds so the REST handlers can return 503 instead of\n // 404 for projects still extracting.\n const bootstrapStatus = new Map<string, BootstrapPhase>()\n const bootstrapStartedAt = new Map<string, number>()\n\n // Rate-limit the dropped-span warning to one log line per project per\n // 60 seconds. OTel exporters retry on a tight cadence; without this we\n // flood the console with the same line per batch when a broken project\n // is sitting in the registry.\n const DROP_WARN_INTERVAL_MS = 60_000\n const lastDropWarnAt = new Map<string, number>()\n function warnDroppedSpan(project: string, reason: string): void {\n const now = Date.now()\n const prev = lastDropWarnAt.get(project) ?? 0\n if (now - prev < DROP_WARN_INTERVAL_MS) return\n lastDropWarnAt.set(project, now)\n console.warn(\n `[neatd] dropping span for project \"${project}\" — project status: broken (${reason}). Run \\`neatd reload\\` to retry bootstrap.`,\n )\n }\n\n // v0.4.1 / refs #339 — when a span's `service.name` doesn't match any\n // registered project AND no `default` project is registered, the span has\n // nowhere to land. We still return 200 on the receiver (OTel spec) but the\n // event lands in <NEAT_HOME>/errors.ndjson so the next operator can see\n // what happened instead of the daemon's stderr being the only signal.\n // Same rate limit as the broken-project warning, keyed by service.name.\n const unroutedPath = unroutedErrorsPath(home)\n const lastUnroutedWarnAt = new Map<string, number>()\n async function recordUnroutedSpan(\n serviceName: string | undefined,\n traceId: string | undefined,\n ): Promise<void> {\n const key = serviceName ?? '<missing>'\n const now = Date.now()\n try {\n await appendUnroutedSpan(home, buildUnroutedSpanRecord(serviceName, traceId, new Date(now)))\n } catch {\n // best-effort — failing to log shouldn't cascade into receiver failure.\n }\n const prev = lastUnroutedWarnAt.get(key) ?? 0\n if (now - prev < DROP_WARN_INTERVAL_MS) return\n lastUnroutedWarnAt.set(key, now)\n console.warn(\n `[neatd] dropping span — service.name \"${key}\" matches no registered project and no \\`default\\` project exists. See ${unroutedPath}.`,\n )\n }\n\n function upsertRegistryFromSlot(slot: ProjectSlot): void {\n if (slot.status !== 'active') return\n registry.set(slot.entry.name, {\n scanPath: slot.entry.path,\n paths: slot.paths,\n graph: slot.graph,\n })\n }\n\n // Attempt to bring a broken slot back online. Used both on SIGHUP reload\n // and inline on ingest when a span arrives for a broken project. Returns\n // the new slot status so callers can decide whether to deliver the span.\n async function tryRecoverSlot(entry: RegistryEntry): Promise<ProjectSlot> {\n try {\n const fresh = await bootstrapProject(entry)\n slots.set(entry.name, fresh)\n upsertRegistryFromSlot(fresh)\n if (fresh.status === 'active') {\n await setStatus(entry.name, 'active').catch(() => {})\n console.log(\n `neatd: project \"${entry.name}\" recovered from broken — active`,\n )\n }\n return fresh\n } catch (err) {\n console.warn(\n `neatd: project \"${entry.name}\" still broken after recovery attempt — ${(err as Error).message}`,\n )\n // Leave the existing broken slot in place; nothing changed.\n return slots.get(entry.name)!\n }\n }\n\n async function bootstrapOne(entry: RegistryEntry): Promise<void> {\n bootstrapStatus.set(entry.name, 'bootstrapping')\n bootstrapStartedAt.set(entry.name, Date.now())\n try {\n const slot = await bootstrapProject(entry)\n slots.set(entry.name, slot)\n upsertRegistryFromSlot(slot)\n bootstrapStatus.set(entry.name, slot.status === 'broken' ? 'broken' : 'active')\n if (slot.status === 'broken') {\n console.warn(`neatd: project \"${entry.name}\" broken — ${slot.errorReason}`)\n } else {\n console.log(`neatd: project \"${entry.name}\" active (${entry.path})`)\n }\n } catch (err) {\n bootstrapStatus.set(entry.name, 'broken')\n console.warn(\n `neatd: project \"${entry.name}\" failed to bootstrap — ${(err as Error).message}`,\n )\n await setStatus(entry.name, 'broken').catch(() => {})\n }\n }\n\n async function loadAll(): Promise<void> {\n const projects = await listProjects()\n const seen = new Set<string>()\n const pending: Promise<void>[] = []\n for (const entry of projects) {\n seen.add(entry.name)\n const existing = slots.get(entry.name)\n if (existing) {\n if (existing.status === 'broken') {\n pending.push(tryRecoverSlot(entry).then(() => {}))\n }\n continue\n }\n pending.push(bootstrapOne(entry))\n }\n for (const [name, slot] of [...slots.entries()]) {\n if (seen.has(name)) continue\n try {\n slot.stopPersist()\n } catch {\n // best-effort\n }\n slots.delete(name)\n bootstrapStatus.delete(name)\n bootstrapStartedAt.delete(name)\n console.log(`neatd: project \"${name}\" removed from registry — stopped`)\n }\n await Promise.allSettled(pending)\n }\n\n // Issue #340 — pre-populate bootstrap status from the registry so the REST\n // listener can answer 503 for projects whose slot hasn't loaded yet. Actual\n // bootstrap moves to the background after `listen()` returns.\n const initialEntries = await listProjects().catch(() => [] as RegistryEntry[])\n for (const entry of initialEntries) {\n bootstrapStatus.set(entry.name, 'bootstrapping')\n bootstrapStartedAt.set(entry.name, Date.now())\n }\n\n // ADR-063 — bind the REST host and the OTLP HTTP receiver. One listener\n // each, multi-tenant by project name in the URL (REST) and by service.name\n // dispatch (OTLP). Failure on either listen aborts startDaemon with a\n // surfacing error rather than letting the supervisor sit half-up.\n const bind = opts.bindListeners !== false\n let restApp: FastifyInstance | null = null\n let otlpApp:\n | (FastifyInstance & { flushPending: () => Promise<void> })\n | null = null\n let restAddress = ''\n let otlpAddress = ''\n\n if (bind) {\n // ADR-073 §3 — fail-loud before binding. Loopback-only without a token is\n // fine (laptop dev); a public bind without one is not. Resolved here\n // ahead of the host so the loopback-default branch (issue #341) reads\n // the same token state the bind-authority gate does.\n const auth = readAuthEnv()\n const host = resolveHost(opts, Boolean(auth.authToken))\n const restPort = resolveRestPort(opts)\n const otlpPort = resolveOtlpPort(opts)\n\n assertBindAuthority(host, auth.authToken)\n\n try {\n restApp = await buildApi({\n projects: registry,\n authToken: auth.authToken,\n trustProxy: auth.trustProxy,\n publicRead: auth.publicRead,\n bootstrap: {\n status: (name) => bootstrapStatus.get(name),\n list: () => {\n const now = Date.now()\n return [...bootstrapStatus.entries()].map(([name, status]) => ({\n name,\n status,\n elapsedMs: now - (bootstrapStartedAt.get(name) ?? now),\n }))\n },\n },\n })\n restAddress = await restApp.listen({ port: restPort, host })\n console.log(`neatd: REST listening on ${restAddress}`)\n } catch (err) {\n // Roll back anything we started so far before surfacing the error.\n for (const slot of slots.values()) {\n try {\n slot.stopPersist()\n } catch {\n // best-effort\n }\n }\n if (restApp) await restApp.close().catch(() => {})\n await fs.unlink(pidPath).catch(() => {})\n throw new Error(\n `neatd: failed to bind REST on port ${restPort} — ${(err as Error).message}`,\n )\n }\n\n // Resolve a span's target slot — running the broken-state recovery\n // when the routed slot is currently broken. Returns null when the span\n // can't be delivered after the recovery attempt; the caller drops with\n // a rate-limited warning. v0.4.1 / refs #339 — when nothing matches and\n // no default slot exists, the no-project-match event lands in\n // <NEAT_HOME>/errors.ndjson before we return null.\n async function resolveTargetSlot(\n serviceName: string | undefined,\n traceId: string | undefined,\n ): Promise<ProjectSlot | null> {\n const liveEntries = await listProjects().catch(() => [])\n const target = routeSpanToProject(serviceName, liveEntries)\n let slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT)\n if (!slot) {\n await recordUnroutedSpan(serviceName, traceId)\n return null\n }\n if (slot.status === 'broken') {\n const entry = liveEntries.find((e) => e.name === slot!.entry.name)\n if (entry) {\n slot = await tryRecoverSlot(entry)\n }\n if (slot.status !== 'active') {\n warnDroppedSpan(slot.entry.name, slot.errorReason ?? 'unknown')\n return null\n }\n }\n return slot.status === 'active' ? slot : null\n }\n\n try {\n otlpApp = await buildOtelReceiver({\n authToken: auth.otelToken,\n trustProxy: auth.trustProxy,\n onSpan: async (span) => {\n // ADR-049 OTel routing — dispatch by service.name. Broken slots\n // get a single inline recovery attempt before the span is dropped\n // with a rate-limited log line. Unknown services route to\n // DEFAULT_PROJECT so the FrontierNode auto-creation flow keeps\n // working (ADR-033); when DEFAULT_PROJECT isn't registered either,\n // resolveTargetSlot writes a no-project-match event to\n // <NEAT_HOME>/errors.ndjson (refs #339).\n const slot = await resolveTargetSlot(span.service, span.traceId)\n if (!slot) return\n await handleSpan(\n {\n graph: slot.graph,\n errorsPath: slot.paths.errorsPath,\n project: slot.entry.name,\n // Receiver already wrote the error event synchronously below.\n writeErrorEventInline: false,\n },\n span,\n )\n },\n onErrorSpanSync: async (span) => {\n const slot = await resolveTargetSlot(span.service, span.traceId)\n if (!slot) return\n await makeErrorSpanWriter(slot.paths.errorsPath)(span)\n },\n })\n otlpAddress = await otlpApp.listen({ port: otlpPort, host })\n console.log(`neatd: OTLP listening on ${otlpAddress}/v1/traces`)\n } catch (err) {\n for (const slot of slots.values()) {\n try {\n slot.stopPersist()\n } catch {\n // best-effort\n }\n }\n if (restApp) await restApp.close().catch(() => {})\n if (otlpApp) await otlpApp.close().catch(() => {})\n await fs.unlink(pidPath).catch(() => {})\n throw new Error(\n `neatd: failed to bind OTLP on port ${otlpPort} — ${(err as Error).message}`,\n )\n }\n }\n\n // Issue #340 — listeners are live; kick off per-project bootstrap in the\n // background. Polled callers watch /health for transitions.\n const initialBootstrap = loadAll().catch((err) => {\n console.warn(`neatd: initial bootstrap pass failed — ${(err as Error).message}`)\n })\n\n let reloading: Promise<void> | null = initialBootstrap\n const reload = async (): Promise<void> => {\n if (reloading) return reloading\n reloading = (async () => {\n try {\n await loadAll()\n } finally {\n reloading = null\n }\n })()\n return reloading\n }\n void initialBootstrap.finally(() => {\n if (reloading === initialBootstrap) reloading = null\n })\n\n const tracker: BootstrapTracker = {\n status: (name) => bootstrapStatus.get(name),\n list: () => {\n const now = Date.now()\n return [...bootstrapStatus.entries()].map(([name, status]) => ({\n name,\n status,\n elapsedMs: now - (bootstrapStartedAt.get(name) ?? now),\n }))\n },\n }\n\n // SIGHUP — external \"reload your config\" signal. ADR-049 #2.\n const sighupHandler = (): void => {\n void reload().catch((err) => {\n console.warn(`neatd: SIGHUP reload failed — ${(err as Error).message}`)\n })\n }\n process.on('SIGHUP', sighupHandler)\n\n let stopped = false\n const stop = async (): Promise<void> => {\n if (stopped) return\n stopped = true\n process.off('SIGHUP', sighupHandler)\n if (otlpApp) await otlpApp.close().catch(() => {})\n if (restApp) await restApp.close().catch(() => {})\n for (const slot of slots.values()) {\n try {\n slot.stopPersist()\n } catch {\n // best-effort\n }\n }\n await fs.unlink(pidPath).catch(() => {})\n }\n\n return {\n slots,\n reload,\n stop,\n pidPath,\n restAddress,\n otlpAddress,\n bootstrap: tracker,\n initialBootstrap,\n }\n}\n","/**\n * Unrouted-span logging (v0.4.1 — refs #339).\n *\n * When the daemon's routing layer can't deliver a span — service.name\n * matches no registered project AND no `default` project exists — we still\n * return 200 on the receiver (OTLP spec), but the dropped event lands here\n * so the next operator can see what happened. The log lives at\n * `<NEAT_HOME>/errors.ndjson` because the unrouted span doesn't belong to\n * any project's neat-out directory.\n *\n * Owner: this module. Daemon imports the appender + warner; no other code\n * writes to the no-project-match log path. Keeping the writes here keeps\n * daemon.ts free of direct `fs.appendFile` calls (asserted by the daemon\n * contract test).\n */\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\n\nexport interface UnroutedSpanRecord {\n timestamp: string\n reason: 'no-project-match'\n service_name: string | null\n traceId: string | null\n}\n\nexport function buildUnroutedSpanRecord(\n serviceName: string | undefined,\n traceId: string | undefined,\n now: Date = new Date(),\n): UnroutedSpanRecord {\n return {\n timestamp: now.toISOString(),\n reason: 'no-project-match',\n service_name: serviceName ?? null,\n traceId: traceId ?? null,\n }\n}\n\nexport async function appendUnroutedSpan(\n neatHome: string,\n record: UnroutedSpanRecord,\n): Promise<void> {\n const target = path.join(neatHome, 'errors.ndjson')\n await fs.mkdir(neatHome, { recursive: true })\n await fs.appendFile(target, JSON.stringify(record) + '\\n', 'utf8')\n}\n\nexport function unroutedErrorsPath(neatHome: string): string {\n return path.join(neatHome, 'errors.ndjson')\n}\n"],"mappings":"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","names":["fs","path","path","fs"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseOtlpRequest
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KYRIQIPG.js";
|
|
4
4
|
|
|
5
5
|
// src/otel-grpc.ts
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
@@ -136,4 +136,4 @@ export {
|
|
|
136
136
|
reshapeGrpcRequest,
|
|
137
137
|
startOtelGrpcReceiver
|
|
138
138
|
};
|
|
139
|
-
//# sourceMappingURL=chunk-
|
|
139
|
+
//# sourceMappingURL=chunk-D5PIJFBE.js.map
|
|
@@ -5,6 +5,13 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
5
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
+
// ../../node_modules/tsup/assets/esm_shims.js
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
var getFilename = () => fileURLToPath(import.meta.url);
|
|
12
|
+
var getDirname = () => path.dirname(getFilename());
|
|
13
|
+
var __dirname = /* @__PURE__ */ getDirname();
|
|
14
|
+
|
|
8
15
|
// src/auth.ts
|
|
9
16
|
import { timingSafeEqual } from "crypto";
|
|
10
17
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
@@ -30,20 +37,31 @@ function assertBindAuthority(host, token) {
|
|
|
30
37
|
if (isLoopbackHost(host)) return;
|
|
31
38
|
throw new BindAuthorityError(host);
|
|
32
39
|
}
|
|
33
|
-
var
|
|
40
|
+
var PUBLIC_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
41
|
+
var DEFAULT_UNAUTH_SUFFIXES = [
|
|
42
|
+
"/health",
|
|
43
|
+
"/healthz",
|
|
44
|
+
"/readyz",
|
|
45
|
+
"/api/config"
|
|
46
|
+
];
|
|
34
47
|
function mountBearerAuth(app, opts) {
|
|
35
48
|
if (!opts.token || opts.token.length === 0) return;
|
|
36
49
|
if (opts.trustProxy) return;
|
|
37
50
|
const expected = Buffer.from(opts.token, "utf8");
|
|
38
51
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
52
|
+
const publicRead = opts.publicRead === true;
|
|
39
53
|
app.addHook("preHandler", (req, reply, done) => {
|
|
40
|
-
const
|
|
54
|
+
const path3 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
41
55
|
for (const suffix of suffixes) {
|
|
42
|
-
if (
|
|
56
|
+
if (path3 === suffix || path3.endsWith(suffix)) {
|
|
43
57
|
done();
|
|
44
58
|
return;
|
|
45
59
|
}
|
|
46
60
|
}
|
|
61
|
+
if (publicRead && PUBLIC_READ_METHODS.has(req.method)) {
|
|
62
|
+
done();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
47
65
|
const header = req.headers.authorization;
|
|
48
66
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
49
67
|
void reply.code(401).send({ error: "unauthorized" });
|
|
@@ -57,19 +75,24 @@ function mountBearerAuth(app, opts) {
|
|
|
57
75
|
done();
|
|
58
76
|
});
|
|
59
77
|
}
|
|
78
|
+
function parseBoolEnv(v) {
|
|
79
|
+
if (!v) return false;
|
|
80
|
+
return v === "true" || v === "1";
|
|
81
|
+
}
|
|
60
82
|
function readAuthEnv(env = process.env) {
|
|
61
83
|
const t = env.NEAT_AUTH_TOKEN;
|
|
62
84
|
const ot = env.NEAT_OTEL_TOKEN;
|
|
63
85
|
return {
|
|
64
86
|
authToken: t && t.length > 0 ? t : void 0,
|
|
65
87
|
otelToken: ot && ot.length > 0 ? ot : t && t.length > 0 ? t : void 0,
|
|
66
|
-
trustProxy: env.NEAT_AUTH_PROXY === "true"
|
|
88
|
+
trustProxy: env.NEAT_AUTH_PROXY === "true",
|
|
89
|
+
publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
|
|
67
90
|
};
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
// src/otel.ts
|
|
71
|
-
import
|
|
72
|
-
import { fileURLToPath } from "url";
|
|
94
|
+
import path2 from "path";
|
|
95
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
73
96
|
import Fastify from "fastify";
|
|
74
97
|
import protobuf from "protobufjs";
|
|
75
98
|
function extractExceptionFromEvents(events) {
|
|
@@ -127,6 +150,18 @@ function isoFromUnixNano(nanos) {
|
|
|
127
150
|
return void 0;
|
|
128
151
|
}
|
|
129
152
|
}
|
|
153
|
+
var ENV_ATTR_CANONICAL = "deployment.environment.name";
|
|
154
|
+
var ENV_ATTR_COMPAT = "deployment.environment";
|
|
155
|
+
var ENV_FALLBACK = "unknown";
|
|
156
|
+
function pickEnv(spanAttrs, resourceAttrs) {
|
|
157
|
+
for (const attrs of [spanAttrs, resourceAttrs]) {
|
|
158
|
+
for (const key of [ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT]) {
|
|
159
|
+
const v = attrs[key];
|
|
160
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return ENV_FALLBACK;
|
|
164
|
+
}
|
|
130
165
|
function parseOtlpRequest(body) {
|
|
131
166
|
const out = [];
|
|
132
167
|
for (const rs of body.resourceSpans ?? []) {
|
|
@@ -146,6 +181,7 @@ function parseOtlpRequest(body) {
|
|
|
146
181
|
endTimeUnixNano: span.endTimeUnixNano ?? "0",
|
|
147
182
|
startTimeIso: isoFromUnixNano(span.startTimeUnixNano),
|
|
148
183
|
durationNanos: durationNanos(span.startTimeUnixNano, span.endTimeUnixNano),
|
|
184
|
+
env: pickEnv(attrs, resourceAttrs),
|
|
149
185
|
attributes: attrs,
|
|
150
186
|
dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
|
|
151
187
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
@@ -162,10 +198,10 @@ function parseOtlpRequest(body) {
|
|
|
162
198
|
var exportTraceServiceRequestType = null;
|
|
163
199
|
var exportTraceServiceResponseType = null;
|
|
164
200
|
function loadProtoRoot() {
|
|
165
|
-
const here =
|
|
166
|
-
const protoRoot =
|
|
201
|
+
const here = path2.dirname(fileURLToPath2(import.meta.url));
|
|
202
|
+
const protoRoot = path2.resolve(here, "..", "proto");
|
|
167
203
|
const root = new protobuf.Root();
|
|
168
|
-
root.resolvePath = (_origin, target) =>
|
|
204
|
+
root.resolvePath = (_origin, target) => path2.resolve(protoRoot, target);
|
|
169
205
|
root.loadSync(
|
|
170
206
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
171
207
|
{ keepCase: true }
|
|
@@ -200,7 +236,7 @@ function encodeProtobufResponseBody() {
|
|
|
200
236
|
async function decodeProtobufBody(buf) {
|
|
201
237
|
const Type = loadProtobufDecoder();
|
|
202
238
|
const decoded = Type.decode(buf).toJSON();
|
|
203
|
-
const { reshapeGrpcRequest } = await import("./otel-grpc-
|
|
239
|
+
const { reshapeGrpcRequest } = await import("./otel-grpc-QTX2YQJZ.js");
|
|
204
240
|
return reshapeGrpcRequest(decoded);
|
|
205
241
|
}
|
|
206
242
|
async function buildOtelReceiver(opts) {
|
|
@@ -298,6 +334,7 @@ function logSpanHandler(span) {
|
|
|
298
334
|
|
|
299
335
|
export {
|
|
300
336
|
__require,
|
|
337
|
+
__dirname,
|
|
301
338
|
BindAuthorityError,
|
|
302
339
|
assertBindAuthority,
|
|
303
340
|
mountBearerAuth,
|
|
@@ -306,4 +343,4 @@ export {
|
|
|
306
343
|
buildOtelReceiver,
|
|
307
344
|
logSpanHandler
|
|
308
345
|
};
|
|
309
|
-
//# sourceMappingURL=chunk-
|
|
346
|
+
//# sourceMappingURL=chunk-KYRIQIPG.js.map
|