@neat.is/core 0.3.5 → 0.3.7

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 +0,0 @@
1
- {"version":3,"sources":["../src/daemon.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 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\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}\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 */\nexport function routeSpanToProject(\n serviceName: string | undefined,\n projects: ReadonlyArray<RegistryEntry>,\n): string {\n if (!serviceName) return DEFAULT_PROJECT\n for (const entry of projects) {\n if (entry.status !== 'active') continue\n if (entry.languages.length === 0) {\n // No language data yet — still acceptable to match by name.\n }\n if (entry.name === serviceName) return entry.name\n }\n return DEFAULT_PROJECT\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): 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 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\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 async function loadAll(): Promise<void> {\n const projects = await listProjects()\n const seen = new Set<string>()\n for (const entry of projects) {\n seen.add(entry.name)\n if (slots.has(entry.name)) continue\n try {\n const slot = await bootstrapProject(entry)\n slots.set(entry.name, slot)\n upsertRegistryFromSlot(slot)\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 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 // Drop entries the registry no longer carries.\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 console.log(`neatd: project \"${name}\" removed from registry — stopped`)\n }\n }\n\n await loadAll()\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 const host = resolveHost(opts)\n const restPort = resolveRestPort(opts)\n const otlpPort = resolveOtlpPort(opts)\n\n try {\n restApp = await buildApi({ projects: registry })\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 try {\n otlpApp = await buildOtelReceiver({\n onSpan: async (span) => {\n // ADR-049 OTel routing — dispatch by service.name across active\n // registry entries. Unknown services route to DEFAULT_PROJECT so\n // the FrontierNode auto-creation flow keeps working (ADR-033).\n const liveEntries = await listProjects().catch(() => [])\n const target = routeSpanToProject(span.service, liveEntries)\n const slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT)\n if (!slot || slot.status !== 'active') 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 liveEntries = await listProjects().catch(() => [])\n const target = routeSpanToProject(span.service, liveEntries)\n const slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT)\n if (!slot || slot.status !== 'active') 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 let reloading: Promise<void> | null = null\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\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 { slots, reload, stop, pidPath, restAddress, otlpAddress }\n}\n"],"mappings":"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","names":[]}