@neat.is/core 0.7.7 → 0.7.8
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-UI3AFJAF.js → chunk-LN75Z624.js} +103 -2
- package/dist/chunk-LN75Z624.js.map +1 -0
- package/dist/{chunk-YVZRBT4C.js → chunk-OVRVDSUO.js} +2 -2
- package/dist/cli.cjs +102 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +102 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +102 -1
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +102 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-UI3AFJAF.js.map +0 -1
- /package/dist/{chunk-YVZRBT4C.js.map → chunk-OVRVDSUO.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/graph.ts","../src/compat.ts","../compat.json","../src/ingest.ts","../src/policy.ts","../src/events.ts","../src/extract/routes.ts","../src/extract/shared.ts","../src/extract/errors.ts","../src/extract/calls/shared.ts","../src/extract/imports.ts","../src/columns.ts","../src/traverse.ts","../src/extract/services.ts","../src/extract/python.ts","../src/extract/go.ts","../src/extract/ruby.ts","../src/extract/php.ts","../src/extract/owners.ts","../src/extract/aliases.ts","../src/extract/files.ts","../src/extract/symbols.ts","../src/extract/symbol-edges.ts","../src/extract/actions.ts","../src/extract/databases/index.ts","../src/extract/databases/db-config-yaml.ts","../src/extract/databases/dotenv.ts","../src/extract/databases/shared.ts","../src/extract/databases/prisma.ts","../src/extract/databases/drizzle.ts","../src/extract/databases/knex.ts","../src/extract/databases/ormconfig.ts","../src/extract/databases/typeorm.ts","../src/extract/databases/sequelize.ts","../src/extract/databases/docker-compose.ts","../src/extract/configs.ts","../src/extract/proto.ts","../src/extract/calls/index.ts","../src/extract/calls/http.ts","../src/extract/calls/route-match.ts","../src/extract/calls/kafka.ts","../src/extract/calls/redis.ts","../src/extract/calls/aws.ts","../src/extract/calls/grpc.ts","../src/extract/calls/supabase.ts","../src/extract/calls/firestore.ts","../src/extract/calls/mongoose.ts","../src/extract/calls/sqlalchemy.ts","../src/extract/calls/django-orm.ts","../src/extract/calls/drizzle.ts","../src/extract/calls/prisma.ts","../src/extract/calls/activerecord.ts","../src/extract/calls/eloquent.ts","../src/extract/calls/go.ts","../src/extract/calls/gorm.ts","../src/extract/table-edges.ts","../src/extract/infra/docker-compose.ts","../src/extract/infra/shared.ts","../src/extract/infra/dockerfile.ts","../src/extract/infra/terraform.ts","../src/extract/infra/k8s.ts","../src/extract/infra/cloudflare.ts","../src/extract/infra/vercel.ts","../src/extract/infra/railway.ts","../src/extract/infra/supabase.ts","../src/extract/infra/index.ts","../src/extract/zod-shapes.ts","../src/extract/firestore-rules.ts","../src/extract/index.ts","../src/extract/retire.ts","../src/divergences.ts","../src/persist.ts","../src/diff.ts","../src/projects.ts","../src/registry.ts","../src/api.ts","../src/extend/index.ts","../src/installers/package-manager.ts","../src/logs-store.ts","../src/streaming.ts","../src/connectors-config.ts","../src/connectors/status.ts","../src/connectors/index.ts","../src/connectors/junction.ts","../src/connectors/supabase/client.ts","../src/connectors/supabase/types.ts","../src/connectors/supabase/map.ts","../src/connectors/supabase/postgres-client.ts","../src/connectors/supabase/resolve.ts","../src/connectors/supabase/index.ts","../src/connectors/railway/index.ts","../src/connectors/railway/client.ts","../src/connectors/firebase/logging-api.ts","../src/connectors/firebase/map.ts","../src/connectors/firebase/resolve.ts","../src/connectors/firebase/index.ts","../src/connectors/cloudflare/connector.ts","../src/connectors/cloudflare/client.ts","../src/connectors/cloudflare/types.ts","../src/connectors/cloudflare/map.ts","../src/connectors/vercel/client.ts","../src/connectors/neon/client.ts","../src/connectors/neon/types.ts","../src/connectors/neon/map.ts","../src/connectors/neon/resolve.ts","../src/connectors/neon/index.ts","../src/connectors/cloud-run/client.ts","../src/connectors/cloud-run/types.ts","../src/connectors/cloud-run/map.ts","../src/connectors/cloud-run/resolve.ts","../src/connectors/cloud-run/index.ts","../src/connectors/render/index.ts","../src/connectors/render/types.ts","../src/connectors/render/client.ts","../src/connectors/planetscale/types.ts","../src/connectors/planetscale/client.ts","../src/connectors/planetscale/map.ts","../src/connectors/planetscale/resolve.ts","../src/connectors/planetscale/index.ts","../src/connectors/registry.ts"],"sourcesContent":["import GraphDefault from 'graphology'\nimport type { MultiDirectedGraph as MDGType } from 'graphology'\nimport type { GraphEdge, GraphNode } from '@neat.is/types'\n\n// graphology ships as a CJS bundle that does `module.exports = Graph` with\n// the other constructors attached as properties (`Graph.MultiDirectedGraph =\n// ...`). cjs-module-lexer can't see through that attachment, so a named\n// import like `import { MultiDirectedGraph } from 'graphology'` fails under\n// strict Node ESM (Node 22+ via tsx in particular). Pull the constructor off\n// the default export instead — same shape under tsx and tsup-bundled output.\ntype MultiDirectedGraphCtor = typeof MDGType\nconst MultiDirectedGraph: MultiDirectedGraphCtor = (\n GraphDefault as unknown as { MultiDirectedGraph: MultiDirectedGraphCtor }\n).MultiDirectedGraph\n\n// Multi because two nodes can have edges of different types simultaneously\n// (e.g. CALLS and DEPENDS_ON between the same pair of services).\nexport type NeatGraph = MDGType<GraphNode, GraphEdge>\n\nexport const DEFAULT_PROJECT = 'default'\n\n// One graph per project. The map is the source of truth; getGraph() with no\n// arg or with 'default' hits the legacy single-project path so existing\n// callers keep working byte-for-byte (ADR-026).\nconst graphs = new Map<string, NeatGraph>()\n\nfunction makeGraph(): NeatGraph {\n return new MultiDirectedGraph<GraphNode, GraphEdge>({ allowSelfLoops: false })\n}\n\nexport function getGraph(project: string = DEFAULT_PROJECT): NeatGraph {\n let g = graphs.get(project)\n if (!g) {\n g = makeGraph()\n graphs.set(project, g)\n }\n return g\n}\n\nexport function hasProject(project: string): boolean {\n return graphs.has(project)\n}\n\nexport function listProjects(): string[] {\n return [...graphs.keys()].sort()\n}\n\n// Reset a single project, or all of them when the arg is omitted. Tests use\n// the no-arg form between cases; runtime never calls it.\nexport function resetGraph(project?: string): void {\n if (project === undefined) {\n graphs.clear()\n return\n }\n graphs.delete(project)\n}\n","import { promises as fs } from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport semver from 'semver'\nimport compatData from '../compat.json' with { type: 'json' }\n\nexport interface CompatibilityResult {\n compatible: boolean\n reason?: string\n minDriverVersion?: string\n}\n\nexport interface CompatPair {\n kind?: 'driver-engine'\n driver: string\n engine: string\n minDriverVersion: string\n // The driver constraint only kicks in once the engine is at this major or higher.\n // Older engines (e.g. PostgreSQL 13) accept the older driver fine.\n minEngineVersion?: string\n reason: string\n}\n\nexport interface NodeEngineConstraint {\n kind?: 'node-engine'\n package: string\n packageMinVersion?: string\n minNodeVersion: string\n reason: string\n}\n\nexport interface PackageConflict {\n kind?: 'package-conflict'\n package: string\n packageMinVersion?: string\n requires: { name: string; minVersion: string }\n reason: string\n}\n\nexport interface DeprecatedApi {\n kind?: 'deprecated-api'\n package: string\n packageMaxVersion?: string\n reason: string\n}\n\nexport interface CompatMatrix {\n pairs: CompatPair[]\n nodeEngineConstraints?: NodeEngineConstraint[]\n packageConflicts?: PackageConflict[]\n deprecatedApis?: DeprecatedApi[]\n}\n\nconst bundledMatrix = compatData as CompatMatrix\nlet mergedMatrix: CompatMatrix | null = null\nlet remoteLoadAttempted = false\n\nconst REMOTE_CACHE_DIR = path.join(os.homedir(), '.neat')\nconst REMOTE_CACHE_PATH = path.join(REMOTE_CACHE_DIR, 'compat-cache.json')\nconst REMOTE_TTL_MS = 24 * 60 * 60 * 1000\n\ninterface RemoteCacheFile {\n fetchedAt: string\n url: string\n matrix: CompatMatrix\n}\n\n// Engines like Postgres/MySQL only carry a major in the version field, so semver\n// won't always parse them cleanly. Compare as integers when both sides look like\n// majors; otherwise fall back to semver.coerce.\nfunction engineMeetsThreshold(engineVersion: string, threshold: string): boolean {\n const e = parseInt(engineVersion, 10)\n const t = parseInt(threshold, 10)\n if (Number.isFinite(e) && Number.isFinite(t)) return e >= t\n\n const ec = semver.coerce(engineVersion)\n const tc = semver.coerce(threshold)\n if (ec && tc) return semver.gte(ec, tc)\n\n return false\n}\n\nexport function checkCompatibility(\n driver: string,\n driverVersion: string,\n engine: string,\n engineVersion: string,\n): CompatibilityResult {\n const matrix = currentMatrix()\n const pair = matrix.pairs.find((p) => p.driver === driver && p.engine === engine)\n if (!pair) return { compatible: true }\n\n if (pair.minEngineVersion && !engineMeetsThreshold(engineVersion, pair.minEngineVersion)) {\n return { compatible: true }\n }\n\n const driverCoerced = semver.coerce(driverVersion)\n if (!driverCoerced) return { compatible: true }\n\n if (semver.lt(driverCoerced, pair.minDriverVersion)) {\n return {\n compatible: false,\n reason: pair.reason,\n minDriverVersion: pair.minDriverVersion,\n }\n }\n\n return { compatible: true }\n}\n\nexport interface NodeEngineCheck {\n compatible: boolean\n reason?: string\n requiredNodeVersion?: string\n}\n\n// True when `serviceNodeRange` (a service's `engines.node`) is guaranteed to\n// admit `requiredNodeVersion`. We use a permissive semver compare via `coerce`\n// — exact ranges like \">=20\" parse fine, exotic ones like \"^20 || ^22\" pass as\n// long as semver can resolve them. If the range can't be parsed at all, we\n// don't claim a conflict — under-flag rather than over-flag.\nfunction rangeAdmitsVersion(serviceNodeRange: string, requiredNodeVersion: string): boolean {\n try {\n const required = semver.coerce(requiredNodeVersion)\n if (!required) return true\n // Is every version that satisfies the service's range >= required? If yes,\n // the service guarantees the requirement; if not, there's at least one\n // admissible Node version that won't satisfy the dep — that's the\n // conflict.\n return semver.subset(serviceNodeRange, `>=${required.version}`, {\n includePrerelease: false,\n })\n } catch {\n return true\n }\n}\n\nexport function checkNodeEngineConstraint(\n constraint: NodeEngineConstraint,\n declaredPackageVersion: string | undefined,\n serviceNodeRange: string | undefined,\n): NodeEngineCheck {\n if (constraint.packageMinVersion && declaredPackageVersion) {\n const v = semver.coerce(declaredPackageVersion)\n if (v && semver.lt(v, constraint.packageMinVersion)) {\n return { compatible: true }\n }\n }\n if (!serviceNodeRange) {\n return { compatible: true }\n }\n if (rangeAdmitsVersion(serviceNodeRange, constraint.minNodeVersion)) {\n return { compatible: true }\n }\n return {\n compatible: false,\n reason: constraint.reason,\n requiredNodeVersion: constraint.minNodeVersion,\n }\n}\n\nexport interface PackageConflictCheck {\n compatible: boolean\n reason?: string\n requires?: { name: string; minVersion: string }\n foundVersion?: string\n}\n\nexport function checkPackageConflict(\n conflict: PackageConflict,\n declaredPackageVersion: string | undefined,\n declaredRequiredVersion: string | undefined,\n): PackageConflictCheck {\n if (!declaredPackageVersion) return { compatible: true }\n if (conflict.packageMinVersion) {\n const v = semver.coerce(declaredPackageVersion)\n if (v && semver.lt(v, conflict.packageMinVersion)) {\n return { compatible: true }\n }\n }\n if (!declaredRequiredVersion) {\n return {\n compatible: false,\n reason: conflict.reason,\n requires: conflict.requires,\n }\n }\n const requiredCoerced = semver.coerce(declaredRequiredVersion)\n if (!requiredCoerced) return { compatible: true }\n if (semver.lt(requiredCoerced, conflict.requires.minVersion)) {\n return {\n compatible: false,\n reason: conflict.reason,\n requires: conflict.requires,\n foundVersion: declaredRequiredVersion,\n }\n }\n return { compatible: true }\n}\n\nexport function checkDeprecatedApi(\n rule: DeprecatedApi,\n declaredVersion: string | undefined,\n): { compatible: boolean; reason?: string } {\n if (declaredVersion === undefined) return { compatible: true }\n if (rule.packageMaxVersion) {\n const v = semver.coerce(declaredVersion)\n const max = semver.coerce(rule.packageMaxVersion)\n if (v && max && semver.gt(v, max)) return { compatible: true }\n }\n return { compatible: false, reason: rule.reason }\n}\n\nfunction currentMatrix(): CompatMatrix {\n return mergedMatrix ?? bundledMatrix\n}\n\nfunction mergeMatrices(a: CompatMatrix, b: CompatMatrix): CompatMatrix {\n return {\n pairs: [...a.pairs, ...(b.pairs ?? [])],\n nodeEngineConstraints: [\n ...(a.nodeEngineConstraints ?? []),\n ...(b.nodeEngineConstraints ?? []),\n ],\n packageConflicts: [...(a.packageConflicts ?? []), ...(b.packageConflicts ?? [])],\n deprecatedApis: [...(a.deprecatedApis ?? []), ...(b.deprecatedApis ?? [])],\n }\n}\n\nasync function readRemoteCache(url: string): Promise<CompatMatrix | null> {\n try {\n const raw = await fs.readFile(REMOTE_CACHE_PATH, 'utf8')\n const parsed = JSON.parse(raw) as RemoteCacheFile\n if (parsed.url !== url) return null\n const age = Date.now() - new Date(parsed.fetchedAt).getTime()\n if (age > REMOTE_TTL_MS) return null\n return parsed.matrix\n } catch {\n return null\n }\n}\n\nasync function writeRemoteCache(url: string, matrix: CompatMatrix): Promise<void> {\n const file: RemoteCacheFile = {\n fetchedAt: new Date().toISOString(),\n url,\n matrix,\n }\n try {\n await fs.mkdir(REMOTE_CACHE_DIR, { recursive: true })\n await fs.writeFile(REMOTE_CACHE_PATH, JSON.stringify(file), 'utf8')\n } catch (err) {\n console.warn(`[neat] failed to cache compat matrix: ${(err as Error).message}`)\n }\n}\n\n// Loads the bundled matrix and, if `NEAT_COMPAT_URL` is set, merges in a\n// remote extension. Falls back to a fresh fetch when the on-disk cache is\n// stale (24h TTL) or missing. Returns the merged matrix; subsequent calls are\n// memoised.\n//\n// Async because the fetch happens lazily on first use. Extract phase 2 awaits\n// this before iterating pairs; everything else goes through the sync\n// `currentMatrix()` view, which is fine because by the time CLI / traversal\n// runs, extraction has already loaded.\nexport async function ensureCompatLoaded(): Promise<CompatMatrix> {\n if (mergedMatrix) return mergedMatrix\n if (remoteLoadAttempted) {\n mergedMatrix = bundledMatrix\n return mergedMatrix\n }\n remoteLoadAttempted = true\n\n const url = process.env.NEAT_COMPAT_URL\n if (!url) {\n mergedMatrix = bundledMatrix\n return mergedMatrix\n }\n\n const cached = await readRemoteCache(url)\n if (cached) {\n mergedMatrix = mergeMatrices(bundledMatrix, cached)\n return mergedMatrix\n }\n\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)\n const remote = (await res.json()) as CompatMatrix\n await writeRemoteCache(url, remote)\n mergedMatrix = mergeMatrices(bundledMatrix, remote)\n return mergedMatrix\n } catch (err) {\n console.warn(\n `[neat] NEAT_COMPAT_URL fetch failed (${(err as Error).message}); using bundled matrix only`,\n )\n mergedMatrix = bundledMatrix\n return mergedMatrix\n }\n}\n\n// Reset the merged-matrix memo. Intended for tests so each test starts with a\n// freshly loaded matrix.\nexport function resetCompatMatrix(): void {\n mergedMatrix = null\n remoteLoadAttempted = false\n}\n\nexport function compatPairs(): readonly CompatPair[] {\n return currentMatrix().pairs\n}\n\nexport function nodeEngineConstraints(): readonly NodeEngineConstraint[] {\n return currentMatrix().nodeEngineConstraints ?? []\n}\n\nexport function packageConflicts(): readonly PackageConflict[] {\n return currentMatrix().packageConflicts ?? []\n}\n\nexport function deprecatedApis(): readonly DeprecatedApi[] {\n return currentMatrix().deprecatedApis ?? []\n}\n","{\n \"pairs\": [\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"pg\",\n \"engine\": \"postgresql\",\n \"minDriverVersion\": \"8.0.0\",\n \"minEngineVersion\": \"14\",\n \"reason\": \"PostgreSQL 14+ requires scram-sha-256 auth by default; pg < 8.0.0 only speaks md5.\"\n },\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"mysql2\",\n \"engine\": \"mysql\",\n \"minDriverVersion\": \"3.0.0\",\n \"minEngineVersion\": \"8\",\n \"reason\": \"MySQL 8 defaults to caching_sha2_password; mysql2 < 3.0.0 doesn't negotiate it.\"\n },\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"mongoose\",\n \"engine\": \"mongodb\",\n \"minDriverVersion\": \"7.0.0\",\n \"minEngineVersion\": \"7\",\n \"reason\": \"MongoDB 7 drops legacy wire-protocol opcodes that mongoose < 7.0.0 still emits.\"\n },\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"psycopg2\",\n \"engine\": \"postgresql\",\n \"minDriverVersion\": \"2.9.0\",\n \"minEngineVersion\": \"14\",\n \"reason\": \"PostgreSQL 14+ requires scram-sha-256 auth by default; psycopg2 < 2.9.0 only speaks md5.\"\n },\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"pymongo\",\n \"engine\": \"mongodb\",\n \"minDriverVersion\": \"4.0.0\",\n \"minEngineVersion\": \"7\",\n \"reason\": \"MongoDB 7 drops legacy wire-protocol opcodes that pymongo < 4.0.0 still emits.\"\n },\n {\n \"kind\": \"driver-engine\",\n \"driver\": \"mysql-connector-python\",\n \"engine\": \"mysql\",\n \"minDriverVersion\": \"8.0.0\",\n \"minEngineVersion\": \"8\",\n \"reason\": \"MySQL 8 defaults to caching_sha2_password; mysql-connector-python < 8.0.0 doesn't negotiate it.\"\n }\n ],\n \"nodeEngineConstraints\": [\n {\n \"kind\": \"node-engine\",\n \"package\": \"vitest\",\n \"packageMinVersion\": \"2.0.0\",\n \"minNodeVersion\": \"18.0.0\",\n \"reason\": \"vitest >= 2.0 drops Node 16 support; requires Node 18+.\"\n },\n {\n \"kind\": \"node-engine\",\n \"package\": \"next\",\n \"packageMinVersion\": \"14.0.0\",\n \"minNodeVersion\": \"18.17.0\",\n \"reason\": \"Next 14+ requires Node 18.17+ (uses APIs introduced in that minor).\"\n },\n {\n \"kind\": \"node-engine\",\n \"package\": \"@modelcontextprotocol/sdk\",\n \"packageMinVersion\": \"1.0.0\",\n \"minNodeVersion\": \"18.0.0\",\n \"reason\": \"@modelcontextprotocol/sdk >= 1 requires Node 18+ (web-streams polyfill removed).\"\n }\n ],\n \"packageConflicts\": [\n {\n \"kind\": \"package-conflict\",\n \"package\": \"@tanstack/react-query\",\n \"packageMinVersion\": \"5.0.0\",\n \"requires\": {\n \"name\": \"react\",\n \"minVersion\": \"18.0.0\"\n },\n \"reason\": \"@tanstack/react-query 5+ uses useSyncExternalStore — only available in React 18+.\"\n },\n {\n \"kind\": \"package-conflict\",\n \"package\": \"react-router-dom\",\n \"packageMinVersion\": \"7.0.0\",\n \"requires\": {\n \"name\": \"react\",\n \"minVersion\": \"18.0.0\"\n },\n \"reason\": \"react-router-dom 7+ requires React 18+.\"\n },\n {\n \"kind\": \"package-conflict\",\n \"package\": \"next\",\n \"packageMinVersion\": \"14.0.0\",\n \"requires\": {\n \"name\": \"react\",\n \"minVersion\": \"18.2.0\"\n },\n \"reason\": \"Next.js 14+ requires React 18.2+.\"\n }\n ],\n \"deprecatedApis\": [\n {\n \"kind\": \"deprecated-api\",\n \"package\": \"request\",\n \"packageMaxVersion\": \"2.88.2\",\n \"reason\": \"request is deprecated; use undici, node-fetch, or axios instead.\"\n },\n {\n \"kind\": \"deprecated-api\",\n \"package\": \"node-uuid\",\n \"reason\": \"node-uuid is deprecated; use the `uuid` package.\"\n }\n ]\n}\n","import { promises as fs, existsSync, readFileSync } from 'node:fs'\nimport path from 'node:path'\nimport * as sourceMapJs from 'source-map-js'\nimport type {\n DatabaseNode,\n ErrorEvent,\n FileNode,\n FrontierNode,\n GraphEdge,\n GraphNode,\n GraphQLOperationNode,\n GrpcMethodNode,\n InfraNode,\n Policy,\n ServiceNode,\n StaleEvent,\n SymbolNode,\n WebSocketChannelNode,\n} from '@neat.is/types'\nimport type { PersistedGraph } from './persist.js'\nimport type { EvaluationContext as PolicyEvaluationContext } from './policy.js'\nimport { canPromoteFrontier } from './policy.js'\nimport {\n EdgeType,\n GraphEdgeSchema,\n GraphNodeSchema,\n NodeType,\n Provenance,\n confidenceForObservedSignal,\n databaseId,\n localDatabaseId,\n extractedEdgeId,\n fileId,\n frontierId,\n graphqlOperationId,\n grpcMethodId,\n inferredEdgeId,\n infraId,\n observedEdgeId,\n serviceId,\n symbolId,\n websocketChannelId,\n type EdgeTypeValue,\n type ProvenanceValue,\n type RouteNode,\n} from '@neat.is/types'\nimport { normalizePathTemplate } from './extract/routes.js'\nimport { foldColumns, OBSERVED_COLUMN_CONFIDENCE } from './columns.js'\nimport type { NeatGraph } from './graph.js'\nimport { DEFAULT_PROJECT } from './graph.js'\nimport type { AttributeValue, ParsedSpan } from './otel.js'\nimport { emitNeatEvent } from './events.js'\n\n// Maps OTel spans to graph signal:\n// * Cross-service span → upsert CALLS edge.\n// * Database span (db.system attr present) → upsert CONNECTS_TO edge to a\n// DatabaseNode resolved by host, or a service-scoped local DatabaseNode when\n// the span carries no peer host (an in-process / embedded DB, ADR-118).\n// * Span with status.code === 2 → ErrorEvent appended to errors.ndjson.\n//\n// Contract anchors (see /docs/contracts.md):\n// * Rule 1 — Provenance: every edge here carries Provenance.X from @neat.is/types.\n// * Rule 2 — Coexistence: OBSERVED edges live alongside EXTRACTED ones with a\n// distinct id pattern (`${type}:OBSERVED:src->tgt`). Never write OBSERVED\n// under the EXTRACTED id; that erases the gap NEAT exists to surface.\n// * Rule 4 — Per-edge-type staleness (ADR-024): STALE_THRESHOLDS_BY_EDGE_TYPE\n// governs decay; never hardcode a flat 24h threshold.\n// * Rule 8 — No demo names: derive driver/engine identifiers from node\n// properties, not literals.\n\nexport interface IngestContext {\n graph: NeatGraph\n errorsPath: string\n // Absolute scan root the daemon is watching for this project. When set, a\n // runtime `code.filepath` is made service-root-relative against it before the\n // FileNode is keyed (file-awareness.md §4) — the service's absolute root is\n // `scanPath/<repoPath>`, which recovers `dist/foo.js` even for a single-\n // package service whose `repoPath` is empty (issue #430). Omitted by ad-hoc\n // callers and most tests, which rely on the repoPath-segment anchor instead.\n scanPath?: string\n // Project name for event-bus routing (ADR-051). Defaults to DEFAULT_PROJECT\n // when omitted — keeps single-project tests / scripts wire-compatible.\n project?: string\n now?: () => number\n // Set to false when the receiver already wrote the ErrorEvent synchronously\n // (production daemons via watch.ts wire this). When true or omitted, handleSpan\n // appends the ErrorEvent itself — the path used by ad-hoc scripts and tests\n // that don't go through buildOtelReceiver. ADR-033 §Error events.\n writeErrorEventInline?: boolean\n // Post-mutation policy trigger (ADR-043). Fires after handleSpan finishes\n // and the queue is drained. Daemons wire this to evaluateAllPolicies +\n // PolicyViolationsLog.append. Ad-hoc callers leave it undefined; their tests\n // don't need policy side effects.\n onPolicyTrigger?: (graph: NeatGraph) => Promise<void> | void\n // 4xx-burst coalescing state, keyed by `${source}->${peer}` (issue #481).\n // Lazily created the first time handleSpan sees a 4xx CLIENT/PRODUCER span.\n // Carried on the context so each project/daemon keeps its own bursts and a\n // long-lived handler accumulates across spans; ad-hoc callers reuse one ctx\n // across a batch and get the same coalescing.\n burstState?: Map<string, BurstState>\n}\n\nconst HOUR_MS = 60 * 60 * 1000\nconst DAY_MS = 24 * HOUR_MS\n\n// Per-edge-type stale thresholds. HTTP CALLS at 24h is meaningless because\n// healthy traffic recurs in seconds; infra DEPENDS_ON is the opposite — a\n// docker-compose service can sit idle overnight without anything being wrong.\n// Override via NEAT_STALE_THRESHOLDS (JSON, ms-per-edge-type).\nconst DEFAULT_STALE_THRESHOLDS: Record<string, number> = {\n CALLS: HOUR_MS,\n CONNECTS_TO: 4 * HOUR_MS,\n PUBLISHES_TO: 4 * HOUR_MS,\n CONSUMES_FROM: 4 * HOUR_MS,\n DEPENDS_ON: DAY_MS,\n CONFIGURED_BY: DAY_MS,\n RUNS_ON: DAY_MS,\n}\n// Fallback for any edge type not in the map (forward compat — adding a new\n// EdgeType shouldn't break staleness sweeps).\nconst FALLBACK_STALE_THRESHOLD_MS = DAY_MS\n\nfunction loadStaleThresholdsFromEnv(): Record<string, number> {\n const raw = process.env.NEAT_STALE_THRESHOLDS\n if (!raw) return DEFAULT_STALE_THRESHOLDS\n try {\n const overrides = JSON.parse(raw) as Record<string, unknown>\n const merged = { ...DEFAULT_STALE_THRESHOLDS }\n for (const [k, v] of Object.entries(overrides)) {\n if (typeof v === 'number' && Number.isFinite(v) && v >= 0) merged[k] = v\n }\n return merged\n } catch (err) {\n console.warn(\n `[neat] NEAT_STALE_THRESHOLDS could not be parsed (${(err as Error).message}); using defaults`,\n )\n return DEFAULT_STALE_THRESHOLDS\n }\n}\n\nexport function thresholdForEdgeType(\n edgeType: string,\n overrides?: Record<string, number>,\n): number {\n const map = overrides ?? loadStaleThresholdsFromEnv()\n return map[edgeType] ?? FALLBACK_STALE_THRESHOLD_MS\n}\n\n// Failing-response incident tuning. A span that completes 5xx, carries an\n// ERROR status, or an exception event records an incident on its own — those\n// are unambiguous failures. A 4xx CLIENT/PRODUCER span doesn't: a single 404 is\n// often correct app behavior (auth probe, conditional fetch). 4xx becomes a\n// signal only when it repeats — N consecutive 4xx against the same (source,\n// peer) pair inside a window record ONE coalesced incident carrying the count\n// and the dominant status code, rather than N separate lines that would drown\n// the history. Mirrors the NEAT_STALE_THRESHOLDS override shape.\n// threshold — how many consecutive 4xx against one peer trip the burst.\n// windowMs — the gap that ends a burst; a 4xx more than this after the\n// previous one starts a fresh burst rather than extending it.\nconst DEFAULT_INCIDENT_THRESHOLDS = {\n threshold: 5,\n windowMs: 60_000,\n}\n\nfunction loadIncidentThresholdsFromEnv(): { threshold: number; windowMs: number } {\n const raw = process.env.NEAT_INCIDENT_THRESHOLDS\n if (!raw) return DEFAULT_INCIDENT_THRESHOLDS\n try {\n const overrides = JSON.parse(raw) as Record<string, unknown>\n const merged = { ...DEFAULT_INCIDENT_THRESHOLDS }\n if (\n typeof overrides.threshold === 'number' &&\n Number.isFinite(overrides.threshold) &&\n overrides.threshold >= 1\n ) {\n merged.threshold = Math.floor(overrides.threshold)\n }\n if (\n typeof overrides.windowMs === 'number' &&\n Number.isFinite(overrides.windowMs) &&\n overrides.windowMs >= 0\n ) {\n merged.windowMs = overrides.windowMs\n }\n return merged\n } catch (err) {\n console.warn(\n `[neat] NEAT_INCIDENT_THRESHOLDS could not be parsed (${(err as Error).message}); using defaults`,\n )\n return DEFAULT_INCIDENT_THRESHOLDS\n }\n}\n\n// An attribute bag — either a live span's `attributes` or the passthrough set a\n// recorded ErrorEvent carries. The message helpers read from both, so the same\n// \"what failed here\" logic that names an incident at record time can re-derive\n// it at read time (dedupeIncidents).\ntype AttrBag = Record<string, unknown>\n\n// Read the HTTP response status off an attribute bag. OTel semconv renamed this\n// attribute — modern SDKs write `http.response.status_code`, older ones\n// `http.status_code`. Returns undefined when neither is present or parseable, so\n// a span with no response status is never misclassified as a failure.\nfunction httpResponseStatusFromAttrs(attrs: AttrBag): number | undefined {\n for (const key of ['http.response.status_code', 'http.status_code']) {\n const v = attrs[key]\n if (typeof v === 'number' && Number.isFinite(v)) return v\n if (typeof v === 'string') {\n const n = Number(v)\n if (Number.isFinite(n)) return n\n }\n }\n return undefined\n}\n\nfunction httpResponseStatus(span: ParsedSpan): number | undefined {\n return httpResponseStatusFromAttrs(span.attributes)\n}\n\n// A human incident line built from the HTTP context a server span carries even\n// when no exception event was recorded — an Express error handler that answers\n// 500 cleanly leaves `span.exception` empty but still carries the route and\n// status. \"500 on GET /users/:id\" reads better than the literal 'unknown\n// error'. Returns undefined when the bag has no usable HTTP context, so a\n// non-HTTP failure falls through to nonHttpFailureMessage / 'unknown error'.\n// Method/route follow the OTel semconv rename (modern `http.request.method` /\n// legacy `http.method`; `http.route` matched template, `http.target` / `url.path`\n// concrete-path fallback).\nfunction httpFailureMessageFromAttrs(attrs: AttrBag): string | undefined {\n const status = httpResponseStatusFromAttrs(attrs)\n const route = pickAttrFrom(attrs, 'http.route', 'http.target', 'url.path')\n const method = pickAttrFrom(attrs, 'http.request.method', 'http.method')\n const where = route ? `${method ? `${method} ` : ''}${route}` : undefined\n if (status !== undefined && where) return `${status} on ${where}`\n if (status !== undefined) return `HTTP ${status}`\n if (where) return `error on ${where}`\n return undefined\n}\n\n// Canonical gRPC status code → name (grpc/status.proto). Fixed protocol\n// constants shared by every gRPC implementation — not driver/engine data, so\n// they don't belong in compat.json (Rule 8 governs the latter, not a wire enum).\nconst GRPC_STATUS_NAMES: Record<number, string> = {\n 1: 'CANCELLED',\n 2: 'UNKNOWN',\n 3: 'INVALID_ARGUMENT',\n 4: 'DEADLINE_EXCEEDED',\n 5: 'NOT_FOUND',\n 6: 'ALREADY_EXISTS',\n 7: 'PERMISSION_DENIED',\n 8: 'RESOURCE_EXHAUSTED',\n 9: 'FAILED_PRECONDITION',\n 10: 'ABORTED',\n 11: 'OUT_OF_RANGE',\n 12: 'UNIMPLEMENTED',\n 13: 'INTERNAL',\n 14: 'UNAVAILABLE',\n 15: 'DATA_LOSS',\n 16: 'UNAUTHENTICATED',\n}\n\nfunction grpcStatusCodeFromAttrs(attrs: AttrBag): number | undefined {\n const v = attrs['rpc.grpc.status_code']\n if (typeof v === 'number' && Number.isFinite(v)) return v\n if (typeof v === 'string') {\n const n = Number(v)\n if (Number.isFinite(n)) return n\n }\n return undefined\n}\n\n// A non-HTTP failure still carries its cause in span attributes — a non-OK gRPC\n// status, or a transport-level connection error (ECONNREFUSED reaching a peer).\n// Reading them keeps the incident from degrading to the literal 'unknown error'\n// when the span has no exception event and no HTTP response code. Returns\n// undefined for a span carrying neither, so the 'unknown error' floor still\n// applies to a genuinely opaque failure (issue #624).\nfunction nonHttpFailureMessageFromAttrs(attrs: AttrBag): string | undefined {\n const grpc = grpcStatusCodeFromAttrs(attrs)\n if (grpc !== undefined && grpc !== 0) {\n const name = GRPC_STATUS_NAMES[grpc] ?? `status ${grpc}`\n const detail = pickAttrFrom(attrs, 'rpc.grpc.status_message')\n return detail ? `gRPC ${name}: ${detail}` : `gRPC ${name}`\n }\n // Transport/connection failure — OTel's `error.type` carries the errno\n // (ECONNREFUSED, ETIMEDOUT, …) or the exception class for a call that never\n // got a response. Skip the HTTP status-class forms (\"500\", \"_OTHER\") that\n // http semconv also writes there; the HTTP path above owns those.\n const errType = pickAttrFrom(attrs, 'error.type')\n if (errType && errType !== '_OTHER' && !/^\\d+$/.test(errType)) {\n const peer = pickAttrFrom(attrs, 'server.address', 'net.peer.name', 'net.host.name')\n return peer ? `${errType} connecting to ${peer}` : errType\n }\n return undefined\n}\n\n// The incident's human message: the recorded exception first, then the HTTP\n// context a server span still carries, then a non-HTTP (gRPC / connection)\n// failure read from attributes, and only then the 'unknown error' floor. Shared\n// by every incident write path so the fallback chain can't drift between the\n// receiver's synchronous write and handleSpan's inline write.\nfunction incidentMessage(span: ParsedSpan): string {\n return (\n span.exception?.message ??\n httpFailureMessageFromAttrs(span.attributes) ??\n nonHttpFailureMessageFromAttrs(span.attributes) ??\n 'unknown error'\n )\n}\n\n// In-flight 4xx burst against one (source, peer) pair. Lives on IngestContext so\n// it survives across spans without leaking into module state shared by every\n// project. firstTs/lastTs are the span timestamps (ADR-033 — span time, not\n// wall clock); codes counts each 4xx by status so the dominant one can be named\n// when the burst flushes.\ninterface BurstState {\n count: number\n firstTs: string\n lastTs: string\n lastMs: number\n codes: Map<number, number>\n}\n\nfunction nowIso(ctx: IngestContext): string {\n return new Date(ctx.now ? ctx.now() : Date.now()).toISOString()\n}\n\n// One-time-per-session-per-project warning for spans whose resource omits\n// `service.name`. The OTel spec requires SDKs to set it; customised exporters\n// occasionally don't. Routing the span to `service:unidentified` keeps\n// diagnostic visibility intact (silent drop hides a real SDK misconfiguration);\n// the warning gives an operator one line of stderr per project to act on.\n// See docs/contracts/otlp-routing.md §Fallback when `resource.service.name`\n// is missing.\nconst unidentifiedWarnedProjects = new Set<string>()\nfunction warnUnidentifiedSpan(project: string): void {\n if (unidentifiedWarnedProjects.has(project)) return\n unidentifiedWarnedProjects.add(project)\n console.warn(\n `[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`,\n )\n}\n\n// Test seam — production code never calls this. Tests that exercise the\n// once-per-session contract reset between cases so each assertion sees a\n// fresh warned-set.\nexport function resetUnidentifiedSpanWarnings(): void {\n unidentifiedWarnedProjects.clear()\n}\n\n// One-time-per-session-per-service audit for a compiled `dist/...js` call site\n// that carried no adjacent source map (file-awareness.md §4 + §6). Without a\n// map, ingest can't reconcile the observed dist file to the static `src/...ts`\n// the extractor parsed — the dist path is the honest answer, never a fabricated\n// src path. The leak this surfaces (issue #430) was hiding behind an absolute\n// path prefix; once the path is service-root-relative the mismatch is legible,\n// and this line tells the operator how to close it.\nconst noSourceMapWarnedServices = new Set<string>()\nfunction warnNoSourceMaps(serviceName: string): void {\n if (noSourceMapWarnedServices.has(serviceName)) return\n noSourceMapWarnedServices.add(serviceName)\n console.warn(\n `[neat] ${serviceName}: no .map files found under dist/; observed file edges will land on dist paths, not src. Set sourceMap: true in tsconfig to enable file-level reconciliation.`,\n )\n}\n\n// Test seam — mirrors resetUnidentifiedSpanWarnings for the once-per-service\n// audit above.\nexport function resetNoSourceMapWarnings(): void {\n noSourceMapWarnedServices.clear()\n}\n\nfunction pickAttrFrom(attrs: AttrBag, ...keys: string[]): string | undefined {\n for (const k of keys) {\n const v = attrs[k]\n if (typeof v === 'string' && v.length > 0) return v\n }\n return undefined\n}\n\nfunction pickAttr(span: ParsedSpan, ...keys: string[]): string | undefined {\n return pickAttrFrom(span.attributes, ...keys)\n}\n\nfunction hostFromUrl(u: string | undefined): string | undefined {\n if (!u) return undefined\n try {\n return new URL(u).hostname\n } catch {\n return undefined\n }\n}\n\n// OTel HTTP/db semconv has gone through several names for \"the host on the\n// other end of this call.\" Try the modern ones first, fall back to the legacy\n// ones, then last resort parse out of a full URL.\nfunction pickAddress(span: ParsedSpan): string | undefined {\n return (\n pickAttr(span, 'server.address', 'net.peer.name', 'net.host.name') ??\n hostFromUrl(pickAttr(span, 'url.full', 'http.url'))\n )\n}\n\n// A loopback peer address is this host talking to itself, never a distinct\n// upstream service. Cross-service correlation on the callee's SERVER span (the\n// parent-span fallback, ADR-033) recovers the real peer, so a loopback address\n// on a CLIENT span must not mint a standalone frontier:localhost /\n// frontier:127.0.0.1 that duplicates that resolved edge (issues #590, #577).\n// Scoped to the cross-service CALLS path — a loopback database is a real local\n// dependency and keeps its CONNECTS_TO edge.\nfunction isLoopbackHost(host: string): boolean {\n const h = host.toLowerCase()\n return (\n h === 'localhost' ||\n h === 'ip6-localhost' ||\n h === '::1' ||\n h === '[::1]' ||\n /^127(?:\\.\\d{1,3}){3}$/.test(h)\n )\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Call-site capture (file-awareness.md §4)\n//\n// The call-site processor stamps the source location on CLIENT/PRODUCER spans.\n// OpenTelemetry stabilized the code attributes at semconv v1.33 —\n// `code.file.path` / `code.line.number` / `code.function.name` — renaming the\n// prior `code.filepath` / `code.lineno` / `code.function`. NEAT's own emit\n// templates (installers/templates.ts, and the Python processor, ADR-151) and any\n// third-party instrumentation may carry either generation, so the read accepts\n// BOTH, stable-name first. When present, an OBSERVED relationship originates from\n// the file rather than the service; a span with neither stays service-level, and\n// evidence is never fabricated (§6).\nconst CODE_FILE_PATH_ATTR = 'code.file.path' // semconv ≥1.33 (stable)\nconst CODE_FILEPATH_ATTR = 'code.filepath' // prior convention\nconst CODE_LINE_NUMBER_ATTR = 'code.line.number'\nconst CODE_LINENO_ATTR = 'code.lineno'\nconst CODE_FUNCTION_NAME_ATTR = 'code.function.name'\nconst CODE_FUNCTION_ATTR = 'code.function'\n\n// Read the source call site off an attribute bag, accepting the stable\n// (semconv ≥1.33) and prior attribute names. Exported so every code.* reader —\n// edge attribution here, incident localization in traverse.ts — shares one\n// definition and the names can't drift across sites.\nexport function codeFilepathOf(attrs: Record<string, AttributeValue>): string | undefined {\n const stable = attrs[CODE_FILE_PATH_ATTR]\n if (typeof stable === 'string' && stable.length > 0) return stable\n const prior = attrs[CODE_FILEPATH_ATTR]\n return typeof prior === 'string' && prior.length > 0 ? prior : undefined\n}\nexport function codeLinenoOf(attrs: Record<string, AttributeValue>): number | undefined {\n const stable = attrs[CODE_LINE_NUMBER_ATTR]\n if (typeof stable === 'number' && Number.isFinite(stable)) return stable\n const prior = attrs[CODE_LINENO_ATTR]\n return typeof prior === 'number' && Number.isFinite(prior) ? prior : undefined\n}\nexport function codeFunctionOf(attrs: Record<string, AttributeValue>): string | undefined {\n const stable = attrs[CODE_FUNCTION_NAME_ATTR]\n if (typeof stable === 'string' && stable.length > 0) return stable\n const prior = attrs[CODE_FUNCTION_ATTR]\n return typeof prior === 'string' && prior.length > 0 ? prior : undefined\n}\n\nfunction toPosix(p: string): string {\n return p.split('\\\\').join('/')\n}\n\nfunction languageForExt(relPath: string): string | undefined {\n const dot = relPath.lastIndexOf('.')\n if (dot === -1) return undefined\n switch (relPath.slice(dot).toLowerCase()) {\n case '.py':\n return 'python'\n case '.go':\n return 'go'\n case '.ts':\n case '.tsx':\n return 'typescript'\n case '.js':\n case '.jsx':\n case '.mjs':\n case '.cjs':\n return 'javascript'\n default:\n return undefined\n }\n}\n\n// Join the runtime `code.filepath` against the service root so the OBSERVED\n// relPath lines up with the EXTRACTED service-relative path (file-awareness.md\n// §4 + §7). ServiceNode.repoPath is the scanPath-relative package dir; its\n// segments appear inside the absolute runtime path, so anchoring on it recovers\n// the package-relative tail. With no usable anchor, the real runtime path is\n// returned in a relative-looking form — honest, even if it doesn't align with a\n// static src path. Never fabricated.\nfunction relPathForRuntimeFile(\n filepath: string,\n serviceNode?: ServiceNode,\n scanPath?: string,\n): string | null {\n let p = toPosix(filepath).replace(/^file:\\/\\//, '')\n // When ingest knows the absolute scan root, the service's absolute root is\n // `scanPath/<repoPath>`. Stripping it directly recovers the service-relative\n // tail (`dist/foo.js`) even for a single-package service whose `repoPath` is\n // empty — the segment anchor below has nothing to grab in that case, so the\n // absolute path used to leak into the FileNode key (issue #430).\n if (scanPath && scanPath.length > 0) {\n const absRoot = toPosix(path.resolve(scanPath, serviceNode?.repoPath ?? ''))\n const anchor = absRoot.endsWith('/') ? absRoot : `${absRoot}/`\n if (p.startsWith(anchor)) return p.slice(anchor.length)\n }\n const root = serviceNode?.repoPath\n if (root && root !== '.' && root.length > 0) {\n const rootPosix = toPosix(root)\n const anchor = `/${rootPosix}/`\n const idx = p.lastIndexOf(anchor)\n if (idx !== -1) return p.slice(idx + anchor.length)\n const base = rootPosix.split('/').filter(Boolean).pop()\n if (base) {\n const baseAnchor = `/${base}/`\n const bidx = p.lastIndexOf(baseAnchor)\n if (bidx !== -1) return p.slice(bidx + baseAnchor.length)\n }\n }\n p = p.replace(/^[A-Za-z]:/, '').replace(/^\\/+/, '')\n return p.length > 0 ? p : null\n}\n\n// Exported so the connectors plane (packages/core/src/connectors/index.ts,\n// docs/contracts/connectors.md) can build one from a signal's own callSite —\n// a connector's file:line comes from the provider's telemetry rather than an\n// OTel span's code.* attributes, but reconciles onto the same EXTRACTED\n// FileNode through the same primitives below.\nexport interface CallSite {\n relPath: string\n line?: number\n fn?: string\n // The service-relative dist path the call site was captured on, when ingest\n // resolved it through a source map to a different (source) `relPath`\n // (file-awareness.md §4). Surfaces as FileNode.originalPath. Absent when the\n // captured frame was already source-grained.\n originalRelPath?: string\n}\n\n// dist→src source-map resolution (file-awareness.md §4). A runtime call site in\n// a compiled `dist/...js` is resolved through a disk-adjacent `.map` to the\n// original `src/...ts`, so an OBSERVED edge lands on the source file an agent\n// can open. Same-host only — when the daemon's filesystem doesn't carry the map\n// (a service that ran on a different machine) the dist frame is kept, honestly,\n// never fabricated (§6). Each dist file is read from disk once: a present map\n// caches its consumer, an absent one caches `null`. Synchronous reads keep\n// callSiteFromSpan synchronous; the cost is amortised across a file's spans.\nconst sourceMapCache = new Map<\n string,\n { consumer: sourceMapJs.SourceMapConsumer; dir: string } | null\n>()\n\ninterface ResolvedSrc {\n filepath: string\n line?: number\n}\n\nfunction resolveDistToSrc(absFilepath: string, line?: number): ResolvedSrc | null {\n if (!absFilepath.endsWith('.js')) return null\n let entry = sourceMapCache.get(absFilepath)\n if (entry === undefined) {\n entry = null\n const mapPath = `${absFilepath}.map`\n try {\n if (existsSync(mapPath)) {\n const raw = JSON.parse(readFileSync(mapPath, 'utf8')) as unknown\n const consumer = new sourceMapJs.SourceMapConsumer(raw as never)\n entry = { consumer, dir: path.dirname(mapPath) }\n }\n } catch {\n entry = null\n }\n sourceMapCache.set(absFilepath, entry)\n }\n if (!entry) return null\n try {\n const pos = entry.consumer.originalPositionFor({\n line: line !== undefined && Number.isFinite(line) ? line : 1,\n column: 0,\n })\n if (!pos || !pos.source) return null\n const root = entry.consumer.sourceRoot ?? ''\n const resolved = path.resolve(entry.dir, root, pos.source)\n return { filepath: resolved, ...(pos.line ? { line: pos.line } : {}) }\n } catch {\n return null\n }\n}\n\n// Read the call-site attributes off a span. Returns null when the span carries\n// no `code.filepath` (SERVER spans, un-instrumented peers, callee side) so the\n// caller falls back to a service-level edge.\nfunction callSiteFromSpan(\n span: ParsedSpan,\n serviceNode?: ServiceNode,\n scanPath?: string,\n): CallSite | null {\n const filepath = codeFilepathOf(span.attributes)\n if (filepath === undefined) return null\n let line = codeLinenoOf(span.attributes)\n // Resolve a compiled dist frame to its source before computing the service-\n // relative path, so the FileNode lands on the original `src/...ts`.\n const abs = toPosix(filepath).replace(/^file:\\/\\//, '')\n const resolved = resolveDistToSrc(abs, line)\n let effectivePath = filepath\n let originalRelPath: string | undefined\n if (resolved) {\n originalRelPath = relPathForRuntimeFile(filepath, serviceNode, scanPath) ?? undefined\n effectivePath = resolved.filepath\n if (resolved.line !== undefined) line = resolved.line\n }\n const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath)\n if (!relPath) return null\n // A compiled `dist/...js` call site that didn't resolve through a map keeps\n // the (honest) dist path. Surface the absence once per service so the\n // operator can enable source maps and recover src-level reconciliation\n // (file-awareness.md §4 + §6, issue #430).\n if (!resolved && abs.endsWith('.js') && relPath.startsWith('dist/') && serviceNode?.name) {\n warnNoSourceMaps(serviceNode.name)\n }\n const fn = codeFunctionOf(span.attributes)\n return {\n relPath,\n ...(line !== undefined ? { line } : {}),\n ...(fn ? { fn } : {}),\n ...(originalRelPath && originalRelPath !== relPath ? { originalRelPath } : {}),\n }\n}\n\n// Reconcile a runtime-derived relPath onto the service-relative path the\n// extractor already minted, so OBSERVED and EXTRACTED FileNodes for the same\n// source file fuse into ONE node instead of two disjoint subgraphs\n// (file-awareness.md §4 — ingest joins the runtime path against the service\n// root to land the edge on a FileNode).\n//\n// relPathForRuntimeFile anchors the absolute `code.filepath` against scanPath /\n// repoPath. When that anchor can't be found — no scanPath wired, or the span\n// was emitted from a service whose absolute root differs from the daemon's\n// checkout (a container image rooted at `/app`, a relocated clone) — the\n// leftover relPath still carries the unanchored leading segments\n// (`app/src/foo.ts`, `Users/me/repo/src/foo.ts`) and forks a parallel FileNode\n// keyed off the absolute path. That splits the graph: the OBSERVED layer never\n// lands on the EXTRACTED `src/foo.ts` node, and divergence/traversal see two\n// half-graphs for one file.\n//\n// The extractor's FileNode paths are ground truth for which service-relative\n// paths exist. Recover the right one by matching the longest EXTRACTED (non-\n// OTel) FileNode path that is a trailing segment-suffix of the runtime relPath.\n// A match means the runtime path is the same file the extractor parsed, just\n// carrying extra leading directories the anchor couldn't strip — reuse the\n// extractor's path so both layers key the same node. No match means the file is\n// genuinely OTel-only; the honest runtime path stands (never fabricated, §6).\nexport function reconcileObservedRelPath(\n graph: NeatGraph,\n serviceName: string,\n relPath: string,\n): string {\n // Already lands on a known node (the anchor resolved cleanly, or a prior span\n // created this node) — fused, nothing to recover.\n if (graph.hasNode(fileId(serviceName, relPath))) return relPath\n let best: string | null = null\n graph.forEachNode((_id, attrs) => {\n const a = attrs as FileNode & { type?: string }\n if (a.type !== NodeType.FileNode || a.service !== serviceName) return\n // Only fuse onto a statically-known file. An existing OTel-only node would\n // already have matched the hasNode short-circuit above.\n if (a.discoveredVia === 'otel') return\n const p = a.path\n if (!p) return\n if ((relPath === p || relPath.endsWith(`/${p}`)) && (!best || p.length > best.length)) {\n best = p\n }\n })\n return best ?? relPath\n}\n\n// Ensure the FileNode for an observed call site and the owning service's\n// OBSERVED `CONTAINS` edge both exist, returning the FileNode id so the caller\n// can originate the relationship from it (file-awareness.md §1–2 + §4). The\n// CONTAINS edge carries no `lastObserved` — structural ownership doesn't go\n// STALE when traffic quiets (markStaleEdges skips edges without lastObserved),\n// and divergence detection skips CONTAINS so an OTel-only file node doesn't\n// surface as a missing-extracted finding.\nexport function ensureObservedFileNode(\n graph: NeatGraph,\n serviceName: string,\n serviceNodeId: string,\n callSite: CallSite,\n): string {\n // Key the file node on the service the span already FUSED onto, not the raw\n // span service.name. ensureServiceNode resolves a differently-cased or\n // env-tagged OTEL_SERVICE_NAME onto the extracted service (`proofrun` → the\n // ServiceNode named `ProofRun`, #880). Keying the file on the raw name here\n // forks a `file:proofrun:…` twin off the extracted `file:ProofRun:…` — the\n // file-level sibling of #880. Reading the canonical name back off the fused\n // service node also lets reconcileObservedRelPath recover the extractor's\n // relative path from a runtime absolute path: its file scan filters by an\n // exact-cased `service`, so the raw name skipped every extracted file before\n // the suffix match could run. Both forks — case and absolute path — close here.\n const svcAttrs = graph.hasNode(serviceNodeId) ? (graph.getNodeAttributes(serviceNodeId) as ServiceNode) : undefined\n const canonicalService =\n svcAttrs && svcAttrs.type === NodeType.ServiceNode && typeof svcAttrs.name === 'string'\n ? svcAttrs.name\n : serviceName\n const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath)\n const fileNodeId = fileId(canonicalService, relPath)\n if (!graph.hasNode(fileNodeId)) {\n const language = languageForExt(relPath)\n const node: FileNode = {\n id: fileNodeId,\n type: NodeType.FileNode,\n service: canonicalService,\n path: relPath,\n ...(language ? { language } : {}),\n ...(callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {}),\n discoveredVia: 'otel',\n }\n graph.addNode(fileNodeId, node)\n }\n const containsId = makeObservedEdgeId(EdgeType.CONTAINS, serviceNodeId, fileNodeId)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: serviceNodeId,\n target: fileNodeId,\n type: EdgeType.CONTAINS,\n provenance: Provenance.OBSERVED,\n }\n graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge)\n }\n // Observed-first edges land one grain finer than the file when the call site\n // falls inside a symbol's definition span (ADR-158, file-awareness.md §4).\n return landObservedSymbol(graph, fileNodeId, canonicalService, relPath, callSite)\n}\n\n// The terminal name of a source-declared qualname (`OrderService.create` →\n// `create`) — what `code.function` carries on a span, and the tiebreaker that\n// picks the intended symbol when nested definition spans both contain the line.\nfunction terminalName(qualname: string): string {\n const dot = qualname.lastIndexOf('.')\n return dot === -1 ? qualname : qualname.slice(dot + 1)\n}\n\n// Given the symbols whose definition span contains the observed `code.line`,\n// pick the one the edge lands on. Line-in-span is primary (every candidate\n// already contains the line); `code.function` is the tiebreaker and drift check —\n// when it names one of the candidates, that symbol wins; otherwise the innermost\n// (smallest) span wins, since a call site inside nested definitions belongs to\n// the tightest one enclosing it (ADR-158 point 4).\nfunction pickContainingSymbol(\n candidates: { id: string; symbol: SymbolNode }[],\n fn: string | undefined,\n): string {\n const bySpan = (a: { symbol: SymbolNode }, b: { symbol: SymbolNode }): number => {\n const sizeA = a.symbol.span.endLine - a.symbol.span.startLine\n const sizeB = b.symbol.span.endLine - b.symbol.span.startLine\n if (sizeA !== sizeB) return sizeA - sizeB\n // Deterministic tiebreak on an exact-size tie: the later-starting span is the\n // more deeply nested one.\n return b.symbol.span.startLine - a.symbol.span.startLine\n }\n if (fn) {\n const named = candidates.filter((c) => terminalName(c.symbol.qualname) === fn)\n if (named.length > 0) return [...named].sort(bySpan)[0]!.id\n }\n return [...candidates].sort(bySpan)[0]!.id\n}\n\n// Mint the OBSERVED symbol a runtime call landed on that static never produced —\n// the symbol-grain missing-extracted signal (ADR-158 point 5, lifecycle.md\n// auto-create-and-merge). `code.function` names it, so the symbol is honest, not\n// guessed; the span is the single observed line NEAT can vouch for, and static\n// fields override it on the next extract pass if the definition later resolves.\n// The file owns it through an OBSERVED `file ──CONTAINS──▶ symbol` edge.\nfunction ensureObservedSymbolNode(\n graph: NeatGraph,\n fileNodeId: string,\n service: string,\n relPath: string,\n fn: string,\n line: number,\n): string {\n const sid = symbolId(service, relPath, fn)\n if (!graph.hasNode(sid)) {\n const node: SymbolNode = {\n id: sid,\n type: NodeType.SymbolNode,\n kind: 'function',\n qualname: fn,\n span: { startLine: line, endLine: line },\n service,\n relPath,\n discoveredVia: 'otel',\n }\n graph.addNode(sid, node)\n }\n const containsId = makeObservedEdgeId(EdgeType.CONTAINS, fileNodeId, sid)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: fileNodeId,\n target: sid,\n type: EdgeType.CONTAINS,\n provenance: Provenance.OBSERVED,\n }\n graph.addEdgeWithKey(containsId, fileNodeId, sid, edge)\n }\n return sid\n}\n\n// Resolve the observed edge's origin to a symbol under the file when the call\n// site's line falls inside a symbol's definition span; otherwise land on the file\n// (ADR-158, file-awareness.md §4–§5). Boundary-grained, not a full call graph:\n// the returned node is what the edge originates from.\n//\n// Degrade to the file honestly when there is no line, or the line is inside no\n// symbol span and either the file was never symbol-extracted (no symbols to be\n// \"missing\" from) or the span carries no function name. When the file *was*\n// symbol-extracted, the line is in no static symbol, and `code.function` names\n// the executing function, mint an `otel` symbol — the missing-extracted signal at\n// symbol grain (point 5), never a degrade that hides the dynamic wiring.\nfunction landObservedSymbol(\n graph: NeatGraph,\n fileNodeId: string,\n service: string,\n relPath: string,\n callSite: CallSite,\n): string {\n const line = callSite.line\n if (line === undefined) return fileNodeId\n\n let sawSymbol = false\n const candidates: { id: string; symbol: SymbolNode }[] = []\n graph.forEachOutboundEdge(fileNodeId, (_edge, edgeAttrs, _source, target) => {\n if (edgeAttrs.type !== EdgeType.CONTAINS) return\n const t = graph.getNodeAttributes(target) as GraphNode\n if (t.type !== NodeType.SymbolNode) return\n sawSymbol = true\n if (line >= t.span.startLine && line <= t.span.endLine) {\n candidates.push({ id: target, symbol: t })\n }\n })\n\n if (candidates.length > 0) return pickContainingSymbol(candidates, callSite.fn)\n\n if (sawSymbol && callSite.fn) {\n return ensureObservedSymbolNode(graph, fileNodeId, service, relPath, callSite.fn, line)\n }\n return fileNodeId\n}\n\n// Edge id helpers live in @neat.is/types/identity.ts (ADR-029). The local\n// signatures below preserve the (type, source, target) argument order ingest.ts\n// has used historically while delegating to the canonical wire-format helpers.\nfunction makeObservedEdgeId(type: EdgeTypeValue, source: string, target: string): string {\n return observedEdgeId(source, target, type)\n}\n\nfunction makeInferredEdgeId(type: EdgeTypeValue, source: string, target: string): string {\n return inferredEdgeId(source, target, type)\n}\n\nconst INFERRED_CONFIDENCE = 0.6\nconst STITCH_MAX_DEPTH = 2\n\n// The trace stitcher only reasons about runtime *dependency* edges — the ones an\n// error actually propagates along (a service calling a service, connecting to a\n// datastore, a declared runtime dependency). Structural edges (CONTAINS a file,\n// IMPORTS a module, CONFIGURED_BY a ConfigNode, RUNS_ON a host) are static facts\n// learned by extraction; a 500 says nothing new about them. Minting an INFERRED\n// twin of a structural EXTRACTED edge would corrupt the trust signal — the twin\n// (conf 0.6) outranks the ground-truth EXTRACTED edge (0.85) under PROV_RANK, so\n// consumer queries would surface the inference in place of the hard fact\n// (docs/contracts/trace-stitcher.md — dependency-edge-type allowlist).\nconst STITCH_EDGE_TYPES = new Set<EdgeTypeValue>([\n EdgeType.CALLS,\n EdgeType.CONNECTS_TO,\n EdgeType.DEPENDS_ON,\n])\n\n// OTLP-wire SpanKind values. The receiver decodes the raw wire integer onto\n// `ParsedSpan.kind` (otel.ts), and the wire enum is offset by one from the\n// `@opentelemetry/api` SpanKind the SDK uses in-process — UNSPECIFIED 0,\n// INTERNAL 1, SERVER 2, CLIENT 3, PRODUCER 4, CONSUMER 5. So we must NOT import\n// `@opentelemetry/api` here: its CLIENT is 2 (= wire SERVER) and PRODUCER is 3\n// (= wire CLIENT), which would gate the wrong kinds. Cross-referenced with the\n// wire fixtures in otel.test.ts (kind 2 = SERVER, kind 3 = CLIENT) and the\n// CLIENT call-site spans in ingest.test.ts (kind 3).\nconst WIRE_SPAN_KIND_CLIENT = 3\nconst WIRE_SPAN_KIND_PRODUCER = 4\nconst WIRE_SPAN_KIND_CONSUMER = 5\n\n// The caller-side gate for the CALLS / CONNECTS_TO paths. A CLIENT or PRODUCER\n// span is the caller/producer side of a service-to-service call or a datastore\n// read; INTERNAL / SERVER spans are not — a SERVER span is the callee, and its\n// edge is minted from its parent CLIENT via the parent-span fallback. Without\n// this gate every INTERNAL span that happens to carry a peer address — e.g. a\n// `tcp.connect` / `tls.connect` to an AWS endpoint — mints a spurious\n// service-level edge (issue #429), because no §4 capture layer stamps `code.*`\n// on INTERNAL spans.\n//\n// CONSUMER spans are handled by the messaging branch in handleSpan, not here:\n// a queue consumer isn't calling a service, it's reading a topic, so it mints a\n// CONSUMES_FROM edge to the destination node (spanMintsMessagingEdge below) —\n// the observed mirror of the static consumer→topic edge, the queue-side pair of\n// the PRODUCER→topic PUBLISHES_TO edge.\n//\n// A span that reports no kind (undefined) or UNSPECIFIED (0) carries no\n// caller/callee signal, so it falls back to the historical unconditional\n// behavior — hand-built and legacy producers keep minting. The leak this gates\n// is always an explicitly-kinded INTERNAL span.\nfunction spanMintsObservedEdge(kind: number | undefined): boolean {\n if (kind === undefined || kind === 0) return true\n return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER\n}\n\n// The messaging counterpart to the caller-side gate. A PRODUCER span publishes\n// to a destination; a CONSUMER span reads from one. Both sides mint an OBSERVED\n// edge to the topic/queue node — the PRODUCER a PUBLISHES_TO, the CONSUMER a\n// CONSUMES_FROM — so declared and observed queue topology fuse. Only spans that\n// actually carry a messaging destination reach this (handleSpan checks the\n// semconv attrs first), so a stray CONSUMER span with no destination stays inert.\nfunction spanMintsMessagingEdge(kind: number | undefined): boolean {\n return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER\n}\n\n// The GraphQL execution span is emitted by the service that RESOLVES the\n// operation — a SERVER or INTERNAL span (the instrumentation's `graphql.execute`),\n// never the CLIENT side that only ever sees an opaque `POST /graphql`. Gating out\n// the caller/producer/consumer kinds keeps a client-side operation span from\n// attributing the operation to the caller (client-side operation attribution is\n// deferred, ADR-122); an unkinded / UNSET span still mints, mirroring the\n// caller-side gate's legacy-producer fallback.\nfunction spanServesGraphqlOperation(kind: number | undefined): boolean {\n return (\n kind !== WIRE_SPAN_KIND_CLIENT &&\n kind !== WIRE_SPAN_KIND_PRODUCER &&\n kind !== WIRE_SPAN_KIND_CONSUMER\n )\n}\n\n// A GraphQL operation node keyed on (service, operationType, operationName) via\n// graphqlOperationId (ADR-122). Minted observed-first from the execution span so\n// operation-level topology is legible even before any static GraphQL extractor\n// exists; a later static extractor fuses onto the same id. `operationType` is\n// stored lower-cased to match the id's normalisation, so a `query` observed and a\n// `Query` static resolver land on one node. Idempotent — a high-volume operation\n// upserts, never grows the node set.\nfunction ensureGraphqlOperationNode(\n graph: NeatGraph,\n serviceName: string,\n operationType: string,\n operationName: string,\n): string {\n const id = graphqlOperationId(serviceName, operationType, operationName)\n if (graph.hasNode(id)) return id\n const node: GraphQLOperationNode = {\n id,\n type: NodeType.GraphQLOperationNode,\n name: operationName,\n service: serviceName,\n operationType: operationType.toLowerCase(),\n operationName,\n discoveredVia: 'otel',\n }\n graph.addNode(id, node)\n return id\n}\n\n// The gRPC execution span carrying `rpc.service` / `rpc.method` is emitted on\n// both sides of a call — the SERVER that resolves the method and the CLIENT that\n// invokes it. Only the serving side owns the method (ADR-123): gating out the\n// caller/producer/consumer kinds keeps a CLIENT gRPC span from attributing\n// ownership to the caller (that client span still falls through to the\n// cross-service resolver below, so the caller→callee edge is unaffected).\n// Client→method attribution is deferred. An unkinded / UNSET span still mints,\n// mirroring the graphql and caller-side gates' legacy fallbacks.\nfunction spanServesGrpcMethod(kind: number | undefined): boolean {\n return (\n kind !== WIRE_SPAN_KIND_CLIENT &&\n kind !== WIRE_SPAN_KIND_PRODUCER &&\n kind !== WIRE_SPAN_KIND_CONSUMER\n )\n}\n\n// A gRPC method node keyed on (rpcService, rpcMethod) via grpcMethodId (ADR-123).\n// `rpcService` is the fully-qualified `rpc.service` the wire carries verbatim\n// (`orders.OrderService`), so an OBSERVED span and a static `.proto` definition\n// fuse onto one node rather than twinning. Idempotent — a high-volume method\n// upserts, never grows the node set.\nfunction ensureGrpcMethodNode(\n graph: NeatGraph,\n rpcService: string,\n rpcMethod: string,\n): string {\n const id = grpcMethodId(rpcService, rpcMethod)\n if (graph.hasNode(id)) return id\n const node: GrpcMethodNode = {\n id,\n type: NodeType.GrpcMethodNode,\n name: `${rpcService}/${rpcMethod}`,\n rpcService,\n rpcMethod,\n discoveredVia: 'otel',\n }\n graph.addNode(id, node)\n return id\n}\n\n// The HTTP upgrade span that opens a WebSocket is emitted by the service that\n// SERVES the channel — a SERVER span (the framework's inbound `GET`), or an\n// unkinded / INTERNAL span from a hand-built handshake. The CLIENT that dials the\n// socket carries the same upgrade header, but the channel belongs to the server\n// (ADR-125), so gating out the caller/producer/consumer kinds keeps a client-side\n// upgrade span from attributing the channel to the caller. An unkinded / UNSET\n// span still mints, mirroring the graphql and gRPC serving-side gates.\nfunction spanServesWebsocketChannel(kind: number | undefined): boolean {\n return (\n kind !== WIRE_SPAN_KIND_CLIENT &&\n kind !== WIRE_SPAN_KIND_PRODUCER &&\n kind !== WIRE_SPAN_KIND_CONSUMER\n )\n}\n\n// A WebSocket channel node keyed on (service, channel) via websocketChannelId\n// (ADR-125). Minted OBSERVED-only from the upgrade span — a WebSocket channel is\n// known from observation, never from static extraction, so there is no declared\n// twin to fuse with and no static producer to fill in `path` / `line` (those stay\n// absent, never fabricated — file-awareness.md §6). Idempotent — every reconnect\n// on the same channel upserts, never grows the node set.\nfunction ensureWebsocketChannelNode(\n graph: NeatGraph,\n serviceName: string,\n channel: string,\n): string {\n const id = websocketChannelId(serviceName, channel)\n if (graph.hasNode(id)) return id\n const node: WebSocketChannelNode = {\n id,\n type: NodeType.WebSocketChannelNode,\n name: channel,\n service: serviceName,\n channel,\n discoveredVia: 'otel',\n }\n graph.addNode(id, node)\n return id\n}\n\n// A messaging destination node (Kafka topic, queue, stream) keyed exactly the\n// way the static extractor keys it, so the OBSERVED and EXTRACTED edges fuse\n// onto one node instead of twinning. The Kafka static side names its topic\n// `infra:kafka-topic:<topic>` (extract/calls/kafka.ts), so the kind is\n// `<messaging.system>-topic` — `kafka` → `kafka-topic` — and the same shape\n// generalises to every messaging system the semconv names. `provider: 'self'`\n// mirrors the static extractor's non-AWS provider so an observed-first node\n// merges cleanly when static analysis later reaches the same destination.\nfunction messagingDestinationKind(system: string): string {\n return `${system}-topic`\n}\n\nfunction ensureMessagingDestinationNode(\n graph: NeatGraph,\n system: string,\n destination: string,\n): string {\n const id = infraId(messagingDestinationKind(system), destination)\n if (graph.hasNode(id)) return id\n const node: InfraNode = {\n id,\n type: NodeType.InfraNode,\n name: destination,\n provider: 'self',\n kind: messagingDestinationKind(system),\n }\n graph.addNode(id, node)\n return id\n}\n\n// Parent-span TTL cache (ADR-033). Address-based peer resolution (server.address /\n// net.peer.name / url.full) misses non-HTTP RPCs and any span with an opaque\n// peer. The cache stores each span's service keyed by `${traceId}:${spanId}` so\n// a child span whose address resolution fails can fall back to its parent's\n// service, identifying a cross-service CALLS edge from parent → current.\n//\n// Bounded size + TTL — out-of-order arrival (child before parent) drops the\n// child rather than buffering. We accept that loss because the cache is best-\n// effort: for every cross-service call, the CLIENT span on the caller side\n// covers the same edge via address-based resolution, so missing one direction\n// is recoverable.\nconst PARENT_SPAN_CACHE_SIZE = 10_000\nconst PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1000\n\ninterface ParentSpanCacheEntry {\n service: string\n // Env discriminator from the parent span (ADR-074 §2). The parent-span\n // fallback in handleSpan uses this so the auto-created parent ServiceNode\n // lands on the same env-tagged id the OTel emitter advertised.\n env: string\n // The parent span's own `code.*` call site, when its SpanProcessor captured\n // one (file-awareness.md §4). The parent-span fallback below originates its\n // edge from the parent's FileNode instead of the bare parent ServiceNode when\n // this is present, so the fallback edge anchors to file:line rather than\n // pinning to a service node (issue #536). Undefined when the parent carried no\n // call site — never fabricated (§6), so the service-level fallback stands.\n callSite?: CallSite\n expiresAt: number\n}\n\nconst parentSpanCache = new Map<string, ParentSpanCacheEntry>()\n\nfunction parentSpanKey(traceId: string, spanId: string): string {\n return `${traceId}:${spanId}`\n}\n\nfunction cacheSpanService(span: ParsedSpan, now: number, callSite: CallSite | null): void {\n if (!span.traceId || !span.spanId) return\n const key = parentSpanKey(span.traceId, span.spanId)\n // Map preserves insertion order, so deleting + re-inserting bumps an entry to\n // the back. Eviction is \"drop oldest\" once size exceeds the cap.\n parentSpanCache.delete(key)\n parentSpanCache.set(key, {\n service: span.service,\n env: span.env ?? 'unknown',\n ...(callSite ? { callSite } : {}),\n expiresAt: now + PARENT_SPAN_CACHE_TTL_MS,\n })\n while (parentSpanCache.size > PARENT_SPAN_CACHE_SIZE) {\n const oldest = parentSpanCache.keys().next().value\n if (!oldest) break\n parentSpanCache.delete(oldest)\n }\n}\n\nfunction lookupParentSpan(\n traceId: string,\n parentSpanId: string,\n now: number,\n): { service: string; env: string; callSite?: CallSite } | null {\n const entry = parentSpanCache.get(parentSpanKey(traceId, parentSpanId))\n if (!entry) return null\n if (entry.expiresAt <= now) {\n parentSpanCache.delete(parentSpanKey(traceId, parentSpanId))\n return null\n }\n return {\n service: entry.service,\n env: entry.env,\n ...(entry.callSite ? { callSite: entry.callSite } : {}),\n }\n}\n\n// Test seam: lets unit tests start from a clean slate.\nexport function resetParentSpanCache(): void {\n parentSpanCache.clear()\n}\n\n// Peer host → ServiceNode id resolution. With env-dimension (ADR-074 §2),\n// the same `name` may live across multiple ServiceNodes — one per env, plus\n// the env-less form from static extraction. When `env` is known (the source\n// span's env), prefer a same-env match; fall back to the env-less node so\n// EXTRACTED edges from static analysis remain reachable until OBSERVED\n// traffic from the same env promotes them.\n//\n// Match passes:\n// 1. Exact id lookup for `(host, env)` — `serviceId(host, env)`.\n// 2. Exact id lookup for env-less `serviceId(host)`.\n// 3. Name/alias scan across every ServiceNode, preferring same-env then\n// env-less then any other env.\nfunction resolveServiceId(\n graph: NeatGraph,\n host: string,\n env: string,\n): string | null {\n const envTagged = serviceId(host, env)\n if (graph.hasNode(envTagged)) return envTagged\n const envLess = serviceId(host)\n if (envLess !== envTagged && graph.hasNode(envLess)) return envLess\n\n let sameEnv: string | null = null\n let envLessMatch: string | null = null\n let anyMatch: string | null = null\n graph.forEachNode((id, attrs) => {\n if (sameEnv) return\n const a = attrs as ServiceNode & { type?: string }\n if (a.type !== NodeType.ServiceNode) return\n const matchesByName = a.name === host\n const matchesByAlias = a.aliases ? a.aliases.includes(host) : false\n if (!matchesByName && !matchesByAlias) return\n const nodeEnv = a.env ?? 'unknown'\n if (nodeEnv === env) {\n sameEnv = id\n return\n }\n if (nodeEnv === 'unknown' && !envLessMatch) envLessMatch = id\n else if (!anyMatch) anyMatch = id\n })\n return sameEnv ?? envLessMatch ?? anyMatch\n}\n\nexport function frontierIdFor(host: string): string {\n return frontierId(host)\n}\n\n// Auto-create a minimal ServiceNode for span.service when no such node exists.\n// Used at the top of handleSpan so subsequent edge upserts always have endpoints\n// — without it, OBSERVED edges silently drop for any service the static\n// extractor hasn't reached yet (and never reaches at all in OTel-only setups).\n// `language: 'unknown'` is the contract's specified placeholder (ADR-033). When\n// static extraction later produces a ServiceNode at the same id, addServiceNodes\n// merges and flips discoveredVia to 'merged' rather than overwriting.\n// Exported for the connectors plane (connectors/index.ts) — a connector\n// signal needs the same auto-vivified ServiceNode any OTel span gets before\n// an edge upsert, since a connector-sourced service may never have been\n// statically extracted or seen a span yet.\nexport function ensureServiceNode(\n graph: NeatGraph,\n serviceName: string,\n env: string,\n): string {\n const id = serviceId(serviceName, env)\n if (graph.hasNode(id)) return id\n // #880 — fuse an observed span onto the service the static extractor already\n // minted, rather than forking a twin that never joins the extracted graph.\n // Two things push the observed id away from the extracted `service:<name>`:\n // `deployment.environment` (standard OTel semconv, commonly set) tags it\n // `service:<name>:<env>`, and OTEL_SERVICE_NAME is routinely a differently-\n // cased form of the manifest name the extractor used (`casetest` vs the\n // registered `CaseTest`). Either one alone split the fused graph in two —\n // `observed-dependencies` reported \"no traffic\" on the extracted node while\n // the traffic sat on the twin, and every divergence doubled and blamed the\n // user's extractor. So match an existing EXTRACTED ServiceNode by name,\n // case-insensitively and ignoring env, and land the observation on it. An\n // OTel-only service with no extracted node still mints its own node below.\n const wanted = serviceName.toLowerCase()\n const extractedId = graph.findNode((_nid, attrs) => {\n if (attrs.type !== NodeType.ServiceNode) return false\n const svc = attrs as ServiceNode\n // Skip observed twins (discoveredVia 'otel') — only fuse onto a statically\n // extracted (or already-merged) service, never onto another observed node.\n if (svc.discoveredVia === 'otel') return false\n return typeof svc.name === 'string' && svc.name.toLowerCase() === wanted\n })\n if (extractedId) return extractedId\n const node: ServiceNode = {\n id,\n type: NodeType.ServiceNode,\n name: serviceName,\n language: 'unknown',\n discoveredVia: 'otel',\n ...(env !== 'unknown' ? { env } : {}),\n }\n graph.addNode(id, node)\n return id\n}\n\n// Exported so a connector's generic pipeline (connectors/index.ts) can create\n// an honest placeholder InfraNode for a provider-named resource no static\n// extractor has (yet) declared — a provider module has no mutation authority\n// of its own (ADR-030), so `resolveTarget` declares this need instead of\n// creating the node itself (ADR-133 §4a, docs/contracts/connectors.md).\n// Idempotent, same shape as `ensureServiceNode`.\nexport function ensureInfraNode(\n graph: NeatGraph,\n kind: string,\n name: string,\n provider: string,\n): string {\n const id = infraId(kind, name)\n if (graph.hasNode(id)) return id\n const node: InfraNode = {\n id,\n type: NodeType.InfraNode,\n name,\n provider,\n kind,\n }\n graph.addNode(id, node)\n return id\n}\n\n// InfraNode kinds that carry columns — a database table read at column grain\n// (ADR-157 §1). `sql-table` is the canonical table node the OTel `db.statement`\n// mint and the Neon connector both land on; `supabase-table` is the same table\n// grain on the Supabase pull path. A project-level or non-table InfraNode (a\n// `supabase` project node, a `mongodb-collection`, a queue/route node) carries no\n// columns, so a merge onto one is a no-op.\nconst COLUMN_BEARING_INFRA_KINDS = new Set(['sql-table', 'supabase-table'])\n\n// Fold columns onto a table InfraNode at the given provenance (ADR-157 §1-2).\n// Columns are provenanced attributes on the table node, never their own nodes. A\n// column records each side it has been seen with in its `provenances` set — a\n// declared column production also touches ends up `[EXTRACTED, OBSERVED]`, not one\n// side clobbering the other — so column drift (ADR-157 §4) can read the declared\n// set and the observed set off one node. A no-op for a non-table node or an empty\n// column set, so a wildcard / join / DDL statement (which recovers no columns)\n// grows nothing. Mutation lives here in ingest.ts per the lifecycle authority\n// (ADR-030); the pure fold is columns.ts so extract/* can call it without a cycle.\nfunction mergeColumnsAt(\n graph: NeatGraph,\n tableNodeId: string,\n columns: readonly string[] | undefined,\n provenance: ProvenanceValue,\n confidence: number,\n): void {\n if (!columns || columns.length === 0 || !graph.hasNode(tableNodeId)) return\n const node = graph.getNodeAttributes(tableNodeId) as InfraNode\n if (node.type !== NodeType.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {\n return\n }\n graph.replaceNodeAttributes(tableNodeId, {\n ...node,\n columns: foldColumns(node.columns, columns, provenance, confidence),\n })\n}\n\n// Merge the columns a production statement touched onto a table InfraNode as\n// OBSERVED (ADR-157 §1-2). OTel ingest and the connector mint path both call in.\nexport function mergeObservedColumns(\n graph: NeatGraph,\n tableNodeId: string,\n columns: readonly string[] | undefined,\n): void {\n mergeColumnsAt(graph, tableNodeId, columns, Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE)\n}\n\n// Merge the columns a schema/ORM declares onto a table InfraNode as EXTRACTED\n// (ADR-157 §3). The declared side of column grain: the static extractor recovers\n// each column at database-name fidelity and calls in from extract/calls/*.\nexport function mergeDeclaredColumns(\n graph: NeatGraph,\n tableNodeId: string,\n columns: readonly string[] | undefined,\n confidence: number,\n): void {\n mergeColumnsAt(graph, tableNodeId, columns, Provenance.EXTRACTED, confidence)\n}\n\n// Same shape for unseen db.system + host pairs. Engine comes off the OTel\n// attribute as a string per Rule 8 — no hardcoded engine list. compatibleDrivers\n// is empty until static extraction merges in the matrix-derived drivers.\nfunction ensureDatabaseNode(graph: NeatGraph, host: string, engine: string): string {\n const id = databaseId(host)\n if (graph.hasNode(id)) return id\n const node: DatabaseNode = {\n id,\n type: NodeType.DatabaseNode,\n name: host,\n engine,\n engineVersion: 'unknown',\n compatibleDrivers: [],\n host,\n discoveredVia: 'otel',\n }\n graph.addNode(id, node)\n return id\n}\n\n// An in-process / embedded database (SQLite, better-sqlite3, an in-memory\n// store) crosses no network boundary, so its span carries no peer host to key a\n// DatabaseNode on. Key it on a service-scoped local identity instead so two\n// services each reading their own `app.db` stay distinct nodes rather than\n// collapsing onto one (ADR-118). `name` is the logical database from the span\n// (db.name) when present, the engine string otherwise. `host` is intentionally\n// omitted — an embedded database has no network host, and evidence is never\n// fabricated (file-awareness.md §6); host-mismatch divergence skips a hostless\n// DatabaseNode. Returns the node id so the caller can point the CONNECTS_TO edge\n// at it. Idempotent — high-volume DB spans upsert, never grow the node set.\nfunction ensureLocalDatabaseNode(\n graph: NeatGraph,\n serviceName: string,\n name: string,\n engine: string,\n): string {\n const id = localDatabaseId(serviceName, name)\n if (graph.hasNode(id)) return id\n const node: DatabaseNode = {\n id,\n type: NodeType.DatabaseNode,\n name,\n engine,\n engineVersion: 'unknown',\n compatibleDrivers: [],\n discoveredVia: 'otel',\n }\n graph.addNode(id, node)\n return id\n}\n\n// ADR-141 — an ORM such as Prisma emits a `db.system` span with no peer host\n// (its query engine backdates the span off the connection), so a host-less span\n// would mint a fresh service-local DatabaseNode and leave the service's\n// statically declared database — the one parsed from its connection string / ORM\n// schema — with no OBSERVED twin: a false `missing-observed` divergence for a DB\n// the service demonstrably hammers. Before minting a local node, fuse onto the\n// service's already-declared database of the same engine when there is exactly\n// one. Ambiguous (two-plus same-engine declared DBs) or none falls back to the\n// ADR-118 service-local node — the fusion never guesses which of several it is.\nfunction findDeclaredDatabaseForService(\n graph: NeatGraph,\n serviceNodeId: string,\n engine: string,\n): string | null {\n if (!graph.hasNode(serviceNodeId)) return null\n // A declared database CONNECTS_TO is file-grained by design — it originates\n // from the config file that named the connection (databases/index.ts,\n // file-awareness §7), not the service node. So scan the service node and every\n // file it CONTAINS for a same-engine EXTRACTED CONNECTS_TO target.\n const sources = [serviceNodeId]\n for (const edgeId of graph.outboundEdges(serviceNodeId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type === EdgeType.CONTAINS) sources.push(e.target)\n }\n const matches = new Set<string>()\n for (const src of sources) {\n if (!graph.hasNode(src)) continue\n for (const edgeId of graph.outboundEdges(src)) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (edge.type !== EdgeType.CONNECTS_TO || edge.provenance !== Provenance.EXTRACTED) continue\n if (!graph.hasNode(edge.target)) continue\n const target = graph.getNodeAttributes(edge.target) as DatabaseNode\n if (target.type !== NodeType.DatabaseNode || target.engine !== engine) continue\n matches.add(edge.target)\n }\n }\n return matches.size === 1 ? [...matches][0]! : null\n}\n\nfunction ensureFrontierNode(graph: NeatGraph, host: string, ts: string): string {\n const id = frontierIdFor(host)\n if (graph.hasNode(id)) {\n const existing = graph.getNodeAttributes(id) as FrontierNode\n graph.replaceNodeAttributes(id, { ...existing, lastObserved: ts })\n return id\n }\n const node: FrontierNode = {\n id,\n type: NodeType.FrontierNode,\n name: host,\n host,\n firstObserved: ts,\n lastObserved: ts,\n }\n graph.addNode(id, node)\n return id\n}\n\n// Exported so the connectors plane (connectors/index.ts) mints edges through\n// the identical primitive OTel ingest uses — a connector-sourced edge and a\n// span-sourced edge are meant to be indistinguishable to traversal,\n// divergence, and staleness (docs/connectors/README.md).\nexport interface UpsertResult {\n edge: GraphEdge\n created: boolean\n}\n\nexport function upsertObservedEdge(\n graph: NeatGraph,\n type: EdgeTypeValue,\n source: string,\n target: string,\n ts: string,\n isError = false,\n evidence?: { file: string; line?: number },\n): UpsertResult | null {\n if (!graph.hasNode(source) || !graph.hasNode(target)) return null\n\n // ADR-142 — grain is a stored fact, not a re-derivation. A `file:` source means\n // a call site was captured (observedSource / the connector's callSite path both\n // mint a FileNode only when they have one), so the edge is file-grained;\n // anything else (service:/infra:/frontier:) is the coarse service-grained\n // fallback. Set on both the OTel and the connector path — they share this mint.\n // A `symbol:` source is finer still (ADR-158) — the call site landed on the\n // calling symbol under its file — and belongs in the same call-site-captured\n // bucket as `file:`, never the coarse service fallback.\n const grain: 'file' | 'service' =\n source.startsWith('file:') || source.startsWith('symbol:') ? 'file' : 'service'\n\n const id = makeObservedEdgeId(type, source, target)\n if (graph.hasEdge(id)) {\n const existing = graph.getEdgeAttributes(id) as GraphEdge\n const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1\n const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0)\n const newSignal = {\n spanCount: newSpanCount,\n errorCount: newErrorCount,\n lastObservedAgeMs: 0,\n }\n // ADR-066 §2 — confidence grades from the signal block. PROV_RANK stays;\n // the grade reflects volume + recency + error ratio within the OBSERVED\n // tier.\n const updated: GraphEdge = {\n ...existing,\n provenance: Provenance.OBSERVED,\n lastObserved: ts,\n callCount: newSpanCount,\n signal: newSignal,\n confidence: confidenceForObservedSignal(newSignal),\n grain, // backfills legacy edges that predate ADR-142\n }\n graph.replaceEdgeAttributes(id, updated)\n return { edge: updated, created: false }\n }\n\n const signal = {\n spanCount: 1,\n errorCount: isError ? 1 : 0,\n lastObservedAgeMs: 0,\n }\n const edge: GraphEdge = {\n id,\n source,\n target,\n type,\n provenance: Provenance.OBSERVED,\n confidence: confidenceForObservedSignal(signal),\n lastObserved: ts,\n callCount: 1,\n signal,\n grain,\n // Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).\n // Only set when code.filepath was present on the span — never fabricated.\n ...(evidence ? { evidence } : {}),\n }\n graph.addEdgeWithKey(id, source, target, edge)\n return { edge, created: true }\n}\n\n// When a span errors, the system is exercising its dependencies right now even\n// if some of them aren't auto-instrumented (pg 7.4.0 in the demo, see ADR-014).\n// Walk EXTRACTED edges out from the erroring service for a couple of hops and\n// promote them to INFERRED twins so traversal can prefer them over the bare\n// static edges without claiming OBSERVED-grade certainty.\nfunction stitchTrace(graph: NeatGraph, sourceServiceId: string, ts: string): void {\n if (!graph.hasNode(sourceServiceId)) return\n\n const visited = new Set<string>([sourceServiceId])\n const queue: { nodeId: string; depth: number }[] = [{ nodeId: sourceServiceId, depth: 0 }]\n\n while (queue.length > 0) {\n const { nodeId, depth } = queue.shift()!\n if (depth >= STITCH_MAX_DEPTH) continue\n\n const outbound = graph.outboundEdges(nodeId)\n for (const edgeId of outbound) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (edge.provenance !== Provenance.EXTRACTED) continue\n\n // Only runtime dependency edges get stitched. Structural edges (CONTAINS /\n // IMPORTS / CONFIGURED_BY / RUNS_ON) are never mirrored into INFERRED twins\n // and the BFS does not recurse through them — an error propagates along\n // dependencies, not static containment (trace-stitcher.md allowlist).\n if (!STITCH_EDGE_TYPES.has(edge.type)) continue\n\n // OBSERVED twin already covers this hop with ground truth — no inference\n // needed (ADR-034). Stomping it with INFERRED erases the gap NEAT exists\n // to surface; skipping it keeps the OBSERVED edge as the authoritative\n // record and avoids cluttering the graph with a redundant INFERRED twin.\n if (graph.hasEdge(observedEdgeId(edge.source, edge.target, edge.type))) continue\n\n upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts)\n\n if (!visited.has(edge.target)) {\n visited.add(edge.target)\n queue.push({ nodeId: edge.target, depth: depth + 1 })\n }\n }\n }\n}\n\nfunction upsertInferredEdge(\n graph: NeatGraph,\n type: EdgeTypeValue,\n source: string,\n target: string,\n ts: string,\n): void {\n const id = makeInferredEdgeId(type, source, target)\n if (graph.hasEdge(id)) {\n const existing = graph.getEdgeAttributes(id) as GraphEdge\n const updated: GraphEdge = { ...existing, lastObserved: ts }\n graph.replaceEdgeAttributes(id, updated)\n return\n }\n\n const edge: GraphEdge = {\n id,\n source,\n target,\n type,\n provenance: Provenance.INFERRED,\n confidence: INFERRED_CONFIDENCE,\n lastObserved: ts,\n }\n graph.addEdgeWithKey(id, source, target, edge)\n}\n\nasync function appendErrorEvent(ctx: IngestContext, ev: ErrorEvent): Promise<void> {\n await fs.mkdir(path.dirname(ctx.errorsPath), { recursive: true })\n await fs.appendFile(ctx.errorsPath, JSON.stringify(ev) + '\\n', 'utf8')\n}\n\n// Resolve the incident's affectedNode. When the span carries a `code.filepath`\n// call site, the incident attributes to the FileNode the failure surfaced in —\n// the same file grain OBSERVED CALLS edges land on (file-awareness.md §4) —\n// resolving a compiled `dist/...js` frame through its disk-adjacent source map\n// when one is present. Without a call site it stays at the originating service,\n// the honest fallback (§2).\n//\n// The runtime `code.filepath` is a deploy-absolute path (`/var/task/...` on\n// Lambda, `/app/...` in a container image) that need not match the daemon's\n// checkout. When the graph is available it's reconciled onto the service-\n// relative path the extractor already minted (reconcileObservedRelPath, the\n// same trailing-suffix match the OBSERVED edge origin uses), so the incident\n// lands on the ONE fused FileNode instead of a phantom keyed off the absolute\n// path — the node root-cause actually walks. Without a graph the honest runtime\n// path stands: the file node may not be materialised yet, but querying the\n// service still surfaces the incident, and the file:line is real (§6 — never\n// fabricated).\nfunction incidentAffectedNode(\n span: ParsedSpan,\n graph?: NeatGraph,\n scanPath?: string,\n): string {\n const sid = serviceId(span.service, span.env)\n const serviceNode =\n graph && graph.hasNode(sid)\n ? (graph.getNodeAttributes(sid) as ServiceNode)\n : undefined\n const callSite = callSiteFromSpan(span, serviceNode, scanPath)\n if (callSite) {\n const relPath = graph\n ? reconcileObservedRelPath(graph, span.service, callSite.relPath)\n : callSite.relPath\n return fileId(span.service, relPath)\n }\n return sid\n}\n\n// Build the minimal ErrorEvent the receiver writes synchronously before\n// replying (ADR-033 §Error events, amended). affectedNode attributes to the\n// FileNode when the span carries a `code.filepath` call site, else to the\n// originating service (incidentAffectedNode above).\n//\n// errorMessage reads from the exception event's `exception.message` (OTel\n// semconv) so the incident surface shows the actual thrown error string.\n// When the span carries no exception event the field falls back to the HTTP\n// context the span still holds — \"500 on GET /users/:id\" (httpFailureMessage)\n// — and only then to the literal 'unknown error'. `span.name` is never in the\n// chain: OTel HTTP server instrumentation routinely populates it with the HTTP\n// method, which produces incidents that read 'GET' or 'POST' instead of the\n// underlying failure. `span.status.message` is intentionally out for the same\n// reason.\n// Span attributes pass through verbatim so consumers can read source\n// attribution (`code.filepath`, `code.lineno`, `code.function`) and other\n// SDK-emitted context without ingest enumerating every key it cares about.\n// Coerce span attributes to a JSON-safe shape — bigint values from the\n// parsed span (long ids, high-cardinality counters) become strings so the\n// passthrough record can be serialised to the ErrorEvent shape and round-\n// tripped through ErrorEventSchema. All other types pass through verbatim.\nfunction sanitizeAttributes(\n attrs: ParsedSpan['attributes'],\n): Record<string, string | number | boolean | null | string[] | number[] | boolean[]> {\n const out: Record<string, string | number | boolean | null | string[] | number[] | boolean[]> = {}\n for (const [k, v] of Object.entries(attrs)) {\n if (typeof v === 'bigint') out[k] = v.toString()\n else out[k] = v as string | number | boolean | null | string[] | number[] | boolean[]\n }\n return out\n}\n\nexport function buildErrorEventForReceiver(\n span: ParsedSpan,\n graph?: NeatGraph,\n scanPath?: string,\n): ErrorEvent | null {\n if (span.statusCode !== 2) return null\n const ts = span.startTimeIso ?? new Date().toISOString()\n const attrs = sanitizeAttributes(span.attributes)\n return {\n id: `${span.traceId}:${span.spanId}`,\n timestamp: ts,\n service: span.service,\n traceId: span.traceId,\n spanId: span.spanId,\n errorMessage: incidentMessage(span),\n ...(span.exception?.type ? { exceptionType: span.exception.type } : {}),\n ...(span.exception?.stacktrace\n ? { exceptionStacktrace: span.exception.stacktrace }\n : {}),\n ...(Object.keys(attrs).length > 0 ? { attributes: attrs } : {}),\n affectedNode: incidentAffectedNode(span, graph, scanPath),\n }\n}\n\n// Synchronous file-write helper bound to a receiver. The receiver awaits this\n// before replying, so a write failure surfaces as 500 → OTel SDK retries.\nexport function makeErrorSpanWriter(\n errorsPath: string,\n graph?: NeatGraph,\n scanPath?: string,\n): (span: ParsedSpan) => Promise<void> {\n return async (span) => {\n const ev = buildErrorEventForReceiver(span, graph, scanPath)\n if (!ev) return\n await fs.mkdir(path.dirname(errorsPath), { recursive: true })\n await fs.appendFile(errorsPath, JSON.stringify(ev) + '\\n', 'utf8')\n }\n}\n\n// Write one failing-response incident (issue #481) to errors.ndjson. Used for\n// an unambiguous 5xx (count 1) and for a flushed 4xx burst (count N). The\n// dominant status code names the failure; `incidentCount` carries N so the\n// incident surface shows \"5× 404\" without the per-span flood. Span attributes\n// pass through verbatim, same as the statusCode === 2 path, so source\n// attribution (`code.*`) and the response code survive to the consumer.\nasync function recordFailingResponseIncident(\n ctx: IngestContext,\n span: ParsedSpan,\n affectedNode: string,\n timestamp: string,\n statusCode: number,\n count: number,\n firstTimestamp?: string,\n): Promise<void> {\n const attrs = sanitizeAttributes(span.attributes)\n const first = firstTimestamp ?? timestamp\n const peer = pickAddress(span)\n const message =\n count > 1\n ? `${count} consecutive HTTP ${statusCode} responses` +\n (peer ? ` to ${peer}` : '')\n : `HTTP ${statusCode} response` + (peer ? ` from ${peer}` : '')\n const ev: ErrorEvent = {\n id: `${span.traceId}:${span.spanId}`,\n timestamp,\n service: span.service,\n traceId: span.traceId,\n spanId: span.spanId,\n errorType: 'http-failure',\n errorMessage: message,\n ...(Object.keys(attrs).length > 0 ? { attributes: attrs } : {}),\n affectedNode,\n httpStatusCode: statusCode,\n incidentCount: count,\n firstTimestamp: first,\n lastTimestamp: timestamp,\n }\n await appendErrorEvent(ctx, ev)\n}\n\n// Record one incident for a span that carries an exception event but no ERROR\n// status and no HTTP failure code — an async / queue / background worker\n// (bullmq, Redis Streams, a scheduled task) whose job threw (ADR-117). Incident\n// recording keys on the failure signal, not on HTTP context: the message\n// follows the shared incidentMessage chain and the record attributes to the\n// handler file:line when the span carries `code.filepath`, else to the\n// originating service (incidentAffectedNode). handleSpan owns this write — the\n// receiver's synchronous error-writer fires only for statusCode === 2, so an\n// exception-only worker span reaches durability here, the same way the\n// response-code incidents below do.\nasync function recordExceptionIncident(\n ctx: IngestContext,\n span: ParsedSpan,\n ts: string,\n): Promise<void> {\n const attrs = sanitizeAttributes(span.attributes)\n const ev: ErrorEvent = {\n id: `${span.traceId}:${span.spanId}`,\n timestamp: ts,\n service: span.service,\n traceId: span.traceId,\n spanId: span.spanId,\n errorMessage: incidentMessage(span),\n ...(span.exception?.type ? { exceptionType: span.exception.type } : {}),\n ...(span.exception?.stacktrace\n ? { exceptionStacktrace: span.exception.stacktrace }\n : {}),\n ...(Object.keys(attrs).length > 0 ? { attributes: attrs } : {}),\n affectedNode: incidentAffectedNode(span, ctx.graph, ctx.scanPath),\n }\n await appendErrorEvent(ctx, ev)\n}\n\n// Advance the 4xx burst for this (source, peer) pair (issue #481). A burst\n// accumulates silently; only when it crosses the threshold inside the window\n// does it flush ONE coalesced incident. A 4xx that arrives more than windowMs\n// after the previous one resets the burst — a slow trickle of probes never\n// coalesces. The dominant code is the most frequent 4xx seen across the burst.\nasync function advance4xxBurst(\n ctx: IngestContext,\n span: ParsedSpan,\n affectedNode: string,\n ts: string,\n nowMs: number,\n status: number,\n): Promise<void> {\n const { threshold, windowMs } = loadIncidentThresholdsFromEnv()\n if (!ctx.burstState) ctx.burstState = new Map()\n const peer = pickAddress(span) ?? span.spanId\n const key = `${span.service}->${peer}`\n const existing = ctx.burstState.get(key)\n let state: BurstState\n if (existing && nowMs - existing.lastMs <= windowMs) {\n existing.count += 1\n existing.lastTs = ts\n existing.lastMs = nowMs\n existing.codes.set(status, (existing.codes.get(status) ?? 0) + 1)\n state = existing\n } else {\n state = {\n count: 1,\n firstTs: ts,\n lastTs: ts,\n lastMs: nowMs,\n codes: new Map([[status, 1]]),\n }\n ctx.burstState.set(key, state)\n }\n\n if (state.count < threshold) return\n\n // Threshold met — flush one incident carrying the count, the dominant code,\n // and the burst's first/last timestamps, then clear the burst so the next\n // run of failures records its own incident rather than re-flushing every span.\n let dominant = status\n let max = 0\n for (const [code, n] of state.codes) {\n if (n > max) {\n max = n\n dominant = code\n }\n }\n await recordFailingResponseIncident(\n ctx,\n span,\n affectedNode,\n state.lastTs,\n dominant,\n state.count,\n state.firstTs,\n )\n ctx.burstState.delete(key)\n}\n\n// Match a SERVER span's `http.route` to the RouteNode the static extractor\n// minted, by normalized (method, template) so param-syntax differences\n// (`{id}` vs `:id` vs `<int:id>`) don't block the fusion. Returns the node id or\n// undefined; an unmatched route mints nothing here — a route NEAT never extracted\n// has no declared twin to fuse, and the served-but-undeclared case is a follow-on.\nfunction findRouteNodeByHttpRoute(\n graph: NeatGraph,\n serviceName: string,\n method: string | undefined,\n httpRoute: string,\n): string | undefined {\n const target = normalizePathTemplate(httpRoute)\n const m = method?.toUpperCase()\n let found: string | undefined\n graph.forEachNode((id, attrs) => {\n if (found) return\n const a = attrs as RouteNode & { type?: string }\n if (a.type !== NodeType.RouteNode || a.service !== serviceName) return\n if (m && a.method !== 'ALL' && a.method !== m) return\n if (normalizePathTemplate(a.pathTemplate) === target) found = id\n })\n return found\n}\n\nexport async function handleSpan(ctx: IngestContext, span: ParsedSpan): Promise<void> {\n // lastObserved derives from the span's own startTime per ADR-033 — replayed\n // traces and out-of-order spans get a timestamp that reflects when the call\n // actually fired, not when the receiver received it. Wall-clock is only the\n // fallback for spans whose startTimeUnixNano is missing or unparseable.\n const ts = span.startTimeIso ?? nowIso(ctx)\n const nowMs = ctx.now ? ctx.now() : Date.now()\n // Env discriminator from `deployment.environment(.name)` (ADR-074 §2).\n // Older ParsedSpan producers may omit it — fall back to the literal\n // `'unknown'` so the env-less wire format is preserved on auto-creation.\n const env = span.env ?? 'unknown'\n // Issue #374 — spans whose resource omits `service.name` route to\n // `service:unidentified` in the URL-resolved project (the parser already\n // substitutes the fallback). One warning per project per session names\n // the project so an operator can fix the SDK config without grepping.\n if (span.resourceServiceNamePresent === false) {\n warnUnidentifiedSpan(ctx.project ?? DEFAULT_PROJECT)\n }\n // Auto-create a minimal ServiceNode for unseen span.service so OBSERVED\n // edges land instead of silently dropping. Static extraction merges richer\n // fields when it later finds the same id (ADR-033). The node is env-tagged\n // when the span carries an env signal.\n const sourceId = ensureServiceNode(ctx.graph, span.service, env)\n const isError = span.statusCode === 2\n\n // File-first OBSERVED origin (file-awareness.md §4). When the injected\n // SpanProcessor captured a call site on this outbound (CLIENT/PRODUCER) span,\n // the relationship originates from the file; without one it stays\n // service-level. `observedSource()` creates the FileNode + CONTAINS lazily so\n // they only land when an edge actually does — and never for the inbound\n // (SERVER) parent-fallback side, which carries no call site.\n const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId) as ServiceNode\n const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath)\n\n // Stash this span in the parent-span cache so any later child whose address\n // resolution misses can still resolve the cross-service edge via parentSpanId.\n // The call site rides along so the fallback edge anchors to this span's\n // file:line when this span turns out to be a parent (issue #536).\n cacheSpanService(span, nowMs, callSite)\n const observedSource = (): string =>\n callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId\n // Evidence for the OBSERVED edge — populated from the span's code.* semconv\n // when the call site resolved (file-awareness.md §4 + §6). Never fabricated:\n // absent call site → undefined evidence. The path is reconciled the same way\n // the edge's origin node is (reconcileObservedRelPath), so evidence.file names\n // the fused EXTRACTED path the edge lands on rather than the raw deployed\n // absolute path — otherwise the edge node and its own evidence disagree.\n const callSiteEvidence: { file: string; line?: number } | undefined = callSite\n ? {\n file: reconcileObservedRelPath(ctx.graph, span.service, callSite.relPath),\n ...(callSite.line !== undefined ? { line: callSite.line } : {}),\n }\n : undefined\n\n let affectedNode = sourceId\n\n // Only the caller/producer side of a call mints an OBSERVED edge directly\n // (issue #429). INTERNAL / SERVER / CONSUMER spans don't: a SERVER/CONSUMER\n // span is the callee, and its edge is minted from its parent via the\n // parent-span fallback below (left ungated). Gating here keeps INTERNAL\n // connection spans (`tcp.connect` / `tls.connect` with a peer address) from\n // minting spurious service-level edges.\n const mintsFromCallerSide = spanMintsObservedEdge(span.kind)\n\n if (span.dbSystem) {\n // Database span. A networked database resolves its DatabaseNode by peer\n // host; an in-process / embedded one (SQLite, better-sqlite3, an in-memory\n // store) crosses no network boundary and carries no peer address, so it\n // keys on a service-scoped local identity instead (ADR-118). Both mint the\n // same file-grained service→database CONNECTS_TO OBSERVED edge — the edge\n // that makes a leaf service's datastore reads legible (#576, #546).\n if (mintsFromCallerSide) {\n const host = pickAddress(span)\n // Engine comes off the OTel attribute as a string per Rule 8 — no\n // hardcoded engine list on either branch.\n let targetId: string\n if (host) {\n ensureDatabaseNode(ctx.graph, host, span.dbSystem)\n targetId = databaseId(host)\n } else {\n // No peer host. Prefer fusing onto the service's declared database of\n // the same engine (ADR-141) so an ORM's host-less span confirms the\n // static dependency instead of minting a divergent twin. Falls back to a\n // service-local node (ADR-118) for a genuinely embedded DB (SQLite) or an\n // ambiguous declaration. Name the local node by its logical database\n // (db.name) when the span carries one, the engine otherwise.\n const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem)\n if (declared) {\n targetId = declared\n } else {\n const localName = span.dbName ?? span.dbSystem\n targetId = ensureLocalDatabaseNode(\n ctx.graph,\n span.service,\n localName,\n span.dbSystem,\n )\n }\n }\n const result = upsertObservedEdge(\n ctx.graph,\n EdgeType.CONNECTS_TO,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n if (result) affectedNode = targetId\n\n // ADR-148 — a mongodb span also names the collection it operated on, one\n // grain finer than the database node above. Mint an additive OBSERVED\n // CALLS edge to `infra:mongodb-collection:<name>` — the same node id the\n // mongoose extractor emits (ensureInfraNode + infraId match calls/index.ts\n // exactly) — so the declared and observed collection edges fuse instead of\n // twinning. The collection is read straight off the span (dbCollection:\n // db.collection.name / db.mongodb.collection), so it is ground truth where\n // the extractor's Mongoose-pluralized derivation is quirk-wrong. Additive:\n // a mongodb span with no collection still mints only the db-grain edge\n // above.\n if (span.dbSystem === 'mongodb' && span.dbCollection) {\n const collectionId = ensureInfraNode(ctx.graph, 'mongodb-collection', span.dbCollection, 'self')\n upsertObservedEdge(\n ctx.graph,\n EdgeType.CALLS,\n observedSource(),\n collectionId,\n ts,\n isError,\n callSiteEvidence,\n )\n }\n // A SQL span's table (ADR-152), recovered from `db.statement` because the\n // SQLAlchemy / dbapi instrumentation carries no table attribute. It mints\n // onto the same `infra:sql-table:<name>` node the SQLAlchemy extractor\n // produces, so the declared and observed table access fuse rather than\n // twin. `db.statement`-derived, so it is ground truth where the extractor's\n // model→table derivation is unresolved; additive to the db-grain edge.\n if (span.dbTable) {\n const tableId = ensureInfraNode(ctx.graph, 'sql-table', span.dbTable, 'self')\n upsertObservedEdge(\n ctx.graph,\n EdgeType.CALLS,\n observedSource(),\n tableId,\n ts,\n isError,\n callSiteEvidence,\n )\n // ADR-157 — the same `db.statement` that named the table also names the\n // columns it touched. Merge them onto the table node as OBSERVED column\n // attributes (parsed in `parseOtlpRequest` via `columnsFromSqlStatement`).\n // A statement that recovers no columns (`SELECT *`, a join/subquery\n // degrade) grows nothing.\n mergeObservedColumns(ctx.graph, tableId, span.dbColumns)\n }\n }\n } else if (\n span.messagingSystem &&\n span.messagingDestination &&\n spanMintsMessagingEdge(span.kind)\n ) {\n // Messaging span. The semantic destination is the topic / queue / stream the\n // code talks to — not the broker host, which is transport. A PRODUCER span\n // publishes; a CONSUMER span consumes. Both mint an OBSERVED edge to the same\n // destination node the static extractor names (extract/calls/kafka.ts), so\n // the declared and observed queue edges fuse into one (→ divergence) instead\n // of twinning. File-grained through the same call-site path as any other\n // OBSERVED edge (file-awareness.md §4): when the span carries `code.*`, the\n // edge originates from the caller's FileNode at the exact call site,\n // reconciled onto the EXTRACTED service-relative path (`reconcileObservedRelPath`,\n // ADR-118); without a call site it stays service-level, honestly. This is the\n // consumer-side pair of the producer edge — the queue topology the OBSERVED\n // layer used to leave dark on the consumer side (issue #614).\n const targetId = ensureMessagingDestinationNode(\n ctx.graph,\n span.messagingSystem,\n span.messagingDestination,\n )\n const edgeType =\n span.kind === WIRE_SPAN_KIND_CONSUMER\n ? EdgeType.CONSUMES_FROM\n : EdgeType.PUBLISHES_TO\n const result = upsertObservedEdge(\n ctx.graph,\n edgeType,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n if (result) affectedNode = targetId\n } else if (\n span.graphqlOperationName &&\n span.graphqlOperationType &&\n spanServesGraphqlOperation(span.kind)\n ) {\n // GraphQL execution span (issue #615). Every GraphQL request rides one HTTP\n // endpoint (`POST /graphql`), so at HTTP grain the whole API collapses to a\n // single edge and the operation-level topology is invisible. The execution\n // span carries the operation the client actually named — `graphql.operation.name`\n // with `graphql.operation.type` — so mint an OBSERVED `CONTAINS` edge from the\n // serving service to a per-operation node, the same ownership shape a service\n // has over a route (ADR-119) and a file (file-awareness.md §2). OBSERVED-only:\n // the SDL / resolver map is not parsed statically in this cut; the node is\n // minted observed-first and a future static GraphQL extractor fuses onto the\n // same id (ADR-122). File-grained through the same call-site path as any other\n // OBSERVED edge (file-awareness.md §4): when the span carries `code.*` (the\n // resolver call site) the edge originates from that `FileNode` at the exact\n // `file:line`, reconciled onto the EXTRACTED service-relative path; without a\n // call site it stays service-level, honestly. Both operation name and type\n // must be present to key a stable id — a nameless/typeless execution span\n // falls through rather than minting a fabricated operation.\n const targetId = ensureGraphqlOperationNode(\n ctx.graph,\n span.service,\n span.graphqlOperationType,\n span.graphqlOperationName,\n )\n const result = upsertObservedEdge(\n ctx.graph,\n EdgeType.CONTAINS,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n if (result) affectedNode = targetId\n } else if (\n span.rpcSystem === 'grpc' &&\n span.rpcService &&\n span.rpcMethod &&\n spanServesGrpcMethod(span.kind)\n ) {\n // gRPC execution span (issue #616). gRPC used to engage only at service\n // grain: every method collapsed onto one service→service edge, so the\n // per-method topology was invisible and one-sided. The serving span carries\n // the method the caller actually invoked — `rpc.service` (the fully-qualified\n // `orders.OrderService`) with `rpc.method` (`GetOrder`) — so mint an OBSERVED\n // `CONTAINS` edge from the serving service to a per-method node, the same\n // ownership shape a service has over a route (ADR-119), a GraphQL operation\n // (ADR-122), and a file (file-awareness.md §2). The node is keyed on the\n // fully-qualified `rpc.service` — the wire contract both the span and the\n // `.proto` carry verbatim — so the static `.proto` extractor's declared\n // method fuses onto this same id into a two-sided divergence (ADR-123).\n // File-grained through the same call-site path as any other OBSERVED edge\n // (file-awareness.md §4): when the span carries `code.*` (the handler call\n // site) the edge originates from that `FileNode` at the exact `file:line`,\n // reconciled onto the EXTRACTED service-relative path; without a call site it\n // stays service-level, honestly. The gate admits only the serving side —\n // SERVER / INTERNAL / unkinded — so a CLIENT span mints no ownership\n // (client→method attribution is deferred) and instead falls through to the\n // cross-service resolver, leaving the caller→callee edge intact. Both service\n // and method must be present to key a stable id.\n const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod)\n const result = upsertObservedEdge(\n ctx.graph,\n EdgeType.CONTAINS,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n if (result) affectedNode = targetId\n } else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {\n // WebSocket upgrade span (issue #617). A WebSocket app used to produce no\n // OBSERVED topology at all: only message-handler errors surfaced, as\n // incidents, and the channels themselves stayed invisible — the frames after\n // the handshake ride the socket, not more spans. The one span that reliably\n // marks a channel is the HTTP upgrade that opens it: a SERVER `GET` carrying\n // `Upgrade: websocket` and the connection path. So mint an OBSERVED\n // `CONNECTS_TO` edge from the serving service to a per-channel node —\n // reusing the same connection edge a service has to a datastore (#576), not a\n // new edge type. Unlike the structural `CONTAINS` a service has over a route /\n // operation / method — durably declared artifacts whose edge never goes\n // stale — a channel's whole meaning is liveness, so `CONNECTS_TO` is the right\n // shape: it carries `lastObserved` and decays OBSERVED → STALE on\n // CONNECTS_TO's own threshold when the channel goes quiet (the daemon\n // staleness loop, #532). OBSERVED-only: a WebSocket channel is known from\n // observation, never from static extraction, so the node has no declared twin\n // and is excluded from `missing-extracted` (divergences.ts). File-grained\n // through the same call-site path as any other OBSERVED edge\n // (file-awareness.md §4): when the span carries `code.*` the edge originates\n // from that `FileNode` at the exact `file:line`, reconciled onto the EXTRACTED\n // service-relative path; without a call site it stays service-level, honestly.\n // The gate admits only the serving side — SERVER / INTERNAL / unkinded — so a\n // CLIENT upgrade span mints no channel (client→channel attribution is\n // deferred, ADR-125).\n const targetId = ensureWebsocketChannelNode(\n ctx.graph,\n span.service,\n span.websocketChannel,\n )\n const result = upsertObservedEdge(\n ctx.graph,\n EdgeType.CONNECTS_TO,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n if (result) affectedNode = targetId\n } else {\n // Possibly a cross-service call. Resolve the peer; if it matches a known\n // ServiceNode, record an OBSERVED CALLS edge to the typed target. If it\n // matches nothing — pod IP, ingress hostname, AWS PrivateLink endpoint —\n // create a FrontierNode placeholder and record an OBSERVED edge to that\n // FrontierNode so the call carries the same provenance + signal-block +\n // graded confidence as any other OBSERVED edge (ADR-068). The target ref\n // identifies the node-type; provenance describes how the edge was learned.\n // promoteFrontierNodes (run by the extract orchestrator) rewrites the\n // target ref once a later round resolves the host; the edge's provenance\n // stays OBSERVED across promotion.\n // A loopback host (localhost / 127.0.0.0/8 / ::1) is skipped here: it never\n // resolves to a distinct peer, and minting frontier:localhost would double\n // the edge that the callee's parent-span fallback already records for this\n // same call (issues #590, #577). Leaving resolvedViaAddress false hands the\n // call to that fallback instead.\n const host = pickAddress(span)\n let resolvedViaAddress = false\n if (mintsFromCallerSide && host && host !== span.service && !isLoopbackHost(host)) {\n const targetId = resolveServiceId(ctx.graph, host, env)\n if (targetId && targetId !== sourceId) {\n upsertObservedEdge(\n ctx.graph,\n EdgeType.CALLS,\n observedSource(),\n targetId,\n ts,\n isError,\n callSiteEvidence,\n )\n affectedNode = targetId\n resolvedViaAddress = true\n } else if (!targetId) {\n const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts)\n upsertObservedEdge(\n ctx.graph,\n EdgeType.CALLS,\n observedSource(),\n frontierNodeId,\n ts,\n isError,\n callSiteEvidence,\n )\n affectedNode = frontierNodeId\n resolvedViaAddress = true\n }\n }\n\n // Parent-span fallback (ADR-033): when address-based resolution didn't\n // produce an edge and the span has a parentSpanId we've cached, the\n // parent's service identifies the caller. The current span is the server\n // side of the call, so the edge direction is parent.service → current.\n // The cached entry carries the parent span's env, so the auto-created\n // parent ServiceNode lands on the env-tagged id the parent advertised.\n if (!resolvedViaAddress && span.parentSpanId) {\n const parent = lookupParentSpan(span.traceId, span.parentSpanId, nowMs)\n if (parent && parent.service !== span.service) {\n const parentId = ensureServiceNode(ctx.graph, parent.service, parent.env)\n // When the parent span carried a `code.*` call site, originate the edge\n // from the parent's FileNode so it anchors to file:line instead of the\n // bare parent ServiceNode (issue #536). Without a cached call site the\n // edge stays service-coarse — never fabricated (file-awareness.md §6).\n const fallbackSource = parent.callSite\n ? ensureObservedFileNode(ctx.graph, parent.service, parentId, parent.callSite)\n : parentId\n const fallbackEvidence: { file: string; line?: number } | undefined =\n parent.callSite\n ? {\n file: reconcileObservedRelPath(\n ctx.graph,\n parent.service,\n parent.callSite.relPath,\n ),\n ...(parent.callSite.line !== undefined\n ? { line: parent.callSite.line }\n : {}),\n }\n : undefined\n upsertObservedEdge(\n ctx.graph,\n EdgeType.CALLS,\n fallbackSource,\n sourceId,\n ts,\n isError,\n fallbackEvidence,\n )\n }\n }\n }\n\n // A SERVER span's `http.route` fuses onto the declared RouteNode, giving an\n // inbound route its OBSERVED twin the way GraphQL/gRPC serving spans do (#576).\n // Matched by normalized (method, template) so a route NEAT extracted gets its\n // observed counterpart; an unmatched route mints nothing here, honestly.\n if (\n span.httpRoute &&\n (span.kind === 2 || span.kind === 1 || span.kind === 0 || span.kind === undefined)\n ) {\n const routeNodeId = findRouteNodeByHttpRoute(\n ctx.graph,\n span.service,\n span.httpMethod,\n span.httpRoute,\n )\n if (routeNodeId) {\n const routeSvc = (ctx.graph.getNodeAttributes(routeNodeId) as RouteNode).service\n upsertObservedEdge(ctx.graph, EdgeType.CONTAINS, serviceId(routeSvc), routeNodeId, ts, isError)\n }\n }\n\n if (span.statusCode === 2) {\n stitchTrace(ctx.graph, sourceId, ts)\n // The durable ErrorEvent write moved to the receiver so the file write\n // happens synchronously before the 200 reply (ADR-033 §Error events,\n // amended). watch.ts wires makeErrorSpanWriter into onErrorSpanSync.\n // handleSpan still runs the in-graph error effects (stitchTrace above);\n // it just doesn't append to errors.ndjson anymore. ctx.errorsPath stays\n // for the optional opt-in path below — daemon-less callers (CLI tests,\n // ad-hoc scripts) that skip the receiver hook still get a write here.\n if (ctx.writeErrorEventInline !== false) {\n const attrs = sanitizeAttributes(span.attributes)\n const ev: ErrorEvent = {\n id: `${span.traceId}:${span.spanId}`,\n timestamp: ts,\n service: span.service,\n traceId: span.traceId,\n spanId: span.spanId,\n errorMessage: incidentMessage(span),\n ...(span.exception?.type ? { exceptionType: span.exception.type } : {}),\n ...(span.exception?.stacktrace\n ? { exceptionStacktrace: span.exception.stacktrace }\n : {}),\n ...(Object.keys(attrs).length > 0 ? { attributes: attrs } : {}),\n affectedNode,\n }\n await appendErrorEvent(ctx, ev)\n }\n }\n\n // Failing-response incidents (issue #481). OTel semconv leaves a CLIENT span's\n // status UNSET on a 4xx/5xx response, so the status-only path above is blind\n // to a service whose outbound calls are failing en masse — the exact gap a\n // debugging session hits (80× HTTP 404 against one peer surfacing nothing).\n // A response status is read from the span here regardless of statusCode:\n // * 5xx → record an incident immediately (unambiguous failure, even with\n // UNSET status). Skipped when statusCode === 2 already recorded above so\n // a 5xx that also carries ERROR status isn't double-counted.\n // * 4xx on a CLIENT/PRODUCER span → coalesce. The burst against this\n // (source, peer) pair advances; when it reaches the threshold inside the\n // window it records ONE incident carrying the count and dominant code.\n // * a lone 4xx, or any 2xx/3xx → no incident.\n // Always written here (not gated on writeErrorEventInline): the daemon's\n // receiver only fires its synchronous error-writer for statusCode === 2, so\n // these spans never reach that durability handoff — handleSpan owns them.\n if (span.statusCode !== 2) {\n const status = httpResponseStatus(span)\n // ADR-117 — an exception event on a span that left its status UNSET is\n // still an unambiguous failure: a bullmq / Redis-Streams / background\n // worker whose job threw carries the exception and no HTTP response\n // context, so the status-only and response-code paths both miss it. Record\n // it independent of HTTP, attributed to the handler file:line (or the\n // service) the same way the statusCode === 2 path is. Ordered first because\n // the exception is the unambiguous signal; the response-code branches below\n // carry the exception-less HTTP failures.\n if (span.exception) {\n await recordExceptionIncident(ctx, span, ts)\n } else if (status !== undefined && status >= 500) {\n // A failing-response incident is attributed to the SOURCE service — the\n // caller whose outbound calls are failing is the node a debugger asks\n // about (\"why is my service erroring\"). The peer it failed against is\n // carried in the message and in attributes. This is deliberately not the\n // edge target (frontier/peer) the OBSERVED edge above resolved to: the\n // signal is \"this service's calls to X are failing\", not \"X failed\".\n await recordFailingResponseIncident(ctx, span, sourceId, ts, status, 1)\n } else if (\n status !== undefined &&\n status >= 400 &&\n spanMintsObservedEdge(span.kind)\n ) {\n await advance4xxBurst(ctx, span, sourceId, ts, nowMs, status)\n }\n }\n void affectedNode\n\n // Post-ingest policy trigger (ADR-043). The hook is awaited so failures\n // surface; daemons wrap it in a try/catch that logs without throwing.\n if (ctx.onPolicyTrigger) await ctx.onPolicyTrigger(ctx.graph)\n}\n\nexport { stitchTrace }\n\n// Promote any frontier:<host> placeholder whose host matches an alias on a\n// real ServiceNode: re-link inbound/outbound edges to the service, then drop\n// the placeholder. Returns the count of nodes promoted, for tests + logs.\n//\n// Called at the end of every extraction round. Static rounds are when new\n// aliases land (compose names, k8s metadata.name, Dockerfile labels), so\n// running it there picks up the case the issue describes: ingest fills in a\n// frontier when traffic arrives for an unknown host, and the next extraction\n// round resolves it.\n// Optional gate for block-action policies (ADR-044). When `policies` is\n// non-empty, each candidate FrontierNode runs through `canPromoteFrontier`\n// before its incident edges are rewired. Block-action policies that fire on\n// the frontier veto the promotion — the FrontierNode persists; the next\n// extract pass tries again.\nexport interface PromoteFrontierOptions {\n policies?: Policy[]\n policyCtx?: PolicyEvaluationContext\n}\n\nexport function promoteFrontierNodes(\n graph: NeatGraph,\n opts: PromoteFrontierOptions = {},\n): number {\n const aliasIndex = new Map<string, string>()\n graph.forEachNode((id, attrs) => {\n const a = attrs as ServiceNode & { type?: string }\n if (a.type !== NodeType.ServiceNode) return\n aliasIndex.set(a.name, id)\n if (a.aliases) {\n for (const alias of a.aliases) aliasIndex.set(alias, id)\n }\n })\n\n const toPromote: { frontierId: string; serviceId: string }[] = []\n graph.forEachNode((id, attrs) => {\n const a = attrs as FrontierNode & { type?: string }\n if (a.type !== NodeType.FrontierNode) return\n const target = aliasIndex.get(a.host)\n if (!target) return\n if (target === id) return\n toPromote.push({ frontierId: id, serviceId: target })\n })\n\n let promoted = 0\n for (const { frontierId, serviceId } of toPromote) {\n if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {\n const gate = canPromoteFrontier(graph, frontierId, opts.policies, opts.policyCtx)\n if (!gate.allowed) {\n // Block-action policy fired on this frontier — skip the rewire and\n // leave the FrontierNode in place. Violations already surfaced via\n // the policy log on the same evaluation pass.\n continue\n }\n }\n rewireFrontierEdges(graph, frontierId, serviceId)\n graph.dropNode(frontierId)\n promoted++\n }\n return promoted\n}\n\nfunction rewireFrontierEdges(graph: NeatGraph, frontierId: string, serviceId: string): void {\n const inbound = [...graph.inboundEdges(frontierId)]\n const outbound = [...graph.outboundEdges(frontierId)]\n\n for (const edgeId of inbound) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n rebuildEdge(graph, edge, edge.source, serviceId, edgeId)\n }\n for (const edgeId of outbound) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n rebuildEdge(graph, edge, serviceId, edge.target, edgeId)\n }\n}\n\nfunction rebuildEdge(\n graph: NeatGraph,\n edge: GraphEdge,\n newSource: string,\n newTarget: string,\n oldEdgeId: string,\n): void {\n graph.dropEdge(oldEdgeId)\n // ADR-068 — promotion rewrites the target ref; provenance carries forward.\n // An OBSERVED edge to a FrontierNode promotes to an OBSERVED edge to the\n // matched typed node; an INFERRED edge stays INFERRED; etc.\n const newId =\n edge.provenance === Provenance.OBSERVED\n ? observedEdgeId(newSource, newTarget, edge.type)\n : edge.provenance === Provenance.INFERRED\n ? inferredEdgeId(newSource, newTarget, edge.type)\n : extractedEdgeId(newSource, newTarget, edge.type)\n\n if (graph.hasEdge(newId)) {\n const existing = graph.getEdgeAttributes(newId) as GraphEdge\n const merged: GraphEdge = {\n ...existing,\n callCount: (existing.callCount ?? 0) + (edge.callCount ?? 0),\n lastObserved: pickLater(existing.lastObserved, edge.lastObserved),\n }\n graph.replaceEdgeAttributes(newId, merged)\n return\n }\n\n const rebuilt: GraphEdge = {\n ...edge,\n id: newId,\n source: newSource,\n target: newTarget,\n }\n graph.addEdgeWithKey(newId, newSource, newTarget, rebuilt)\n}\n\nfunction pickLater(a: string | undefined, b: string | undefined): string | undefined {\n if (!a) return b\n if (!b) return a\n return new Date(a).getTime() >= new Date(b).getTime() ? a : b\n}\n\nexport function makeSpanHandler(ctx: IngestContext): (span: ParsedSpan) => Promise<void> {\n return (span) => handleSpan(ctx, span)\n}\n\nexport type { StaleEvent }\n\nexport interface MarkStaleOptions {\n // Per-edge-type override map. Defaults to DEFAULT_STALE_THRESHOLDS, merged\n // with NEAT_STALE_THRESHOLDS if the env var is set.\n thresholds?: Record<string, number>\n now?: number\n // ndjson path. When set, every OBSERVED → STALE transition appends one\n // line. Skipped if undefined — tests and embedded use cases don't need a\n // log.\n staleEventsPath?: string\n // Project tag for event-bus routing (ADR-051). Defaults to DEFAULT_PROJECT.\n project?: string\n}\n\n// Demote OBSERVED edges that haven't been seen in a while. Per-edge-type\n// thresholds: HTTP CALLS go stale fast; infra DEPENDS_ON is patient. Returns\n// the count of demotions and the events appended to the log.\nexport async function markStaleEdges(\n graph: NeatGraph,\n options: MarkStaleOptions = {},\n): Promise<{ count: number; events: StaleEvent[] }> {\n const thresholds = options.thresholds ?? loadStaleThresholdsFromEnv()\n const now = options.now ?? Date.now()\n const events: StaleEvent[] = []\n\n const project = options.project ?? DEFAULT_PROJECT\n graph.forEachEdge((id, attrs) => {\n const e = attrs as GraphEdge\n if (e.provenance !== Provenance.OBSERVED) return\n if (!e.lastObserved) return\n const threshold = thresholdForEdgeType(e.type, thresholds)\n const age = now - new Date(e.lastObserved).getTime()\n if (age > threshold) {\n const updated: GraphEdge = { ...e, provenance: Provenance.STALE, confidence: 0.3 }\n graph.replaceEdgeAttributes(id, updated)\n events.push({\n edgeId: id,\n source: e.source,\n target: e.target,\n edgeType: e.type,\n thresholdMs: threshold,\n ageMs: age,\n lastObserved: e.lastObserved,\n transitionedAt: new Date(now).toISOString(),\n })\n // Stale-transition fires through the bus (ADR-051). The graph\n // subscription in events.ts can't see the OBSERVED→STALE semantic on\n // its own — a provenance flip is just an attribute update from\n // graphology's view.\n emitNeatEvent({\n type: 'stale-transition',\n project,\n payload: {\n edgeId: id,\n from: Provenance.OBSERVED,\n to: Provenance.STALE,\n },\n })\n }\n })\n\n if (options.staleEventsPath && events.length > 0) {\n await appendStaleEvents(options.staleEventsPath, events)\n }\n\n return { count: events.length, events }\n}\n\nasync function appendStaleEvents(staleEventsPath: string, events: StaleEvent[]): Promise<void> {\n await fs.mkdir(path.dirname(staleEventsPath), { recursive: true })\n const lines = events.map((e) => JSON.stringify(e)).join('\\n') + '\\n'\n await fs.appendFile(staleEventsPath, lines, 'utf8')\n}\n\nexport async function readStaleEvents(staleEventsPath: string): Promise<StaleEvent[]> {\n try {\n const raw = await fs.readFile(staleEventsPath, 'utf8')\n return raw\n .split('\\n')\n .filter((line) => line.length > 0)\n .map((line) => JSON.parse(line) as StaleEvent)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n}\n\nexport interface StalenessLoopOptions {\n thresholds?: Record<string, number>\n intervalMs?: number\n staleEventsPath?: string\n // Project tag for event-bus routing (ADR-051).\n project?: string\n // Post-stale-transition policy trigger (ADR-043). Fires after each tick of\n // markStaleEdges so policies see the new STALE state. Daemons wire this to\n // evaluateAllPolicies + PolicyViolationsLog.append.\n onPolicyTrigger?: (graph: NeatGraph) => Promise<void> | void\n}\n\nexport function startStalenessLoop(\n graph: NeatGraph,\n options: StalenessLoopOptions = {},\n): () => void {\n let stopped = false\n const intervalMs = options.intervalMs ?? 60_000\n const tick = (): void => {\n if (stopped) return\n void (async () => {\n try {\n await markStaleEdges(graph, {\n thresholds: options.thresholds,\n staleEventsPath: options.staleEventsPath,\n project: options.project,\n })\n if (options.onPolicyTrigger) await options.onPolicyTrigger(graph)\n } catch (err) {\n console.error('staleness tick failed', err)\n }\n })()\n }\n const interval = setInterval(tick, intervalMs)\n if (typeof interval.unref === 'function') interval.unref()\n return () => {\n stopped = true\n clearInterval(interval)\n }\n}\n\nexport async function readErrorEvents(errorsPath: string): Promise<ErrorEvent[]> {\n try {\n const raw = await fs.readFile(errorsPath, 'utf8')\n const events = raw\n .split('\\n')\n .filter((line) => line.length > 0)\n .map((line) => JSON.parse(line) as ErrorEvent)\n return dedupeIncidents(events)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n}\n\n// A synthesized HTTP-status incident carries no failure of its own — it's the\n// \"500 on GET /users/:id\" line handleSpan mints for a server span that answered\n// 5xx with no exception event of its own (httpFailureMessage). When the real\n// failure surfaced deeper in the same trace (a DB driver threw, a downstream\n// gRPC returned UNAVAILABLE), that exception is recorded as its own incident on\n// the same node, and the server's HTTP echo is a duplicate of it. A record is\n// \"synthesized HTTP\" when it carries no exception data, no explicit errorType\n// (the coalesced http-failure incidents set one and carry their own count), and\n// its message is exactly the HTTP line re-derived from its own attributes.\nfunction isSynthesizedHttpIncident(ev: ErrorEvent): boolean {\n if (ev.exceptionType || ev.exceptionStacktrace) return false\n if (ev.errorType) return false\n if (!ev.attributes) return false\n const synth = httpFailureMessageFromAttrs(ev.attributes)\n return synth !== undefined && synth === ev.errorMessage\n}\n\n// Make the incident surface idempotent per failure. Two passes:\n//\n// Pass 1 — collapse exact `(traceId, spanId)` re-deliveries. The ndjson sidecar\n// is append-only (persistence contract), so a re-delivered span — OTel\n// BatchSpanProcessor retries, or a receiver + handler both writing one POST —\n// leaves duplicate lines on disk. The deterministic incident `id` already\n// encodes the pair (`${traceId}:${spanId}`); we dedupe on it directly, falling\n// back to the raw pair for any record that predates the id. Records that carry\n// neither (extract parse-failure rows, `source: 'extract'`) pass through\n// untouched — they aren't span incidents. First write wins so the original\n// timestamp is preserved.\n//\n// Pass 2 — collapse one failure recorded from two spans of the same trace. A\n// failing request lands one incident from the span that actually threw (the DB\n// child's exception, a downstream gRPC error) and a second, synthesized one from\n// the HTTP server span that echoed it as a 5xx. Both key to the same\n// `(traceId, affectedNode)`; the exact-id pass can't see it because the spanIds\n// differ. When a real failure shares a trace and node with a synthesized HTTP\n// echo, drop the echo so the request counts once (issue #624). A cross-service\n// failure keeps both sides: the caller's failing-response incident and the\n// callee's exception land on different `affectedNode`s (separate ledgers per the\n// otel-ingest contract), so they never share a group.\nfunction dedupeIncidents(events: ErrorEvent[]): ErrorEvent[] {\n const seen = new Set<string>()\n const once: ErrorEvent[] = []\n for (const ev of events) {\n const key =\n ev.id ??\n (ev.traceId && ev.spanId ? `${ev.traceId}:${ev.spanId}` : undefined)\n if (key === undefined) {\n once.push(ev)\n continue\n }\n if (seen.has(key)) continue\n seen.add(key)\n once.push(ev)\n }\n\n const groupKey = (ev: ErrorEvent): string => `${ev.traceId}\\u0000${ev.affectedNode}`\n const hasRealFailure = new Set<string>()\n for (const ev of once) {\n if (ev.traceId && !isSynthesizedHttpIncident(ev)) hasRealFailure.add(groupKey(ev))\n }\n return once.filter((ev) => {\n if (!ev.traceId || !isSynthesizedHttpIncident(ev)) return true\n return !hasRealFailure.has(groupKey(ev))\n })\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Snapshot merge (ADR-074 §1)\n//\n// `neat sync` (local) and `neat sync --to <url>` (remote) feed snapshots into\n// a live graph through this helper. It lives in ingest.ts because mutation\n// authority sits with ingest + extract per the lifecycle contract (ADR-030);\n// the merge is ingestion of an external snapshot, no different in shape from\n// the way handleSpan ingests an OTel span.\n//\n// The merge preserves EXTRACTED + OBSERVED coexistence per Rule 2 — each\n// provenance variant has its own edge id, so the incoming EXTRACTED edges\n// can't stomp the daemon's accumulated OBSERVED edges and vice versa. Rule of\n// thumb: incoming wins for nodes/edges the live graph hasn't seen yet;\n// everything already present keeps its current attributes.\n// ──────────────────────────────────────────────────────────────────────────\n\nexport interface MergeSnapshotResult {\n nodesAdded: number\n edgesAdded: number\n}\n\n// A pushed snapshot is untrusted input — it can arrive from `neat sync --to`\n// against a daemon the operator doesn't fully control, or from anything else\n// that can reach the `/snapshot` route. Before #693, every entry's\n// `attributes` went straight from `JSON.parse` to `graph.addNode` /\n// `graph.addEdgeWithKey` with only a schemaVersion check upstream in api.ts —\n// a malformed or hostile payload (wrong `type` literal, missing required\n// field, wrong field type) landed on the live graph as-is. This error\n// collects every entry that fails `GraphNodeSchema` / `GraphEdgeSchema` — the\n// same canonical shapes the rest of the API validates responses against\n// (packages/types) — so the caller gets back exactly what was wrong instead\n// of a generic parse failure or, worse, a silently corrupted graph.\nexport class SnapshotValidationError extends Error {\n constructor(public readonly issues: string[]) {\n super(`snapshot failed validation (${issues.length} invalid ${issues.length === 1 ? 'entry' : 'entries'})`)\n this.name = 'SnapshotValidationError'\n }\n}\n\nfunction describeZodIssues(error: { issues: Array<{ path: PropertyKey[]; message: string }> }): string {\n return error.issues\n .map((issue) => (issue.path.length > 0 ? `${issue.path.join('.')}: ${issue.message}` : issue.message))\n .join('; ')\n}\n\nexport function mergeSnapshot(\n graph: NeatGraph,\n snapshot: PersistedGraph,\n): MergeSnapshotResult {\n const exported = snapshot.graph as {\n nodes?: Array<{ key: string; attributes?: unknown }>\n edges?: Array<{ key?: string; source: string; target: string; attributes?: unknown }>\n }\n const incomingNodes = Array.isArray(exported.nodes) ? exported.nodes : []\n const incomingEdges = Array.isArray(exported.edges) ? exported.edges : []\n\n // Validate everything up front and merge nothing until every entry checks\n // out — a snapshot that's partly hostile or partly corrupt is rejected\n // whole rather than landing its valid entries and silently dropping the\n // rest (matching how the schemaVersion check upstream already treats a\n // bad snapshot as all-or-nothing).\n const issues: string[] = []\n const validNodes: Array<{ key: string; attributes: GraphNode }> = []\n const validEdges: Array<{ key: string; source: string; target: string; attributes: GraphEdge }> = []\n\n for (const node of incomingNodes) {\n // No attributes at all is a no-op skip, not a malformed entry — mirrors\n // the pre-#693 behaviour for this specific (empty) shape.\n if (node.attributes === undefined) continue\n const parsed = GraphNodeSchema.safeParse(node.attributes)\n if (!parsed.success) {\n issues.push(`node \"${node.key}\": ${describeZodIssues(parsed.error)}`)\n continue\n }\n validNodes.push({ key: node.key, attributes: parsed.data })\n }\n\n for (const edge of incomingEdges) {\n if (edge.attributes === undefined) continue\n const parsed = GraphEdgeSchema.safeParse(edge.attributes)\n if (!parsed.success) {\n const label = edge.key ?? `${edge.source}->${edge.target}`\n issues.push(`edge \"${label}\": ${describeZodIssues(parsed.error)}`)\n continue\n }\n const id = edge.key ?? parsed.data.id\n validEdges.push({ key: id, source: edge.source, target: edge.target, attributes: parsed.data })\n }\n\n if (issues.length > 0) {\n throw new SnapshotValidationError(issues)\n }\n\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const node of validNodes) {\n if (graph.hasNode(node.key)) continue\n graph.addNode(node.key, node.attributes)\n nodesAdded++\n }\n\n for (const edge of validEdges) {\n if (graph.hasEdge(edge.key)) continue\n // Skip when either endpoint is missing — can happen if the snapshot\n // names a node the live graph already evicted and the incoming nodes\n // array didn't include.\n if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue\n graph.addEdgeWithKey(edge.key, edge.source, edge.target, edge.attributes)\n edgesAdded++\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type {\n ApplicablePolicy,\n GraphEdge,\n GraphNode,\n Policy,\n PolicyAction,\n PolicyFile,\n PolicyRule,\n PolicySeverity,\n PolicyViolation,\n ServiceNode,\n} from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n PolicyFileSchema,\n} from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\nimport { DEFAULT_PROJECT } from './graph.js'\nimport {\n checkCompatibility,\n checkDeprecatedApi,\n checkNodeEngineConstraint,\n checkPackageConflict,\n compatPairs,\n deprecatedApis,\n nodeEngineConstraints,\n packageConflicts,\n} from './compat.js'\nimport { emitNeatEvent } from './events.js'\nimport { getBlastRadius } from './traverse.js'\n\n// Policy evaluation engine (ADR-043). The entry point evaluateAllPolicies is\n// pure: same graph + same policies → same violations. Per-rule-type dispatch\n// via the policyEvaluators table. Adding a new rule type means one new\n// evaluator entry plus the schema entry in @neat.is/types/policy.ts.\n//\n// Deterministic violation ids per ADR-043: ${policy.id}:${context}. The\n// context is shape-specific (nodeId, edgeId, or composite). The\n// policy-violations.ndjson writer skips on duplicate ids.\n\nexport interface EvaluationContext {\n // Wall-clock provider. Tests pin this; production uses Date.now.\n now: () => number\n}\n\ninterface RuleEvaluatorArgs<T extends PolicyRule = PolicyRule> {\n graph: NeatGraph\n policy: Policy\n rule: T\n ctx: EvaluationContext\n}\n\ntype RuleEvaluator<T extends PolicyRule = PolicyRule> = (\n args: RuleEvaluatorArgs<T>,\n) => PolicyViolation[]\n\n// Severity-driven default action per ADR-044.\nconst DEFAULT_ACTION_BY_SEVERITY: Record<PolicySeverity, PolicyAction> = {\n info: 'log',\n warning: 'alert',\n error: 'alert',\n critical: 'block',\n}\n\nexport function resolveOnViolation(policy: Policy): PolicyAction {\n return policy.onViolation ?? DEFAULT_ACTION_BY_SEVERITY[policy.severity]\n}\n\nfunction makeViolation(\n policy: Policy,\n rule: PolicyRule,\n contextSuffix: string,\n message: string,\n subject: PolicyViolation['subject'],\n ctx: EvaluationContext,\n): PolicyViolation {\n return {\n id: `${policy.id}:${contextSuffix}`,\n policyId: policy.id,\n policyName: policy.name,\n severity: policy.severity,\n onViolation: resolveOnViolation(policy),\n ruleType: rule.type,\n subject,\n message,\n observedAt: new Date(ctx.now()).toISOString(),\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Per-rule-type evaluators\n// ──────────────────────────────────────────────────────────────────────────\n\nconst evaluateStructural: RuleEvaluator<Extract<PolicyRule, { type: 'structural' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const violations: PolicyViolation[] = []\n graph.forEachNode((id, attrs) => {\n const a = attrs as GraphNode\n if (a.type !== rule.fromNodeType) return\n let satisfied = false\n for (const edgeId of graph.outboundEdges(id)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== rule.edgeType) continue\n const target = graph.getNodeAttributes(e.target) as GraphNode\n // FrontierNodes are unresolved peers (ADR-068) — skip; the rule\n // counts edges that resolve to a real typed node only.\n if (target.type === NodeType.FrontierNode) continue\n if (target.type === rule.toNodeType) {\n satisfied = true\n break\n }\n }\n if (!satisfied) {\n violations.push(\n makeViolation(\n policy,\n rule,\n id,\n `${rule.fromNodeType} ${id} has no ${rule.edgeType} edge to a ${rule.toNodeType}`,\n { nodeId: id },\n ctx,\n ),\n )\n }\n })\n return violations\n}\n\nconst evaluateOwnership: RuleEvaluator<Extract<PolicyRule, { type: 'ownership' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const violations: PolicyViolation[] = []\n graph.forEachNode((id, attrs) => {\n const a = attrs as GraphNode & Record<string, unknown>\n if (a.type !== rule.nodeType) return\n const value = a[rule.field]\n if (typeof value !== 'string' || value.length === 0) {\n violations.push(\n makeViolation(\n policy,\n rule,\n id,\n `${rule.nodeType} ${id} is missing required field \"${rule.field}\"`,\n { nodeId: id },\n ctx,\n ),\n )\n }\n })\n return violations\n}\n\nconst evaluateProvenance: RuleEvaluator<Extract<PolicyRule, { type: 'provenance' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const required = Array.isArray(rule.required) ? new Set(rule.required) : new Set([rule.required])\n const violations: PolicyViolation[] = []\n graph.forEachEdge((edgeId, attrs) => {\n const e = attrs as GraphEdge\n if (e.type !== rule.edgeType) return\n if (rule.targetNodeId && e.target !== rule.targetNodeId) return\n if (!required.has(e.provenance)) {\n const requiredList = [...required].join(' | ')\n violations.push(\n makeViolation(\n policy,\n rule,\n edgeId,\n `${rule.edgeType} edge ${edgeId} has provenance ${e.provenance}; required ${requiredList}`,\n { edgeId },\n ctx,\n ),\n )\n }\n })\n return violations\n}\n\nconst evaluateBlastRadius: RuleEvaluator<Extract<PolicyRule, { type: 'blast-radius' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const violations: PolicyViolation[] = []\n const depth = rule.depth\n graph.forEachNode((id, attrs) => {\n const a = attrs as GraphNode\n if (a.type !== rule.nodeType) return\n const result = depth !== undefined ? getBlastRadius(graph, id, depth) : getBlastRadius(graph, id)\n if (result.totalAffected > rule.maxAffected) {\n violations.push(\n makeViolation(\n policy,\n rule,\n id,\n `${rule.nodeType} ${id} has blast radius ${result.totalAffected} > ${rule.maxAffected}`,\n { nodeId: id, path: [id] },\n ctx,\n ),\n )\n }\n })\n return violations\n}\n\nconst evaluateCompatibility: RuleEvaluator<Extract<PolicyRule, { type: 'compatibility' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const violations: PolicyViolation[] = []\n // Iterate every ServiceNode and re-run the compat shapes the static\n // extractor runs at extract time. Catches OBSERVED-vs-EXTRACTED divergence:\n // a service whose dep manifest changed since the last extract gets re-flagged\n // here on every evaluation cycle.\n const wantsKind = (kind: NonNullable<typeof rule.kind>): boolean =>\n rule.kind === undefined || rule.kind === kind\n\n graph.forEachNode((svcId, attrs) => {\n const a = attrs as GraphNode\n if (a.type !== NodeType.ServiceNode) return\n const svc = a as ServiceNode\n const deps = svc.dependencies ?? {}\n\n if (wantsKind('driver-engine')) {\n // Walk every CONNECTS_TO edge from this service to a DatabaseNode,\n // then run the driver-engine compat for each (driver, declared, engine,\n // engineVersion) tuple.\n for (const edgeId of graph.outboundEdges(svcId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== EdgeType.CONNECTS_TO) continue\n const dbAttrs = graph.getNodeAttributes(e.target) as GraphNode\n // FrontierNodes are unresolved peers (ADR-068) — skip; compat\n // checking needs a typed DatabaseNode.\n if (dbAttrs.type === NodeType.FrontierNode) continue\n if (dbAttrs.type !== NodeType.DatabaseNode) continue\n const db = dbAttrs as { engine: string; engineVersion: string }\n for (const pair of compatPairs()) {\n if (pair.engine !== db.engine) continue\n const declared = deps[pair.driver]\n if (!declared) continue\n const result = checkCompatibility(pair.driver, declared, db.engine, db.engineVersion)\n if (!result.compatible && result.reason) {\n violations.push(\n makeViolation(\n policy,\n rule,\n `${svcId}:driver-engine:${pair.driver}@${declared}:${db.engine}@${db.engineVersion}`,\n result.reason,\n { nodeId: svcId, edgeId },\n ctx,\n ),\n )\n }\n }\n }\n }\n\n if (wantsKind('node-engine')) {\n const serviceNodeRange = svc.nodeEngine\n for (const constraint of nodeEngineConstraints()) {\n const declared = deps[constraint.package]\n if (!declared) continue\n const result = checkNodeEngineConstraint(constraint, declared, serviceNodeRange)\n if (!result.compatible && result.reason) {\n violations.push(\n makeViolation(\n policy,\n rule,\n `${svcId}:node-engine:${constraint.package}@${declared}`,\n result.reason,\n { nodeId: svcId },\n ctx,\n ),\n )\n }\n }\n }\n\n if (wantsKind('package-conflict')) {\n for (const conflict of packageConflicts()) {\n const declared = deps[conflict.package]\n if (!declared) continue\n const requiredDeclared = deps[conflict.requires.name]\n const result = checkPackageConflict(conflict, declared, requiredDeclared)\n if (!result.compatible && result.reason) {\n violations.push(\n makeViolation(\n policy,\n rule,\n `${svcId}:package-conflict:${conflict.package}@${declared}`,\n result.reason,\n { nodeId: svcId },\n ctx,\n ),\n )\n }\n }\n }\n\n if (wantsKind('deprecated-api')) {\n for (const dep of deprecatedApis()) {\n const declared = deps[dep.package]\n if (!declared) continue\n const result = checkDeprecatedApi(dep, declared)\n if (!result.compatible && result.reason) {\n violations.push(\n makeViolation(\n policy,\n rule,\n `${svcId}:deprecated-api:${dep.package}@${declared}`,\n result.reason,\n { nodeId: svcId },\n ctx,\n ),\n )\n }\n }\n }\n })\n\n return violations\n}\n\n// ADR-169 — the generic declared-set-subset rule. For every node of the rule's\n// type (optionally narrowed to an InfraNode `kind`), every member of set A (the\n// named `subjectSet`) must appear in set B (the node's `guardSet` string[]\n// attribute). A member of A absent from B is one violation. A node whose\n// `guardSet` attribute is absent (or not an array) is INDETERMINATE — skipped,\n// never a false positive; this is what keeps condition-based / unparseable\n// firestore.rules silent. The Firestore instance's set A is client-written\n// columns, whose `sdkWrites` tag is F1's (ADR-167) declared interface — read\n// defensively (absent ⇒ set A empty ⇒ no violation).\nconst evaluateFieldGuard: RuleEvaluator<Extract<PolicyRule, { type: 'field-guard' }>> = ({\n graph,\n policy,\n rule,\n ctx,\n}) => {\n const violations: PolicyViolation[] = []\n graph.forEachNode((id, attrs) => {\n const a = attrs as GraphNode & Record<string, unknown>\n if (a.type !== rule.nodeType) return\n if (rule.nodeKind !== undefined && a.kind !== rule.nodeKind) return\n // Set B — the covering guard set. Absent / non-array ⇒ indeterminate ⇒ silent.\n const guardRaw = a[rule.guardSet]\n if (!Array.isArray(guardRaw)) return\n const covering = new Set(guardRaw.map((g) => String(g)))\n // Set A — resolved from the named selector.\n for (const field of resolveFieldGuardSubject(rule.subjectSet, a)) {\n if (!covering.has(field)) {\n violations.push(\n makeViolation(\n policy,\n rule,\n `${id}:${field}`,\n `${a.type}${rule.nodeKind !== undefined ? ` (${rule.nodeKind})` : ''} ${id} writes \"${field}\" but it is absent from ${rule.guardSet}`,\n { nodeId: id },\n ctx,\n ),\n )\n }\n }\n })\n return violations\n}\n\n// Resolves a `field-guard` rule's set A off a node. A new named selector is a new\n// case here (ADR-043 growth) — the RuleEvaluator interface is untouched.\nfunction resolveFieldGuardSubject(\n selector: Extract<PolicyRule, { type: 'field-guard' }>['subjectSet'],\n node: Record<string, unknown>,\n): string[] {\n switch (selector) {\n case 'client-written-columns': {\n const columns = node.columns\n if (!Array.isArray(columns)) return []\n const out: string[] = []\n for (const col of columns) {\n // F1's ColumnAttr.sdkWrites (ADR-167). Read defensively so this compiles\n // and behaves before F1 lands: absent ⇒ not a client write ⇒ the column\n // is not in set A ⇒ no false positive. Admin-only writes bypass the\n // rules and are excluded.\n const sdkWrites = (col as { sdkWrites?: unknown }).sdkWrites\n const name = (col as { name?: unknown }).name\n if (Array.isArray(sdkWrites) && sdkWrites.includes('client') && typeof name === 'string') {\n out.push(name)\n }\n }\n return out\n }\n }\n}\n\nconst policyEvaluators: { [K in PolicyRule['type']]: RuleEvaluator<Extract<PolicyRule, { type: K }>> } = {\n structural: evaluateStructural,\n ownership: evaluateOwnership,\n provenance: evaluateProvenance,\n 'blast-radius': evaluateBlastRadius,\n compatibility: evaluateCompatibility,\n 'field-guard': evaluateFieldGuard,\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Public entry point\n// ──────────────────────────────────────────────────────────────────────────\n\n// Block-action gating for FrontierNode promotion (ADR-044 §block, MVP scope).\n// Runs the policy evaluator and returns the subset of block-action violations\n// that mention the candidate FrontierNode. Callers (ingest.ts\n// promoteFrontierNodes) check `allowed` before rewiring; when false, the\n// promotion is skipped and the violations surface through the standard\n// policy-violations.ndjson channel.\n//\n// Block scope is tightly bounded per the contract: FrontierNode promotion\n// only. Other gating points (deploy, codemod, OTel auto-create) need their\n// own ADRs before this function expands.\nexport function canPromoteFrontier(\n graph: NeatGraph,\n frontierId: string,\n policies: Policy[],\n ctx: EvaluationContext,\n): { allowed: boolean; violations: PolicyViolation[] } {\n if (policies.length === 0) return { allowed: true, violations: [] }\n const all = evaluateAllPolicies(graph, policies, ctx)\n const blocking = all.filter((v) => {\n if (v.onViolation !== 'block') return false\n return (\n v.subject.nodeId === frontierId ||\n v.subject.path?.includes(frontierId) === true\n )\n })\n return { allowed: blocking.length === 0, violations: blocking }\n}\n\nexport function evaluateAllPolicies(\n graph: NeatGraph,\n policies: Policy[],\n ctx: EvaluationContext,\n): PolicyViolation[] {\n const out: PolicyViolation[] = []\n for (const policy of policies) {\n const evaluator = policyEvaluators[policy.rule.type] as RuleEvaluator\n const violations = evaluator({ graph, policy, rule: policy.rule, ctx })\n for (const v of violations) out.push(v)\n }\n return out\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Soft guardrail — applicable-policy selection (ADR-108)\n// ──────────────────────────────────────────────────────────────────────────\n\n// The launch form of \"every agent stays inside the lines\": policies INFORM,\n// they never block. selectApplicablePolicies answers \"which policies govern the\n// node I'm about to edit?\" so the rules can ride into the agent's context.\n//\n// Matching is a subject/region match:\n// - subject — the node's type is the rule's declared subject (the rule\n// governs every node of that type).\n// - region — for most rule types, the node sits one hop inside the rule's\n// region: the target end of a structural edge, the database a compat rule\n// reaches across a CONNECTS_TO, or a node sitting on an edge a provenance\n// rule governs. For blast-radius rules the region is the subject's real\n// blast radius — its dependents: we walk getBlastRadius (to the rule's\n// declared depth) and surface the rule on every dependent it reaches, so a\n// far-away node that breaks when the subject changes still sees the\n// invariant that governs it.\n//\n// The general scope×distance injection over the policy overlay (ADR-105 §5),\n// which would carry this reachability behaviour to every rule type rather than\n// just blast-radius, is still unbuilt; the gap is written up in\n// docs/quirks/policies-soft-guardrail.md. selectApplicablePolicies never\n// evaluates a violation and never returns a verdict — surfacing a policy is not\n// gating it.\nexport function selectApplicablePolicies(\n graph: NeatGraph,\n policies: Policy[],\n nodeId: string,\n): ApplicablePolicy[] {\n // Without the node in the graph there's no type to match against, so we\n // can't decide applicability. A not-yet-created node (a brand-new edit) is\n // exactly the case the unbuilt overlay would handle; here we return empty\n // rather than guess.\n if (!graph.hasNode(nodeId)) return []\n const node = graph.getNodeAttributes(nodeId) as GraphNode\n const out: ApplicablePolicy[] = []\n for (const policy of policies) {\n const m = matchPolicyToNode(graph, policy, nodeId, node)\n if (!m) continue\n out.push({\n policyId: policy.id,\n policyName: policy.name,\n ...(policy.description !== undefined ? { description: policy.description } : {}),\n severity: policy.severity,\n onViolation: resolveOnViolation(policy),\n ruleType: policy.rule.type,\n match: m.match,\n reason: m.reason,\n })\n }\n return out\n}\n\ninterface PolicyMatch {\n match: ApplicablePolicy['match']\n reason: string\n}\n\nfunction requiredProvenanceList(required: string | readonly string[]): string {\n // required is ProvenanceRule['required'] — a single value or a non-empty\n // array. Normalize to a readable \"A | B\" string.\n if (Array.isArray(required)) return required.join(' | ')\n return String(required)\n}\n\nfunction nodeTouchesEdgeType(\n graph: NeatGraph,\n nodeId: string,\n edgeType: string,\n requiredOtherEnd?: string,\n): boolean {\n const incident = [...graph.outboundEdges(nodeId), ...graph.inboundEdges(nodeId)]\n for (const edgeId of incident) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== edgeType) continue\n // A provenance rule with a targetNodeId only governs edges that touch that\n // target. Without one, any edge of the type counts.\n if (requiredOtherEnd === undefined) return true\n if (e.source === requiredOtherEnd || e.target === requiredOtherEnd) return true\n }\n return false\n}\n\n// Reverse-reachability for blast-radius rules. Returns the id of a subject-type\n// node whose blast radius (its dependents, to the rule's declared depth)\n// contains nodeId, or null if none does. This is what lets a blast-radius rule\n// surface on the dependents it actually governs, not only on its subject.\nfunction blastRadiusSubjectReaching(\n graph: NeatGraph,\n rule: Extract<PolicyRule, { type: 'blast-radius' }>,\n nodeId: string,\n): string | null {\n let found: string | null = null\n graph.forEachNode((subjId, attrs) => {\n if (found !== null) return\n if (subjId === nodeId) return\n if ((attrs as GraphNode).type !== rule.nodeType) return\n const radius =\n rule.depth !== undefined\n ? getBlastRadius(graph, subjId, rule.depth)\n : getBlastRadius(graph, subjId)\n if (radius.affectedNodes.some((n) => n.nodeId === nodeId)) found = subjId\n })\n return found\n}\n\nfunction matchPolicyToNode(\n graph: NeatGraph,\n policy: Policy,\n nodeId: string,\n node: GraphNode,\n): PolicyMatch | null {\n const rule = policy.rule\n switch (rule.type) {\n case 'structural': {\n if (node.type === rule.fromNodeType) {\n return {\n match: 'subject',\n reason: `every ${rule.fromNodeType} must have a ${rule.edgeType} edge to a ${rule.toNodeType}`,\n }\n }\n // The target end is one hop inside the rule's region — editing it can\n // make a sibling fromNodeType pass or fail the rule.\n if (node.type === rule.toNodeType) {\n return {\n match: 'region',\n reason: `${rule.fromNodeType} nodes must reach a ${rule.toNodeType} like this one via a ${rule.edgeType} edge`,\n }\n }\n return null\n }\n case 'ownership': {\n if (node.type === rule.nodeType) {\n return {\n match: 'subject',\n reason: `every ${rule.nodeType} must declare a non-empty \"${rule.field}\" field`,\n }\n }\n return null\n }\n case 'blast-radius': {\n const depthLabel = rule.depth !== undefined ? ` at depth ${rule.depth}` : ''\n if (node.type === rule.nodeType) {\n return {\n match: 'subject',\n reason: `no ${rule.nodeType} may exceed a blast radius of ${rule.maxAffected}${depthLabel}`,\n }\n }\n // Dependent surfacing. The whole point of a blast-radius rule is \"this\n // subject has too many things depending on it\" — so the agent who most\n // needs the warning is the one editing one of those dependents, the nodes\n // that break if the subject changes. We compute the subject's actual\n // blast radius with getBlastRadius (to the rule's own declared depth) and\n // surface the rule on any dependent inside it. This is the buildable slice\n // of ADR-105 §5's scope×distance injection: a real reverse-reachability\n // walk, not the general overlay, so a far-away node still surfaces the\n // invariant.\n const subject = blastRadiusSubjectReaching(graph, rule, nodeId)\n if (subject) {\n return {\n match: 'region',\n reason: `this node is in the blast radius of ${subject} (a ${rule.nodeType} held to a blast radius of ${rule.maxAffected}${depthLabel}) — it breaks if that ${rule.nodeType} changes`,\n }\n }\n return null\n }\n case 'compatibility': {\n const kindLabel = rule.kind ?? 'all compat shapes'\n // The evaluator iterates every ServiceNode, so a ServiceNode is the\n // direct subject.\n if (node.type === NodeType.ServiceNode) {\n return {\n match: 'subject',\n reason: `this service's dependencies are compatibility-checked (${kindLabel})`,\n }\n }\n // A DatabaseNode one CONNECTS_TO hop from a service is in the region of\n // the driver-engine shape — its engine version feeds that check.\n const reachesDriverEngine = rule.kind === undefined || rule.kind === 'driver-engine'\n if (\n reachesDriverEngine &&\n node.type === NodeType.DatabaseNode &&\n nodeTouchesEdgeType(graph, nodeId, EdgeType.CONNECTS_TO)\n ) {\n return {\n match: 'region',\n reason: 'services connecting to this database have their driver/engine compatibility checked against it',\n }\n }\n return null\n }\n case 'provenance': {\n const requiredList = requiredProvenanceList(rule.required)\n // The named target is the direct subject — every governed edge points at\n // it.\n if (rule.targetNodeId !== undefined && nodeId === rule.targetNodeId) {\n return {\n match: 'subject',\n reason: `every ${rule.edgeType} edge into ${rule.targetNodeId} must carry ${requiredList} provenance`,\n }\n }\n // Otherwise the node is in the region if it sits on a governed edge.\n if (nodeTouchesEdgeType(graph, nodeId, rule.edgeType, rule.targetNodeId)) {\n return {\n match: 'region',\n reason:\n rule.targetNodeId !== undefined\n ? `this node sits on a ${rule.edgeType} edge to ${rule.targetNodeId}, which must carry ${requiredList} provenance`\n : `this node sits on a ${rule.edgeType} edge, which must carry ${requiredList} provenance`,\n }\n }\n return null\n }\n case 'field-guard': {\n // The governed node is the rule's direct subject — its type (and, when\n // narrowed, its `kind`) is what the rule checks. The rule reads two attribute\n // sets on the one node, so there is no region hop to surface.\n const kindLabel = rule.nodeKind !== undefined ? ` (kind ${rule.nodeKind})` : ''\n if (\n node.type === rule.nodeType &&\n (rule.nodeKind === undefined || (node as { kind?: string }).kind === rule.nodeKind)\n ) {\n return {\n match: 'subject',\n reason: `every ${rule.subjectSet} on this ${rule.nodeType}${kindLabel} must appear in ${rule.guardSet}`,\n }\n }\n return null\n }\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Loader\n// ──────────────────────────────────────────────────────────────────────────\n\n// Reads <projectRoot>/policy.json. Returns [] when the file doesn't exist —\n// a project without policies is a perfectly fine state. Failures to parse\n// throw with the Zod error so the daemon surfaces malformed files loudly\n// instead of silently dropping rules.\nexport async function loadPolicyFile(policyPath: string): Promise<Policy[]> {\n let raw: string\n try {\n raw = await fs.readFile(policyPath, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n const json = JSON.parse(raw) as unknown\n const file: PolicyFile = PolicyFileSchema.parse(json)\n return file.policies\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Append-only ndjson writer with id-based dedup\n// ──────────────────────────────────────────────────────────────────────────\n\n// Keeps an in-memory Set of seen violation ids so re-evaluation cycles don't\n// produce duplicate ndjson lines. The set hydrates from disk on first append\n// — startups that load an existing log don't lose dedup state.\nexport class PolicyViolationsLog {\n private readonly path: string\n private readonly project: string\n private seen: Set<string> | null = null\n\n constructor(logPath: string, project: string = DEFAULT_PROJECT) {\n this.path = logPath\n this.project = project\n }\n\n async append(v: PolicyViolation): Promise<boolean> {\n if (!this.seen) await this.hydrate()\n if (this.seen!.has(v.id)) return false\n this.seen!.add(v.id)\n await fs.mkdir(path.dirname(this.path), { recursive: true })\n await fs.appendFile(this.path, JSON.stringify(v) + '\\n', 'utf8')\n // Emit policy-violation only on first sighting (post-dedup) so SSE\n // consumers don't see the same violation again on every evaluation\n // cycle (ADR-051 #2).\n emitNeatEvent({\n type: 'policy-violation',\n project: this.project,\n payload: { violation: v },\n })\n return true\n }\n\n async readAll(): Promise<PolicyViolation[]> {\n try {\n const raw = await fs.readFile(this.path, 'utf8')\n return raw\n .split('\\n')\n .filter(Boolean)\n .map((line) => JSON.parse(line) as PolicyViolation)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n }\n\n private async hydrate(): Promise<void> {\n this.seen = new Set()\n const existing = await this.readAll()\n for (const v of existing) this.seen.add(v.id)\n }\n}\n","// Frontend-facing event bus (ADR-051). The single in-process EventEmitter\n// every producer (ingest / extract / watch / policy) emits through and the\n// only thing the SSE handler in streaming.ts subscribes to.\n//\n// Direct producer-to-handler coupling is a contract violation — every event\n// goes through `eventBus.emit('event', envelope)` so the SSE layer is the\n// only consumer that has to know the wire taxonomy.\n//\n// The taxonomy is locked at eight types (ADR-051 #2). Adding a ninth type\n// requires a successor ADR.\n\nimport { EventEmitter } from 'node:events'\nimport type { GraphEdge, GraphNode, PolicyViolation, Provenance } from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\n\n// Locked event taxonomy. Eight values. Tests assert the array shape\n// directly, so adding here without updating the contract is a regression.\nexport const NEAT_EVENT_TYPES = [\n 'node-added',\n 'node-updated',\n 'node-removed',\n 'edge-added',\n 'edge-removed',\n 'extraction-complete',\n 'policy-violation',\n 'stale-transition',\n] as const\n\nexport type NeatEventType = (typeof NEAT_EVENT_TYPES)[number]\n\n// Per-type payload shapes. The wire layer (SSE) writes the payload as the\n// `data` field; producers use `emit*` helpers below for type safety.\nexport interface NodeAddedPayload {\n node: GraphNode\n}\nexport interface NodeUpdatedPayload {\n id: string\n changes: Partial<GraphNode>\n}\nexport interface NodeRemovedPayload {\n id: string\n}\nexport interface EdgeAddedPayload {\n edge: GraphEdge\n}\nexport interface EdgeRemovedPayload {\n id: string\n}\nexport interface ExtractionCompletePayload {\n project: string\n fileCount: number\n nodesAdded: number\n edgesAdded: number\n}\nexport interface PolicyViolationPayload {\n violation: PolicyViolation\n}\nexport interface StaleTransitionPayload {\n edgeId: string\n from: typeof Provenance.OBSERVED\n to: typeof Provenance.STALE\n}\n\nexport type NeatEventPayload = {\n 'node-added': NodeAddedPayload\n 'node-updated': NodeUpdatedPayload\n 'node-removed': NodeRemovedPayload\n 'edge-added': EdgeAddedPayload\n 'edge-removed': EdgeRemovedPayload\n 'extraction-complete': ExtractionCompletePayload\n 'policy-violation': PolicyViolationPayload\n 'stale-transition': StaleTransitionPayload\n}\n\n// What the bus carries internally. Project is metadata used by the SSE\n// handler to route between /events and /projects/:project/events; it never\n// lands in the wire payload.\nexport interface NeatEventEnvelope<T extends NeatEventType = NeatEventType> {\n type: T\n project: string\n payload: NeatEventPayload[T]\n}\n\n// Single event channel — listeners filter by `envelope.type` and\n// `envelope.project`. Using one channel keeps the contract surface narrow\n// and matches the SSE handler's needs (one subscription, fan out).\nexport const EVENT_BUS_CHANNEL = 'event'\n\nclass NeatEventBus extends EventEmitter {}\n\n// Singleton. Process-wide so producers in ingest / extract / watch / policy\n// can emit without threading a bus instance through every call site.\nexport const eventBus: NeatEventBus = new NeatEventBus()\n\n// EventEmitter defaults to 10 listeners; SSE clients add up quickly under a\n// browser refresh storm, so lift the cap.\neventBus.setMaxListeners(0)\n\nexport function emitNeatEvent<T extends NeatEventType>(envelope: NeatEventEnvelope<T>): void {\n eventBus.emit(EVENT_BUS_CHANNEL, envelope)\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Graph subscription — one place to wire graphology mutation events into\n// the bus so producers don't have to instrument every addNode/addEdge.\n// ──────────────────────────────────────────────────────────────────────────\n\nexport interface AttachOptions {\n project: string\n}\n\n// Subscribes to a NeatGraph and re-emits node/edge add/remove + node/edge\n// attribute updates as bus envelopes scoped to `project`. Returns a detach\n// fn that removes every listener it installed.\n//\n// Stale-transition is NOT routed through here — a provenance flip is just an\n// attribute update from graphology's view, and we'd lose the OBSERVED→STALE\n// semantic. ingest.ts emits stale-transition itself.\nexport function attachGraphToEventBus(graph: NeatGraph, opts: AttachOptions): () => void {\n const { project } = opts\n\n const onNodeAdded = (payload: { key: string; attributes: GraphNode }): void => {\n emitNeatEvent({\n type: 'node-added',\n project,\n payload: { node: payload.attributes },\n })\n }\n const onNodeDropped = (payload: { key: string }): void => {\n emitNeatEvent({\n type: 'node-removed',\n project,\n payload: { id: payload.key },\n })\n }\n const onEdgeAdded = (payload: { key: string; attributes: GraphEdge }): void => {\n emitNeatEvent({\n type: 'edge-added',\n project,\n payload: { edge: payload.attributes },\n })\n }\n const onEdgeDropped = (payload: { key: string }): void => {\n emitNeatEvent({\n type: 'edge-removed',\n project,\n payload: { id: payload.key },\n })\n }\n const onNodeAttrsUpdated = (payload: { key: string; attributes: GraphNode }): void => {\n emitNeatEvent({\n type: 'node-updated',\n project,\n payload: { id: payload.key, changes: payload.attributes },\n })\n }\n\n graph.on('nodeAdded', onNodeAdded)\n graph.on('nodeDropped', onNodeDropped)\n graph.on('edgeAdded', onEdgeAdded)\n graph.on('edgeDropped', onEdgeDropped)\n graph.on('nodeAttributesUpdated', onNodeAttrsUpdated)\n\n return () => {\n graph.off('nodeAdded', onNodeAdded)\n graph.off('nodeDropped', onNodeDropped)\n graph.off('edgeAdded', onEdgeAdded)\n graph.off('edgeDropped', onEdgeDropped)\n graph.off('nodeAttributesUpdated', onNodeAttrsUpdated)\n }\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport Python from 'tree-sitter-python'\nimport Go from 'tree-sitter-go'\nimport Ruby from 'tree-sitter-ruby'\nimport Php from 'tree-sitter-php'\nimport type { GraphEdge, RouteNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n routeId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport { isTestPath, type DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { loadSourceFiles, snippet, toPosix, type SourceFile } from './calls/shared.js'\nimport { resolveJsImport, loadTsPathConfig, type TsPathConfig } from './imports.js'\n\n// Server-route extraction (ADR-119). Reads a mainstream router's route table —\n// Express (`app.get`/`router.post`/…), Fastify (`fastify.get`, `fastify.route`),\n// Next.js (app-router `route.*` handlers, pages `api/` handlers) — and\n// materialises each route as a RouteNode at (method, path-template) grain, owned\n// by its service through a `service ──CONTAINS──▶ route` edge. This is the\n// server half of the static contract-matching in calls/route-match.ts: a\n// client call site is matched to the route it names, bridging the two static\n// islands into a route-grained cross-service CALLS edge.\n//\n// Scope is mainstream routers only, gated by manifest dependency (the extensible\n// registry pattern — coverage grows one router at a time, not by exhaustive\n// heuristics). A service with none of these deps is skipped.\n\nconst PARSE_CHUNK = 16384\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nfunction makeJsParser(): Parser {\n const p = new Parser()\n p.setLanguage(JavaScript)\n return p\n}\n\nfunction makePyParser(): Parser {\n const p = new Parser()\n p.setLanguage(Python)\n return p\n}\n\nfunction makeGoParser(): Parser {\n const p = new Parser()\n p.setLanguage(Go)\n return p\n}\n\nfunction makeRubyParser(): Parser {\n const p = new Parser()\n p.setLanguage(Ruby)\n return p\n}\n\n// Laravel's route files are pure PHP (a controller + route definitions, no HTML\n// islands), so the grammar's `php_only` variant is the right parser — `.php`\n// would accept `?>`-delimited HTML the route files never carry (ADR-177).\nfunction makePhpParser(): Parser {\n const p = new Parser()\n p.setLanguage(Php.php_only)\n return p\n}\n\n// The HTTP verbs an Express / Fastify router registers a route under. `all`\n// registers a method-agnostic route; it normalises to the `ALL` method token.\nconst ROUTER_METHODS = new Set([\n 'get',\n 'post',\n 'put',\n 'patch',\n 'delete',\n 'options',\n 'head',\n 'all',\n])\n\n// The exported handler names a Next.js app-router `route.*` file uses, one per\n// HTTP method it serves.\nconst NEXT_APP_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'])\n\n// The HTTP methods a Go 1.22 `net/http` ServeMux pattern may lead with\n// (`\"GET /orders/{id}\"`). This precise set is half the structural gate: the\n// leading token must be one of these AND the remainder must start with `/`,\n// which is what keeps a generic `HandleFunc(\"literal\", h)` — or a pre-1.22\n// bare-prefix registration — from minting a route (ADR-182).\nconst NET_HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'])\n\nconst JS_ROUTE_EXTENSIONS = new Set(['.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx'])\n\nexport interface ExtractedRoute {\n method: string // upper-cased HTTP method, or 'ALL' for a method-agnostic route\n pathTemplate: string // canonicalised declared template, e.g. '/users/:id'\n line: number // 1-indexed line the route is declared on\n framework: string // Router registry label; NestJS joins the existing JS/Python set in ADR-155.\n // The route's `controller#action` handler, when the recogniser knows it —\n // Rails' resourceful expansion derives it (ADR-173). RouteNode carries no\n // handler field today, so `addRoutes` keeps this off the graph; it exists so\n // the recogniser tests can assert the module/pluralisation logic the path\n // template alone can't show (a `scope module:` route differs only here).\n controller?: string\n}\n\n// Gin, Echo, and Fiber all declare their route table the same way — a router\n// value carries an HTTP-verb method (`r.GET(...)`, `e.GET(...)`, `app.Get(...)`)\n// whose first argument is the path, and `<router>.Group(\"/prefix\")` returns a\n// sub-router that composes a prefix onto everything registered on it. At the Go\n// AST that is one grammar: a `call_expression` on a `selector_expression`, the\n// verb differing only in casing (Gin/Echo upper-case `GET`, Fiber title-case\n// `Get`) which the lower-cased method check already absorbs. So one walker reads\n// all three; they differ only in the framework label and the manifest dependency\n// that gates them (`addRoutes`). A group variable is tracked the way the receiver\n// var is: `admin := r.Group(\"/admin\")` records `admin → /admin`, and a group\n// opened off another group (`v1 := admin.Group(\"/v1\")`) composes onto its\n// parent's prefix, so nested groups reach `/admin/v1`. Both `:=` and `var x = …`\n// forms are read. Route-param constraints and middleware-only / `Static` mounts\n// are deferred.\nfunction goRouterRoutesFromSource(\n source: string,\n parser: Parser,\n framework: string,\n): ExtractedRoute[] {\n const tree = parseSource(parser, source)\n const prefixes = new Map<string, string>()\n const out: ExtractedRoute[] = []\n walk(tree.rootNode, (node) => {\n if (node.type === 'short_var_declaration' || node.type === 'var_spec') {\n const name =\n node.childForFieldName('left')?.namedChild(0)?.text ?? node.childForFieldName('name')?.text\n const value =\n node.childForFieldName('right')?.namedChild(0) ?? node.childForFieldName('value')\n if (name && value?.type === 'call_expression') {\n const fn = value.childForFieldName('function')\n if (fn?.childForFieldName('field')?.text === 'Group') {\n const leaf = goStringLiteral(value.childForFieldName('arguments')?.namedChild(0))\n if (leaf !== null) {\n const parent = fn.childForFieldName('operand')?.text ?? ''\n prefixes.set(name, (prefixes.get(parent) ?? '') + leaf)\n }\n }\n }\n return\n }\n if (node.type !== 'call_expression') return\n const fn = node.childForFieldName('function')\n if (fn?.type !== 'selector_expression') return\n const method = fn.childForFieldName('field')?.text?.toUpperCase()\n if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return\n const receiver = fn.childForFieldName('operand')?.text ?? ''\n const leaf = goStringLiteral(node.childForFieldName('arguments')?.namedChild(0))\n if (leaf === null) return\n out.push({\n method,\n pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? '') + leaf),\n line: node.startPosition.row + 1,\n framework,\n })\n })\n return out\n}\n\n// Read a Go string literal's inner text. Both the double-quoted\n// (`interpreted_string_literal`) and back-quoted (`raw_string_literal`) forms\n// carry their delimiters in `.text`, so slicing one char off each end yields the\n// path; a non-literal argument (a computed path) returns null and is skipped.\nfunction goStringLiteral(node: Parser.SyntaxNode | null | undefined): string | null {\n if (node?.type === 'interpreted_string_literal' || node?.type === 'raw_string_literal') {\n return node.text.slice(1, -1)\n }\n return null\n}\n\n// Gin (`github.com/gin-gonic/gin`).\nexport function ginRoutesFromSource(source: string, parser: Parser): ExtractedRoute[] {\n return goRouterRoutesFromSource(source, parser, 'gin')\n}\n\n// Echo (`github.com/labstack/echo/v4`, also plain `.../echo`). `otelecho` sets\n// `http.route` from `c.Path()` — the group-composed template — so the extracted\n// node fuses through `normalizePathTemplate` with no ingest change.\nexport function echoRoutesFromSource(source: string, parser: Parser): ExtractedRoute[] {\n return goRouterRoutesFromSource(source, parser, 'echo')\n}\n\n// Fiber (`github.com/gofiber/fiber/v2`, also `/v3`). Both the v2 `otelfiber` and\n// the v3 middleware set `http.route` from `c.Route().Path` — the group-composed\n// template — so the node fuses with no ingest change. Fiber's optional-param\n// `:id?` reduces to `:param` like `:id`; a bare `*` / `+` wildcard stays literal\n// on both the extracted template and `c.Route().Path`, so it still matches.\nexport function fiberRoutesFromSource(source: string, parser: Parser): ExtractedRoute[] {\n return goRouterRoutesFromSource(source, parser, 'fiber')\n}\n\n// ── Chi (`github.com/go-chi/chi/v5`, also pre-modules `.../chi`) ─────────────\n//\n// Chi declares verbs the Fiber way — title-cased `r.Get/Post/Put/Patch/Delete`\n// on a router value, first argument the path — so the verb read is the same one\n// the Gin/Echo/Fiber walker does. What Chi does differently is prefix\n// composition. Gin/Echo/Fiber name a sub-router (`admin := r.Group(\"/admin\")`)\n// and the prefix follows that variable; Chi's `Route(\"/articles\", func(r\n// chi.Router){ … })` has no assignment — the prefix is lexically scoped to the\n// closure body and the inner `r` shadows the outer. So a variable Map can't\n// track it; a prefix STACK can. This walker recurses the tree carrying the\n// accumulated prefix: a `Route(strLit, funcLit)` pushes the literal and recurses\n// into the `func_literal` body, nested `Route`s compose (`/articles` +\n// `/{articleID}`), and the prefix pops on return (the recursion frame). A\n// `Group(funcLit)` — first argument a closure, not a string — is middleware\n// grouping: it contributes no prefix, just recurses. A `Mount(\"/admin\", h)` is\n// deferred: the mounted sub-router is usually built in another func or file, the\n// same cross-func/cross-file resolution Express `app.use` needed before ADR-160,\n// so it's skipped here (costing one fused edge, since otelchi emits the full\n// composed path).\n//\n// Inline regex constraints (`{id:[0-9]+}`) are stripped to `{id}` so the stored\n// template stays clean and `canonicalizeTemplate` can't read a `?` inside the\n// regex as a query delimiter; either way `normalizePathTemplate` collapses a\n// `{…}` segment to `:param`, so fusion is unaffected.\n//\n// FUSION CAVEAT (ADR-182): Chi fuses only under `otelchi`\n// (`github.com/riandyrn/otelchi`), which reads `chi.RouteContext(r).RoutePattern()`\n// — the composed template — and sets `http.route` to it (path-only), the same\n// shape echo's `c.Path()` / fiber's `c.Route().Path` produce. Under bare\n// `otelhttp` a Chi app sets NO `http.route` (Chi never populates `r.Pattern`),\n// so these RouteNodes stay EXTRACTED-only with nothing to fuse onto.\nexport function chiRoutesFromSource(source: string, parser: Parser): ExtractedRoute[] {\n const tree = parseSource(parser, source)\n const out: ExtractedRoute[] = []\n chiWalk(tree.rootNode, '', out)\n return out\n}\n\n// Strip a Chi inline-regex constraint from each param segment: `{id:[0-9]+}` →\n// `{id}`. Handles the common single-level case (a regex with no nested braces);\n// a regex carrying its own `{n,m}` quantifier is left as-is, and still collapses\n// to `:param` at match time.\nfunction stripChiRegex(path: string): string {\n return path.replace(/\\{([^{}:]+):[^{}]*\\}/g, '{$1}')\n}\n\n// Recurse the tree carrying the accumulated route prefix. A `Route`/`Group` call\n// is handled specially (its closure body is recursed with the pushed/unchanged\n// prefix and the generic descent stops there); a verb call emits; everything\n// else descends with the same prefix.\nfunction chiWalk(node: Parser.SyntaxNode, prefix: string, out: ExtractedRoute[]): void {\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) chiHandle(child, prefix, out)\n }\n}\n\nfunction chiHandle(node: Parser.SyntaxNode, prefix: string, out: ExtractedRoute[]): void {\n if (node.type === 'call_expression') {\n const fn = node.childForFieldName('function')\n if (fn?.type === 'selector_expression') {\n const field = fn.childForFieldName('field')?.text\n const args = node.childForFieldName('arguments')\n if (field === 'Route') {\n const leaf = goStringLiteral(args?.namedChild(0))\n const closure = args?.namedChild(1)\n if (leaf !== null && closure?.type === 'func_literal') {\n const body = closure.childForFieldName('body')\n if (body) chiWalk(body, prefix + leaf, out)\n }\n return // handled — the string arg and closure carry no other routes\n }\n if (field === 'Group') {\n // Chi's `Group(func(r chi.Router){ … })` is middleware grouping: the\n // first argument is a closure, not a path, so it adds no prefix.\n const closure = args?.namedChild(0)\n if (closure?.type === 'func_literal') {\n const body = closure.childForFieldName('body')\n if (body) chiWalk(body, prefix, out)\n }\n return\n }\n if (field === 'Mount') {\n return // deferred (ADR-182) — the mounted router is resolved elsewhere\n }\n if (field && ROUTER_METHODS.has(field.toLowerCase())) {\n const leaf = goStringLiteral(args?.namedChild(0))\n if (leaf !== null) {\n out.push({\n method: field.toUpperCase(),\n pathTemplate: canonicalizeTemplate(stripChiRegex(prefix + leaf)),\n line: node.startPosition.row + 1,\n framework: 'chi',\n })\n }\n return\n }\n }\n }\n // Not a Chi routing call — descend into its children at the same prefix.\n chiWalk(node, prefix, out)\n}\n\n// ── net/http (Go 1.22+ ServeMux method patterns) ────────────────────────────\n//\n// Go 1.22's `ServeMux` carries the method and the path template in ONE string\n// literal: `mux.HandleFunc(\"GET /orders/{id}\", h)`, `http.HandleFunc(…)`,\n// `mux.Handle(…)`, `http.Handle(…)`. Read the first string-literal argument and\n// split on the first space; emit a RouteNode ONLY when the leading token is an\n// HTTP method AND the remainder starts with `/`. That precision is the whole\n// gate: net/http is stdlib, so there's no go.mod dependency to key on the way\n// Gin/Echo/Fiber/Chi are. Instead this recogniser self-gates STRUCTURALLY — the\n// file must `import \"net/http\"`, and only the method-prefixed pattern shape\n// mints a route, which is what keeps a generic `HandleFunc(\"literal\", h)` or a\n// pre-1.22 bare-prefix `HandleFunc(\"/orders/\", h)` (no method, coarse subtree —\n// the biggest false-positive source) from being read (ADR-182).\n//\n// `{id}` (single segment), `{path...}` (multi-segment wildcard), and `{$}` (the\n// end-of-path anchor) all sit under `normalizePathTemplate` unchanged — each is a\n// `{…}` segment that collapses to `:param` — so fusion needs no special casing.\n//\n// Fusion is automatic on a modern stack (ADR-182): Go 1.23's `Request.Pattern` +\n// otelhttp ≥ v1.36 set `http.route` to the path-only template (method stripped),\n// exactly the template stored here. On an older stack `http.route` is absent and\n// the node stays EXTRACTED-only — an honest version gate, documented not guessed.\nexport function netHttpRoutesFromSource(source: string, parser: Parser): ExtractedRoute[] {\n const tree = parseSource(parser, source)\n if (!goImportsNetHttp(tree.rootNode)) return []\n const out: ExtractedRoute[] = []\n walk(tree.rootNode, (node) => {\n if (node.type !== 'call_expression') return\n const fn = node.childForFieldName('function')\n if (fn?.type !== 'selector_expression') return\n const field = fn.childForFieldName('field')?.text\n if (field !== 'HandleFunc' && field !== 'Handle') return\n const leaf = goStringLiteral(node.childForFieldName('arguments')?.namedChild(0))\n if (leaf === null) return\n const sp = leaf.indexOf(' ')\n if (sp < 0) return // no method token — pre-1.22 bare-prefix form, skipped\n const method = leaf.slice(0, sp)\n const rest = leaf.slice(sp + 1)\n if (!NET_HTTP_METHODS.has(method)) return\n if (!rest.startsWith('/')) return // a host-prefixed pattern isn't composed\n out.push({\n method,\n pathTemplate: canonicalizeTemplate(rest),\n line: node.startPosition.row + 1,\n framework: 'net/http',\n })\n })\n return out\n}\n\n// Whether the file imports the `net/http` package — the structural half of the\n// net/http gate. Reads both the grouped `import ( … )` and the single\n// `import \"net/http\"` forms (each yields an `import_spec` with a string path).\nfunction goImportsNetHttp(root: Parser.SyntaxNode): boolean {\n let found = false\n walk(root, (node) => {\n if (found || node.type !== 'import_spec') return\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (goStringLiteral(child) === 'net/http') found = true\n }\n })\n return found\n}\n\n// The HTTP verbs FastAPI/Starlette register a route decorator under\n// (`@router.get(...)`, `@app.post(...)`, …). `api_route` is handled separately —\n// it carries its methods in a `methods=[…]` keyword argument.\nconst FASTAPI_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace'])\n\nconst NESTJS_METHODS = new Map([\n ['Get', 'GET'],\n ['Post', 'POST'],\n ['Put', 'PUT'],\n ['Patch', 'PATCH'],\n ['Delete', 'DELETE'],\n ['Options', 'OPTIONS'],\n ['Head', 'HEAD'],\n ['All', 'ALL'],\n])\n\n// ── path-template canonicalisation ──────────────────────────────────────────\n\n// Canonicalise a declared route path for use as a RouteNode's stable template:\n// drop any query/hash, ensure a leading slash, drop a trailing slash (except\n// root). The template keeps its declared params verbatim (`:id`, `{id}`) so an\n// OBSERVED server span carrying the same `http.route` lands on the same node.\nexport function canonicalizeTemplate(raw: string): string {\n let p = raw.split('?')[0]!.split('#')[0]!\n if (!p.startsWith('/')) p = '/' + p\n if (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1)\n return p\n}\n\n// A path segment is dynamic when it names a parameter rather than a literal.\n// Covers the router param syntaxes (`:id`, `{id}`, `[id]`, `[...slug]`), a\n// reconstructed client interpolation (`:param`), and a concrete value a client\n// URL carries in a param position (all-digits, uuid, long hex / Mongo id).\nfunction isDynamicSegment(seg: string): boolean {\n if (seg.length === 0) return false\n if (seg.includes(':')) return true // :id (express/fastify) or reconstructed :param\n if (seg.startsWith('{') || seg.startsWith('[')) return true // {id} openapi, [id] next\n if (/^\\d+$/.test(seg)) return true // concrete numeric id\n if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true // uuid\n if (/^[0-9a-f]{24,}$/i.test(seg)) return true // mongo objectid / long hex token\n return false\n}\n\n// Normalise a path-template to a param-agnostic matching key: every dynamic\n// segment collapses to `:param`, every literal segment lowercases. This is the\n// comparison form both a server route's declared template and a client call's\n// URL path reduce to, so `/users/:id` (server) matches `/users/123` and\n// `/users/${userId}` (client). The declared template is kept intact on the\n// node; only matching uses this reduction.\nexport function normalizePathTemplate(raw: string): string {\n const canonical = canonicalizeTemplate(raw)\n const segments = canonical.split('/').filter((s) => s.length > 0)\n const normalised = segments.map((seg) => (isDynamicSegment(seg) ? ':param' : seg.toLowerCase()))\n return '/' + normalised.join('/')\n}\n\n// ── AST helpers ─────────────────────────────────────────────────────────────\n\nfunction walk(node: Parser.SyntaxNode, visit: (n: Parser.SyntaxNode) => void): void {\n visit(node)\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) walk(child, visit)\n }\n}\n\n// The interior text of a string literal, stripped of quotes. Returns null for a\n// template string carrying interpolation (a route path is a static literal).\nfunction staticStringText(node: Parser.SyntaxNode): string | null {\n if (node.type === 'string') {\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child?.type === 'string_fragment') return child.text\n }\n // Empty string literal ('' — no fragment child).\n return ''\n }\n if (node.type === 'template_string') {\n // Only a template with no substitution is a usable static path.\n for (let i = 0; i < node.namedChildCount; i++) {\n if (node.namedChild(i)?.type === 'template_substitution') return null\n }\n const raw = node.text\n return raw.length >= 2 ? raw.slice(1, -1) : ''\n }\n return null\n}\n\n// Read a string-valued property off an object-expression node (`{ url: '/x' }`).\nfunction objectStringProp(objNode: Parser.SyntaxNode, key: string): string | null {\n for (let i = 0; i < objNode.namedChildCount; i++) {\n const pair = objNode.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n if (!k) continue\n const kText = k.type === 'string' ? staticStringText(k) : k.text\n if (kText !== key) continue\n const v = pair.childForFieldName('value')\n if (v) return staticStringText(v)\n }\n return null\n}\n\n// Read the `method` property off a Fastify route-options object. Accepts a\n// single string (`method: 'GET'`) or an array (`method: ['GET','POST']`).\nfunction fastifyRouteMethods(objNode: Parser.SyntaxNode): string[] {\n for (let i = 0; i < objNode.namedChildCount; i++) {\n const pair = objNode.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n const kText = k ? (k.type === 'string' ? staticStringText(k) : k.text) : null\n if (kText !== 'method') continue\n const v = pair.childForFieldName('value')\n if (!v) return []\n if (v.type === 'string' || v.type === 'template_string') {\n const s = staticStringText(v)\n return s ? [s.toUpperCase()] : []\n }\n if (v.type === 'array') {\n const out: string[] = []\n for (let j = 0; j < v.namedChildCount; j++) {\n const el = v.namedChild(j)\n if (el && (el.type === 'string' || el.type === 'template_string')) {\n const s = staticStringText(el)\n if (s) out.push(s.toUpperCase())\n }\n }\n return out\n }\n }\n return []\n}\n\n// ── NestJS decorator routes ────────────────────────────────────────────────\n\n// Collect only route decorators imported by name from `@nestjs/common`.\n// The local name is the key, so `Get as Read` remains deterministic without\n// admitting an unrelated decorator that happens to be named `Get`.\nfunction nestDecoratorImports(root: Parser.SyntaxNode): Map<string, string> {\n const imports = new Map<string, string>()\n walk(root, (node) => {\n if (node.type !== 'import_statement') return\n const source = node.childForFieldName('source')\n if (!source || staticStringText(source) !== '@nestjs/common') return\n walk(node, (child) => {\n if (child.type !== 'import_specifier') return\n const imported = child.childForFieldName('name')?.text\n const local = child.childForFieldName('alias')?.text ?? imported\n if (!imported || !local) return\n if (imported === 'Controller' || NESTJS_METHODS.has(imported)) {\n imports.set(local, imported)\n }\n })\n })\n return imports\n}\n\nfunction decoratorCall(node: Parser.SyntaxNode): Parser.SyntaxNode | null {\n if (node.type !== 'decorator') return null\n const expression = node.namedChild(0)\n return expression?.type === 'call_expression' ? expression : null\n}\n\nfunction decoratorCanonicalName(\n decorator: Parser.SyntaxNode,\n imports: Map<string, string>,\n): string | null {\n const call = decoratorCall(decorator)\n const fn = call?.childForFieldName('function')\n if (!fn || fn.type !== 'identifier') return null\n return imports.get(fn.text) ?? null\n}\n\n// Nest accepts one path or an array of paths on both Controller and method\n// decorators. Keep only static alternatives; a computed member contributes no\n// guessed route. No argument is the empty path segment.\nfunction nestStaticPaths(call: Parser.SyntaxNode): string[] {\n const args = call.childForFieldName('arguments')\n const first = args?.namedChild(0)\n if (!first) return ['']\n const single = staticStringText(first)\n if (single !== null) return [single]\n if (first.type !== 'array') return []\n const paths: string[] = []\n for (let i = 0; i < first.namedChildCount; i++) {\n const item = first.namedChild(i)\n if (!item) continue\n const value = staticStringText(item)\n if (value !== null) paths.push(value)\n }\n return paths\n}\n\nfunction nestJoinedPath(prefix: string, leaf: string): string {\n const segments = [prefix, leaf]\n .map((part) => part.replace(/^\\/+|\\/+$/g, ''))\n .filter((part) => part.length > 0)\n return canonicalizeTemplate(segments.join('/'))\n}\n\n// Nest composes class-level `@Controller()` metadata with method-level HTTP\n// decorators. The method decorator is the definition site, so its line is the\n// evidence line and matches where a maintainer edits the route.\nexport function nestjsRoutesFromSource(source: string, parser: Parser): ExtractedRoute[] {\n const tree = parseSource(parser, source)\n const imports = nestDecoratorImports(tree.rootNode)\n if (![...imports.values()].includes('Controller')) return []\n\n const out: ExtractedRoute[] = []\n walk(tree.rootNode, (node) => {\n if (node.type !== 'class_declaration') return\n const decoratorOwner = node.parent?.type === 'export_statement' ? node.parent : node\n const classDecorators: Parser.SyntaxNode[] = []\n for (let i = 0; i < decoratorOwner.namedChildCount; i++) {\n const child = decoratorOwner.namedChild(i)\n if (child?.type === 'decorator') classDecorators.push(child)\n }\n const controller = classDecorators.find(\n (decorator) => decoratorCanonicalName(decorator, imports) === 'Controller',\n )\n const controllerCall = controller ? decoratorCall(controller) : null\n if (!controllerCall) return\n const prefixes = nestStaticPaths(controllerCall)\n if (prefixes.length === 0) return\n\n const body = node.childForFieldName('body')\n if (!body) return\n for (let i = 0; i < body.namedChildCount; i++) {\n const methodNode = body.namedChild(i)\n if (methodNode?.type !== 'method_definition') continue\n for (let j = 0; j < methodNode.namedChildCount; j++) {\n const decorator = methodNode.namedChild(j)\n if (decorator?.type !== 'decorator') continue\n const canonical = decoratorCanonicalName(decorator, imports)\n const method = canonical ? NESTJS_METHODS.get(canonical) : undefined\n const call = decoratorCall(decorator)\n if (!method || !call) continue\n const leaves = nestStaticPaths(call)\n for (const prefix of prefixes) {\n for (const leaf of leaves) {\n out.push({\n method,\n pathTemplate: nestJoinedPath(prefix, leaf),\n line: decorator.startPosition.row + 1,\n framework: 'nestjs',\n })\n }\n }\n }\n }\n })\n return out\n}\n\n// ── Express / Fastify / Hono call-expression routes ─────────────────────────\n\n// Recognise route registrations of the shape `<router>.<method>('/path', …)`\n// — Express, Fastify, and Hono (`hono.get('/path', handler)` etc.) all share\n// this exact call shape (ADR-133 §5: same registry pattern, Cloudflare\n// Worker's route-grain fast-follow) — and Fastify's own generic\n// `<fastify>.route({ method, url })` form. The guard that keeps this off\n// `db.get('key')` / `_.get(obj, path)` is a string first argument that starts\n// with '/', combined with the caller-side dep gate in addRoutes (only\n// services that depend on express / fastify / hono reach here). This per-file\n// scan captures the leaf path as declared; the Express mount prefix\n// (`app.use('/api', router)`) is composed onto it afterwards by\n// `expressMountPrefixes` (ADR-160), the whole-program pass that resolves the\n// mounted router across files. `.route().get()` chaining and Hono's own\n// `app.on([...methods], '/path', handler)` form remain out of scope for this\n// slice. Coverage grows one router at a time, same discipline as the registry.\nexport function serverRoutesFromSource(\n source: string,\n parser: Parser,\n hasExpress: boolean,\n hasFastify: boolean,\n hasHono = false,\n): ExtractedRoute[] {\n const tree = parseSource(parser, source)\n const out: ExtractedRoute[] = []\n const framework = hasExpress ? 'express' : hasFastify ? 'fastify' : hasHono ? 'hono' : 'unknown'\n walk(tree.rootNode, (node) => {\n if (node.type !== 'call_expression') return\n const fn = node.childForFieldName('function')\n if (!fn || fn.type !== 'member_expression') return\n const prop = fn.childForFieldName('property')\n if (!prop) return\n const method = prop.text.toLowerCase()\n const args = node.childForFieldName('arguments')\n const first = args?.namedChild(0)\n if (!first) return\n const line = node.startPosition.row + 1\n\n if (ROUTER_METHODS.has(method)) {\n const p = staticStringText(first)\n if (p && p.startsWith('/')) {\n out.push({\n method: method === 'all' ? 'ALL' : method.toUpperCase(),\n pathTemplate: canonicalizeTemplate(p),\n line,\n framework,\n })\n }\n return\n }\n\n // Fastify's generic form: fastify.route({ method, url }).\n if (method === 'route' && hasFastify && first.type === 'object') {\n const url = objectStringProp(first, 'url')\n if (!url || !url.startsWith('/')) return\n const methods = fastifyRouteMethods(first)\n const list = methods.length > 0 ? methods : ['ALL']\n for (const m of list) {\n out.push({\n method: m === 'ALL' ? 'ALL' : m.toUpperCase(),\n pathTemplate: canonicalizeTemplate(url),\n line,\n framework: 'fastify',\n })\n }\n }\n })\n return out\n}\n\n// ── Next.js file-convention routes ──────────────────────────────────────────\n\nfunction segmentsOf(relFile: string): string[] {\n return toPosix(relFile).split('/').filter((s) => s.length > 0)\n}\n\n// An app-router route handler file: `<…>/app/**/route.{js,ts,jsx,tsx,…}` (also\n// `src/app`). Route handlers live only in a file literally named `route`.\nexport function isNextAppRouteFile(relFile: string): boolean {\n const segs = segmentsOf(relFile)\n if (!segs.includes('app')) return false\n const base = segs[segs.length - 1] ?? ''\n return /^route\\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base)\n}\n\n// A pages-router API file: `<…>/pages/api/**/*.{js,ts,…}`. Skips Next's special\n// `_app` / `_document` / `_middleware` files, which aren't routes.\nexport function isNextPagesApiFile(relFile: string): boolean {\n const segs = segmentsOf(relFile)\n const pagesIdx = segs.indexOf('pages')\n if (pagesIdx === -1 || segs[pagesIdx + 1] !== 'api') return false\n const base = segs[segs.length - 1] ?? ''\n if (/^_(app|document|middleware)\\./.test(base)) return false\n return JS_ROUTE_EXTENSIONS.has(path.extname(base))\n}\n\n// Convert one Next path segment to its template form: route groups `(group)`\n// drop out, `[...slug]` / `[[...slug]]` catch-alls and `[id]` dynamics become\n// `:name`, everything else stays literal.\nfunction nextSegment(seg: string): string | null {\n if (seg.startsWith('(') && seg.endsWith(')')) return null // route group — not in the URL\n const catchAll = seg.match(/^\\[\\[?\\.\\.\\.(.+?)\\]?\\]$/)\n if (catchAll) return ':' + catchAll[1]\n const dynamic = seg.match(/^\\[(.+?)\\]$/)\n if (dynamic) return ':' + dynamic[1]\n return seg\n}\n\n// Derive the URL path-template from an app-router `route.*` file's directory:\n// `app/users/[id]/route.ts` → `/users/:id`.\nfunction nextAppPathTemplate(relFile: string): string {\n const segs = segmentsOf(relFile)\n const appIdx = segs.lastIndexOf('app')\n const between = segs.slice(appIdx + 1, segs.length - 1) // dirs between app/ and route.*\n const parts: string[] = []\n for (const seg of between) {\n const mapped = nextSegment(seg)\n if (mapped !== null) parts.push(mapped)\n }\n return '/' + parts.join('/')\n}\n\n// Derive the URL path-template from a pages `api/` file:\n// `pages/api/users/[id].ts` → `/api/users/:id`, `pages/api/index.ts` → `/api`.\nfunction nextPagesApiPathTemplate(relFile: string): string {\n const segs = segmentsOf(relFile)\n const pagesIdx = segs.indexOf('pages')\n const rest = segs.slice(pagesIdx + 1) // api/...\n const parts: string[] = []\n for (let i = 0; i < rest.length; i++) {\n let seg = rest[i]!\n if (i === rest.length - 1) {\n seg = seg.replace(/\\.(?:js|jsx|mjs|cjs|ts|tsx)$/, '')\n if (seg === 'index') continue\n }\n const mapped = nextSegment(seg)\n if (mapped !== null) parts.push(mapped)\n }\n return '/' + parts.join('/')\n}\n\n// The exported HTTP-method handler names in an app-router `route.*` file:\n// `export async function GET() {}` / `export const POST = …`. Each is one route.\nfunction nextAppMethods(root: Parser.SyntaxNode): { method: string; line: number }[] {\n const out: { method: string; line: number }[] = []\n walk(root, (node) => {\n if (node.type !== 'export_statement') return\n const decl = node.childForFieldName('declaration')\n if (!decl) return\n const line = node.startPosition.row + 1\n if (decl.type === 'function_declaration') {\n const name = decl.childForFieldName('name')?.text\n if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line })\n return\n }\n if (decl.type === 'lexical_declaration' || decl.type === 'variable_declaration') {\n for (let i = 0; i < decl.namedChildCount; i++) {\n const d = decl.namedChild(i)\n if (d?.type !== 'variable_declarator') continue\n const name = d.childForFieldName('name')?.text\n if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line })\n }\n }\n })\n return out\n}\n\nfunction nextRoutesFromFile(\n source: string,\n relFile: string,\n parser: Parser,\n): ExtractedRoute[] {\n if (isNextAppRouteFile(relFile)) {\n const tree = parseSource(parser, source)\n const template = nextAppPathTemplate(relFile)\n return nextAppMethods(tree.rootNode).map(({ method, line }) => ({\n method,\n pathTemplate: canonicalizeTemplate(template),\n line,\n framework: 'next',\n }))\n }\n if (isNextPagesApiFile(relFile)) {\n // A pages API handler is the module's default export and serves every\n // method — recorded as a single method-agnostic route.\n return [\n {\n method: 'ALL',\n pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),\n line: 1,\n framework: 'next',\n },\n ]\n }\n return []\n}\n\n// ── Python / FastAPI decorator routes ───────────────────────────────────────\n\n// The interior text of a Python string literal, stripped of quotes. Returns\n// null for an f-string carrying interpolation (a route path is a static\n// literal); returns '' for an empty string (no `string_content` child).\nfunction pyStaticStringText(node: Parser.SyntaxNode): string | null {\n if (node.type !== 'string') return null\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child?.type === 'interpolation') return null // f-string — not a static path\n if (child?.type === 'string_content') return child.text\n }\n return ''\n}\n\n// Read a keyword argument whose value is a list of string literals:\n// `methods=['GET','POST']` → ['GET','POST']. Anything else → [].\nfunction keywordArrayStrings(argsNode: Parser.SyntaxNode, key: string): string[] {\n for (let i = 0; i < argsNode.namedChildCount; i++) {\n const arg = argsNode.namedChild(i)\n if (arg?.type !== 'keyword_argument') continue\n if (arg.childForFieldName('name')?.text !== key) continue\n const val = arg.childForFieldName('value')\n if (!val || val.type !== 'list') return []\n const out: string[] = []\n for (let j = 0; j < val.namedChildCount; j++) {\n const el = val.namedChild(j)\n if (el?.type === 'string') {\n const s = pyStaticStringText(el)\n if (s) out.push(s)\n }\n }\n return out\n }\n return []\n}\n\n// Map an in-file router/blueprint variable to its declared literal prefix:\n// FastAPI `router = APIRouter(prefix='/items')` → { router: '/items' }, Flask\n// `bp = Blueprint('x', __name__, url_prefix='/api')` → { bp: '/api' }. Handles\n// bare and dotted constructors (`fastapi.APIRouter(...)`), the prefix kwarg in\n// any position. A prefix built from a config symbol or an f-string\n// (`prefix=settings.API_V1_STR`) stays unmapped — the router contributes no\n// prefix rather than a guessed one, and the leaf-relative path is kept honestly.\nfunction collectPythonRouterPrefixes(root: Parser.SyntaxNode): Map<string, string> {\n const prefixes = new Map<string, string>()\n walk(root, (node) => {\n if (node.type !== 'assignment') return\n const right = node.childForFieldName('right')\n if (!right || right.type !== 'call') return\n const fn = right.childForFieldName('function')\n if (!fn) return\n const ctor = fn.type === 'attribute' ? fn.childForFieldName('attribute')?.text : fn.text\n // FastAPI's APIRouter names its mount `prefix`; Flask's Blueprint names it\n // `url_prefix`. Any other constructor contributes no prefix.\n const prefixKey = ctor === 'APIRouter' ? 'prefix' : ctor === 'Blueprint' ? 'url_prefix' : null\n if (!prefixKey) return\n const left = node.childForFieldName('left')\n if (!left || left.type !== 'identifier') return\n const args = right.childForFieldName('arguments')\n if (!args) return\n for (let i = 0; i < args.namedChildCount; i++) {\n const arg = args.namedChild(i)\n if (arg?.type !== 'keyword_argument') continue\n if (arg.childForFieldName('name')?.text !== prefixKey) continue\n const val = arg.childForFieldName('value')\n const p = val ? pyStaticStringText(val) : null\n if (p !== null) prefixes.set(left.text, p)\n }\n })\n return prefixes\n}\n\n// Module-level string constants, for resolving a mount prefix given as a symbol\n// (`prefix=API_V1_STR` or `prefix=settings.API_V1_STR`) rather than a literal.\nfunction collectStringConstants(root: Parser.SyntaxNode): Map<string, string> {\n const consts = new Map<string, string>()\n walk(root, (node) => {\n if (node.type !== 'assignment') return\n const left = node.childForFieldName('left')\n const right = node.childForFieldName('right')\n if (left?.type !== 'identifier' || right?.type !== 'string') return\n const v = pyStaticStringText(right)\n if (v !== null) consts.set(left.text, v)\n })\n return consts\n}\n\n// Resolve a mount-prefix argument: a literal string, a bare constant\n// (`API_V1_STR`), or an attribute (`settings.API_V1_STR`) whose trailing name is\n// a known in-file constant. Anything else → null (unmapped, kept honest).\nfunction resolvePrefixArg(node: Parser.SyntaxNode | null, consts: Map<string, string>): string | null {\n if (!node) return null\n if (node.type === 'string') return pyStaticStringText(node)\n if (node.type === 'identifier') return consts.get(node.text) ?? null\n if (node.type === 'attribute') {\n const attr = node.childForFieldName('attribute')?.text\n return attr ? (consts.get(attr) ?? null) : null\n }\n return null\n}\n\n// In-file mounts — `app.include_router(<router>, prefix=<p>)` (FastAPI) and\n// `app.register_blueprint(<bp>, url_prefix=<p>)` (Flask). Maps the mounted\n// router/blueprint variable to its resolved mount prefix, so a route on that\n// router carries the mounted path. A cross-file mount (the router imported from\n// another module) and nested `include_router` chains are a follow-on — the\n// one-level prefix is composed, honestly.\nfunction collectMountPrefixes(\n root: Parser.SyntaxNode,\n consts: Map<string, string>,\n): Map<string, string> {\n const mounts = new Map<string, string>()\n walk(root, (node) => {\n if (node.type !== 'call') return\n const fn = node.childForFieldName('function')\n if (fn?.type !== 'attribute') return\n const method = fn.childForFieldName('attribute')?.text\n const prefixKey =\n method === 'include_router' ? 'prefix' : method === 'register_blueprint' ? 'url_prefix' : null\n if (!prefixKey) return\n const args = node.childForFieldName('arguments')\n const first = args?.namedChild(0)\n if (first?.type !== 'identifier') return // only a bare in-file router var\n let prefix: string | null = null\n for (let i = 0; i < (args?.namedChildCount ?? 0); i++) {\n const a = args!.namedChild(i)\n if (a?.type !== 'keyword_argument') continue\n if (a.childForFieldName('name')?.text !== prefixKey) continue\n prefix = resolvePrefixArg(a.childForFieldName('value'), consts)\n }\n if (prefix !== null && prefix.length > 0) mounts.set(first.text, prefix)\n })\n return mounts\n}\n\n// The decorator forms `<router>.<verb>('/path')` (FastAPI + Flask 2.0 shortcuts),\n// FastAPI's `<router>.api_route('/path', methods=[…])`, and Flask's\n// `<router>.route('/path', methods=[…])` (default GET). `<router>` is the app, an\n// APIRouter, or a Blueprint; the path is the first positional string, read off\n// the call's argument list (so a multi-line decorator still lands). A route's\n// full path composes the in-file mount prefix (`app.include_router(r,\n// prefix='/api/v1')`, resolving a config constant) + the router's own\n// `APIRouter(prefix='/x')` / `Blueprint(url_prefix='/x')` + the decorator path.\n// The declared template keeps its `{id}` params verbatim so an OBSERVED server\n// span's `http.route` lands on the same node.\n//\n// Out of scope, deferred like express `app.use('/api', router)`: cross-file /\n// nested mounting. The one-level, in-file prefix is composed as-is.\nexport function pythonRoutesFromSource(\n source: string,\n parser: Parser,\n framework: string,\n): ExtractedRoute[] {\n const tree = parseSource(parser, source)\n const prefixes = collectPythonRouterPrefixes(tree.rootNode)\n const consts = collectStringConstants(tree.rootNode)\n const mounts = collectMountPrefixes(tree.rootNode, consts)\n const out: ExtractedRoute[] = []\n walk(tree.rootNode, (node) => {\n if (node.type !== 'decorator') return\n const call = node.namedChild(0)\n if (!call || call.type !== 'call') return\n const fn = call.childForFieldName('function')\n if (!fn || fn.type !== 'attribute') return\n const method = fn.childForFieldName('attribute')?.text?.toLowerCase()\n if (!method) return\n const isVerb = FASTAPI_METHODS.has(method)\n const isFlaskRoute = method === 'route' // Flask `.route(...)`, defaults to GET\n const isApiRoute = method === 'api_route' // FastAPI multi-method\n if (!isVerb && !isFlaskRoute && !isApiRoute) return\n\n const args = call.childForFieldName('arguments')\n const first = args?.namedChild(0)\n if (!first || first.type !== 'string') return\n const rawPath = pyStaticStringText(first)\n if (rawPath === null || !rawPath.startsWith('/')) return\n\n const obj = fn.childForFieldName('object')?.text\n const routerPrefix = obj ? (prefixes.get(obj) ?? '') : ''\n const mountPrefix = obj ? (mounts.get(obj) ?? '') : ''\n const pathTemplate = canonicalizeTemplate(mountPrefix + routerPrefix + rawPath)\n const line = node.startPosition.row + 1\n\n if (isVerb) {\n out.push({ method: method.toUpperCase(), pathTemplate, line, framework })\n return\n }\n // `.route` (Flask, default GET) / `.api_route` (FastAPI) — one route per method.\n const methods = keywordArrayStrings(args!, 'methods')\n const list = methods.length > 0 ? methods : isFlaskRoute ? ['GET'] : ['ALL']\n for (const m of list) {\n out.push({ method: m === 'ALL' ? 'ALL' : m.toUpperCase(), pathTemplate, line, framework })\n }\n })\n return out\n}\n\n// Back-compat alias — FastAPI is the framework-labelled specialisation.\nexport function fastapiRoutesFromSource(source: string, parser: Parser): ExtractedRoute[] {\n return pythonRoutesFromSource(source, parser, 'fastapi')\n}\n\n// Django URLconf routes — `urlpatterns = [path('orders/<int:pk>/', view), …]`.\n// Django dispatches HTTP methods inside the view, not at the URLconf, so a route\n// is method-agnostic (`ALL`). Only the modern `path()` converter form is\n// recognised; an `include(...)` mount (cross-file, the Python analog of express\n// `app.use`) and legacy `re_path` regex patterns are a follow-on. The `<int:pk>`\n// converter param is kept verbatim and collapses to `:param` at match time.\nexport function djangoRoutesFromSource(source: string, parser: Parser): ExtractedRoute[] {\n const tree = parseSource(parser, source)\n const out: ExtractedRoute[] = []\n walk(tree.rootNode, (node) => {\n if (node.type !== 'assignment') return\n if (node.childForFieldName('left')?.text !== 'urlpatterns') return\n const list = node.childForFieldName('right')\n if (!list || list.type !== 'list') return\n for (let i = 0; i < list.namedChildCount; i++) {\n const el = list.namedChild(i)\n if (el?.type !== 'call') continue\n if (el.childForFieldName('function')?.text !== 'path') continue // path() only\n const args = el.childForFieldName('arguments')\n const first = args?.namedChild(0)\n if (first?.type !== 'string') continue\n const raw = pyStaticStringText(first)\n if (raw === null) continue\n // Skip an `include(...)` mount — cross-file, deferred.\n const second = args?.namedChild(1)\n if (second?.type === 'call' && second.childForFieldName('function')?.text === 'include') continue\n out.push({\n method: 'ALL',\n pathTemplate: canonicalizeTemplate(raw),\n line: el.startPosition.row + 1,\n framework: 'django',\n })\n }\n })\n return out\n}\n\n// ── Rails config/routes.rb routes (ADR-173) ─────────────────────────────────\n//\n// Reads a Rails router table out of `config/routes.rb` and emits a RouteNode per\n// (HTTP method, path template). Explicit verb routes and `root` are read from\n// the source directly; `resources`/`resource` fan out through Rails' resourceful\n// expansion, `namespace`/`scope` contribute a path and/or a controller-module\n// prefix, `member`/`collection` add routes to the enclosing resource, and one\n// level of nesting stamps the parent's `:<singular>_id` param onto the child.\n//\n// The declared template keeps Rails' `:id` form verbatim: the action_pack OTel\n// instrumentation (Rails ≥ 7.1) sets `http.route` to exactly that string with\n// the trailing `(.:format)` stripped, and `normalizePathTemplate` already\n// collapses every `:word` segment to `:param`, so a route-grain server span\n// fuses onto this node with no normalisation. What can't be resolved statically\n// — routes.rb metaprogramming, `draw(:file)` split files, mounted engines,\n// `constraints`, nesting past one level — is left out rather than guessed; those\n// routes still appear at runtime, as an honest observed-but-not-declared\n// divergence rather than a wrong RouteNode.\n\nconst RAILS_VERBS = new Set(['get', 'post', 'put', 'patch', 'delete', 'options', 'head'])\n\n// The member routes a `resources` (plural) declaration fans out to. `update`\n// lands on both PATCH and PUT — Rails registers both and a runtime request can\n// carry either. `new`/`edit` are the form-rendering GETs.\ninterface ResourceRow {\n action: string\n methods: string[]\n suffix: string\n}\nconst RAILS_PLURAL_ROWS: ResourceRow[] = [\n { action: 'index', methods: ['GET'], suffix: '' },\n { action: 'new', methods: ['GET'], suffix: '/new' },\n { action: 'create', methods: ['POST'], suffix: '' },\n { action: 'show', methods: ['GET'], suffix: '/:id' },\n { action: 'edit', methods: ['GET'], suffix: '/:id/edit' },\n { action: 'update', methods: ['PATCH', 'PUT'], suffix: '/:id' },\n { action: 'destroy', methods: ['DELETE'], suffix: '/:id' },\n]\n// The singular `resource` set — no index and no `:id` segment (there is only ever\n// one), and the controller is pluralised (`resource :profile` → `profiles`).\nconst RAILS_SINGULAR_ROWS: ResourceRow[] = [\n { action: 'new', methods: ['GET'], suffix: '/new' },\n { action: 'create', methods: ['POST'], suffix: '' },\n { action: 'show', methods: ['GET'], suffix: '' },\n { action: 'edit', methods: ['GET'], suffix: '/edit' },\n { action: 'update', methods: ['PATCH', 'PUT'], suffix: '' },\n { action: 'destroy', methods: ['DELETE'], suffix: '' },\n]\n\n// Minimal English inflection — enough for the common resource names. Rails uses\n// ActiveSupport's fuller irregular table; a resource's own path segments are the\n// literal symbol it declares, so only the derived controller name and the nested\n// `:<singular>_id` param depend on this — and the param name never affects fusion\n// (`normalizePathTemplate` collapses any `:word` to `:param`). Irregular plurals\n// (person/people) are a later refinement, not a fusion risk.\nfunction railsPluralize(word: string): string {\n if (/[^aeiou]y$/i.test(word)) return word.slice(0, -1) + 'ies'\n if (/(s|x|z|ch|sh)$/i.test(word)) return word + 'es'\n return word + 's'\n}\nfunction railsSingularize(word: string): string {\n if (/ies$/i.test(word)) return word.slice(0, -3) + 'y'\n if (/(x|z|ch|sh)es$/i.test(word)) return word.slice(0, -2)\n if (/s$/i.test(word) && !/ss$/i.test(word)) return word.slice(0, -1)\n return word\n}\n\n// The interior text of a Ruby string literal, or a symbol's name without its\n// leading colon (`:orders` → 'orders'). Returns null for an interpolated string\n// (it carries a child that isn't `string_content`) — a route path or handler is\n// a static literal.\nfunction rubyLiteral(node: Parser.SyntaxNode | null | undefined): string | null {\n if (!node) return null\n if (node.type === 'simple_symbol') return node.text.replace(/^:/, '')\n if (node.type === 'string') {\n let text = ''\n let sawContent = false\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (!child) continue\n if (child.type === 'string_content' || child.type === 'escape_sequence') {\n text += child.text\n sawContent = true\n } else {\n return null // interpolation or anything non-literal\n }\n }\n return sawContent ? text : '' // an empty string literal has no content child\n }\n return null\n}\n\nfunction rubyArgs(call: Parser.SyntaxNode): Parser.SyntaxNode | null {\n return call.childForFieldName('arguments')\n}\n\n// The positional (non-`pair`) arguments of a call, in order.\nfunction rubyPositional(args: Parser.SyntaxNode | null): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n if (!args) return out\n for (let i = 0; i < args.namedChildCount; i++) {\n const c = args.namedChild(i)\n if (c && c.type !== 'pair') out.push(c)\n }\n return out\n}\n\n// The value node of a keyword argument `key: …` — a `pair` whose key is a\n// `hash_key_symbol` with the given name.\nfunction rubyKwarg(args: Parser.SyntaxNode | null, key: string): Parser.SyntaxNode | null {\n if (!args) return null\n for (let i = 0; i < args.namedChildCount; i++) {\n const pair = args.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n if (k?.type === 'hash_key_symbol' && k.text === key) return pair.childForFieldName('value')\n }\n return null\n}\n\nfunction rubyKwargText(args: Parser.SyntaxNode | null, key: string): string | null {\n return rubyLiteral(rubyKwarg(args, key))\n}\n\n// The hash-rocket verb shorthand `get 'path' => 'controller#action'` — a `pair`\n// whose key is a string literal. Returns the path and the handler target.\nfunction rubyRocketRoute(\n args: Parser.SyntaxNode | null,\n): { path: string; target: string | null } | null {\n if (!args) return null\n for (let i = 0; i < args.namedChildCount; i++) {\n const pair = args.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n if (k?.type !== 'string') continue\n const path = rubyLiteral(k)\n if (path === null) continue\n return { path, target: rubyLiteral(pair.childForFieldName('value')) }\n }\n return null\n}\n\n// The statement `call` nodes directly inside a `do … end` block. tree-sitter-ruby\n// wraps multiple statements in a `body_statement`; fall back to the block's own\n// direct children for the single-statement shape.\nfunction rubyBlockCalls(block: Parser.SyntaxNode | null): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n if (!block) return out\n const pushCalls = (n: Parser.SyntaxNode): void => {\n for (let i = 0; i < n.namedChildCount; i++) {\n const c = n.namedChild(i)\n if (c?.type === 'call') out.push(c)\n }\n }\n let sawBody = false\n for (let i = 0; i < block.namedChildCount; i++) {\n const c = block.namedChild(i)\n if (c?.type === 'body_statement') {\n pushCalls(c)\n sawBody = true\n }\n }\n if (!sawBody) pushCalls(block)\n return out\n}\n\n// Join an accumulated prefix with a leaf path, tolerating a leading slash on the\n// leaf. `canonicalizeTemplate` finishes the job (leading slash, no trailing).\nfunction railsJoin(prefix: string, leaf: string): string {\n return prefix + '/' + leaf.replace(/^\\/+/, '')\n}\n\nfunction railsEmit(\n out: ExtractedRoute[],\n method: string,\n rawPath: string,\n controller: string | null,\n line: number,\n): void {\n out.push({\n method,\n pathTemplate: canonicalizeTemplate(rawPath === '' ? '/' : rawPath),\n line,\n framework: 'rails',\n ...(controller ? { controller } : {}),\n })\n}\n\n// `only:` / `except:` restrict a resource's emitted action set. A value is a\n// single symbol (`except: :destroy`) or an array (`only: [:index, :show]`).\nfunction railsActionFilter(args: Parser.SyntaxNode | null): (action: string) => boolean {\n const collect = (key: string): Set<string> | null => {\n const val = rubyKwarg(args, key)\n if (!val) return null\n const set = new Set<string>()\n if (val.type === 'array') {\n for (let i = 0; i < val.namedChildCount; i++) {\n const name = rubyLiteral(val.namedChild(i))\n if (name) set.add(name)\n }\n } else {\n const name = rubyLiteral(val)\n if (name) set.add(name)\n }\n return set\n }\n const only = collect('only')\n const except = collect('except')\n return (action: string) =>\n (only ? only.has(action) : true) && (except ? !except.has(action) : true)\n}\n\n// A bare `get :recent` verb inside a resource block — emits one route relative to\n// `pathBase` (a member path carries `:id`, a collection path does not).\nfunction railsSingleVerb(\n verb: Parser.SyntaxNode,\n pathBase: string,\n controller: string,\n out: ExtractedRoute[],\n): void {\n const vm = verb.childForFieldName('method')?.text\n if (!vm || !RAILS_VERBS.has(vm)) return\n const action = rubyLiteral(rubyPositional(rubyArgs(verb))[0])\n if (action === null) return\n railsEmit(out, vm.toUpperCase(), railsJoin(pathBase, action), `${controller}#${action}`, verb.startPosition.row + 1)\n}\n\n// The `member do … end` / `collection do … end` verb lists inside a resource.\nfunction railsBlockVerbs(\n call: Parser.SyntaxNode,\n pathBase: string,\n controller: string,\n out: ExtractedRoute[],\n): void {\n for (const verb of rubyBlockCalls(call.childForFieldName('block'))) {\n railsSingleVerb(verb, pathBase, controller, out)\n }\n}\n\ninterface RailsCtx {\n path: string // accumulated path prefix, '' at the draw root, no trailing slash\n module: string // accumulated controller-module prefix, '' or e.g. 'admin/'\n depth: number // resource-nesting depth; 0 at the top level\n}\n\n// An explicit verb route: `get '/x', to: 'c#a'`, the hash-rocket `get 'x' => 'c#a'`,\n// or a bare `get 'about'` (handler inferred `about#index`-style).\nfunction railsVerbRoute(\n call: Parser.SyntaxNode,\n method: string,\n ctx: RailsCtx,\n out: ExtractedRoute[],\n line: number,\n): void {\n const args = rubyArgs(call)\n let rawPath: string | null\n let target: string | null\n const rocket = rubyRocketRoute(args)\n if (rocket) {\n rawPath = rocket.path\n target = rocket.target\n } else {\n rawPath = rubyLiteral(rubyPositional(args)[0])\n target = rubyKwargText(args, 'to')\n }\n if (rawPath === null) return\n if (target === null) target = railsInferHandler(rawPath)\n railsEmit(out, method, railsJoin(ctx.path, rawPath), target ? ctx.module + target : null, line)\n}\n\n// Infer a `controller#action` for a bare verb route with no `to:`: a single\n// segment maps to `<segment>#index`, a multi-segment path splits the last off as\n// the action. Handler metadata only — the path template is what fuses.\nfunction railsInferHandler(rawPath: string): string {\n const segs = rawPath\n .replace(/^\\/+|\\/+$/g, '')\n .split('/')\n .filter((s) => s.length > 0 && !s.startsWith(':'))\n if (segs.length === 0) return 'index'\n if (segs.length === 1) return `${segs[0]}#index`\n return `${segs.slice(0, -1).join('/')}#${segs[segs.length - 1]}`\n}\n\n// `resources :orders` (plural) / `resource :profile` (singular) — Rails'\n// resourceful expansion, plus the resource's own `member`/`collection`/nested\n// block. One level of nesting is composed; deeper nesting is deferred.\nfunction railsResource(\n call: Parser.SyntaxNode,\n ctx: RailsCtx,\n out: ExtractedRoute[],\n plural: boolean,\n): void {\n if (ctx.depth >= 2) return // one level of nesting only (ADR-173)\n const args = rubyArgs(call)\n const name = rubyLiteral(rubyPositional(args)[0])\n if (!name) return\n const base = `${ctx.path}/${name}`\n const controller = ctx.module + (plural ? name : railsPluralize(name))\n const allow = railsActionFilter(args)\n const line = call.startPosition.row + 1\n\n for (const row of plural ? RAILS_PLURAL_ROWS : RAILS_SINGULAR_ROWS) {\n if (!allow(row.action)) continue\n for (const m of row.methods) railsEmit(out, m, base + row.suffix, `${controller}#${row.action}`, line)\n }\n\n const block = call.childForFieldName('block')\n if (!block) return\n const memberBase = plural ? `${base}/:id` : base // a singular member has no :id\n const nestedPath = plural ? `${base}/:${railsSingularize(name)}_id` : base\n for (const child of rubyBlockCalls(block)) {\n const cm = child.childForFieldName('method')?.text\n if (cm === 'member') railsBlockVerbs(child, memberBase, controller, out)\n else if (cm === 'collection') railsBlockVerbs(child, base, controller, out)\n else if (cm && RAILS_VERBS.has(cm)) railsSingleVerb(child, base, controller, out)\n else if (cm === 'resources' || cm === 'resource' || cm === 'namespace' || cm === 'scope') {\n railsProcess([child], { path: nestedPath, module: ctx.module, depth: ctx.depth + 1 }, out)\n }\n // concerns, member-less helpers, etc. — deferred\n }\n}\n\n// Walk a list of routes.rb statements in a routing context, dispatching each\n// `call` on its method name.\nfunction railsProcess(statements: Parser.SyntaxNode[], ctx: RailsCtx, out: ExtractedRoute[]): void {\n for (const call of statements) {\n const method = call.childForFieldName('method')?.text\n if (!method) continue\n const args = rubyArgs(call)\n const line = call.startPosition.row + 1\n\n if (method === 'root') {\n const target = rubyKwargText(args, 'to') ?? rubyLiteral(rubyPositional(args)[0])\n railsEmit(out, 'GET', ctx.path || '/', target ? ctx.module + target : null, line)\n continue\n }\n if (RAILS_VERBS.has(method)) {\n railsVerbRoute(call, method.toUpperCase(), ctx, out, line)\n continue\n }\n if (method === 'resources' || method === 'resource') {\n railsResource(call, ctx, out, method === 'resources')\n continue\n }\n if (method === 'namespace') {\n const name = rubyLiteral(rubyPositional(args)[0])\n if (!name) continue\n railsProcess(rubyBlockCalls(call.childForFieldName('block')), {\n path: `${ctx.path}/${name}`,\n module: `${ctx.module}${name}/`,\n depth: ctx.depth,\n }, out)\n continue\n }\n if (method === 'scope') {\n // A positional string/symbol scopes the path; `path:`/`module:` scope each\n // axis explicitly. A `scope module: 'admin'` shifts only the controller\n // module, leaving the URL unchanged — the case only the handler shows.\n const scopePath = rubyKwargText(args, 'path') ?? rubyLiteral(rubyPositional(args)[0])\n const scopeModule = rubyKwargText(args, 'module')\n railsProcess(rubyBlockCalls(call.childForFieldName('block')), {\n path: scopePath ? railsJoin(ctx.path, scopePath) : ctx.path,\n module: scopeModule ? `${ctx.module}${scopeModule}/` : ctx.module,\n depth: ctx.depth,\n }, out)\n continue\n }\n // mount, constraints, `%i[…].each { resources … }`, concerns — deferred.\n }\n}\n\n// Read `config/routes.rb`: find the `Rails.application.routes.draw do … end`\n// block and expand its body. `draw(:file)` split-route files carry no block and\n// are deferred.\nexport function railsRoutesFromSource(source: string, parser: Parser): ExtractedRoute[] {\n const tree = parseSource(parser, source)\n const out: ExtractedRoute[] = []\n walk(tree.rootNode, (node) => {\n if (node.type !== 'call') return\n if (node.childForFieldName('method')?.text !== 'draw') return\n const block = node.childForFieldName('block')\n if (!block) return\n railsProcess(rubyBlockCalls(block), { path: '', module: '', depth: 0 }, out)\n })\n return out\n}\n\n// ── Laravel routes/web.php + routes/api.php (ADR-177) ───────────────────────\n//\n// Reads a Laravel router table out of `routes/web.php` and `routes/api.php` and\n// emits a RouteNode per (HTTP method, path template). Explicit verb routes\n// (`Route::get('/orders/{id}', …)`) are read from the source directly;\n// `Route::resource`/`apiResource` fan out through Laravel's resourceful\n// convention; `Route::prefix('admin')->group(…)` (and the `middleware`,\n// `controller`, `name` group forms) compose a path prefix across nesting; and\n// every route in `routes/api.php` gets the automatic `/api` prefix the framework\n// adds (it isn't in the source), passed in as the base prefix.\n//\n// The declared template keeps Laravel's `{id}` form verbatim: Laravel's\n// OpenTelemetry auto-instrumentation sets `http.route` to exactly the templated\n// URI (`orders/{id}` for a request to `/orders/42`), and `normalizePathTemplate`\n// already drops the leading slash and collapses every `{…}` segment to `:param`,\n// so a route-grain server span fuses onto this node with no ingest change.\n//\n// Deferred (ADR-177): Symfony entirely — its OTel emits the route NAME, not the\n// path, so it needs a distinct extractor + ingest name-join; `config/routes.yaml`;\n// controller-array handler resolution beyond the template; `match`, `redirect`,\n// `view`, `fallback`, singleton and domain routing; and resource `->only`/`->except`\n// filtering. Those routes surface as observed-but-not-declared divergence rather\n// than a fabricated node.\n\nconst LARAVEL_VERBS = new Set(['get', 'post', 'put', 'patch', 'delete', 'options'])\n\n// The rows a `Route::resource` fans out to — Laravel's convention, reimplemented\n// (it isn't in the source text). `{param}` is the singularized resource name;\n// `update` lands on both PUT and PATCH, since Laravel registers both and a\n// runtime request can carry either. `apiResource` drops the two form-rendering\n// GETs (`create`, `edit`).\ninterface LaravelResourceRow {\n action: string\n methods: string[]\n suffix: string\n}\nconst LARAVEL_RESOURCE_ROWS: LaravelResourceRow[] = [\n { action: 'index', methods: ['GET'], suffix: '' },\n { action: 'create', methods: ['GET'], suffix: '/create' },\n { action: 'store', methods: ['POST'], suffix: '' },\n { action: 'show', methods: ['GET'], suffix: '/{param}' },\n { action: 'edit', methods: ['GET'], suffix: '/{param}/edit' },\n { action: 'update', methods: ['PUT', 'PATCH'], suffix: '/{param}' },\n { action: 'destroy', methods: ['DELETE'], suffix: '/{param}' },\n]\nconst LARAVEL_API_RESOURCE_SKIP = new Set(['create', 'edit'])\n\n// A deliberately simple singularizer — strip one trailing `s`. The resource param\n// is DISPLAY ONLY: `normalizePathTemplate` collapses every `{…}` segment to\n// `:param` before fusion, so `{photo}` vs `{photos}` never changes the matching\n// key (ADR-177). Reproducing Laravel's fuller inflection isn't a fusion\n// requirement, so the cheap rule stands.\nfunction laravelSingularize(word: string): string {\n return word.endsWith('s') ? word.slice(0, -1) : word\n}\n\n// The interior text of a PHP single-quoted `string` or double-quoted\n// `encapsed_string`, or null when it carries interpolation (a route path is a\n// static literal). An empty literal returns ''.\nfunction phpStaticString(node: Parser.SyntaxNode | null | undefined): string | null {\n if (!node) return null\n if (node.type !== 'string' && node.type !== 'encapsed_string') return null\n let text = ''\n let sawContent = false\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (!child) continue\n if (child.type === 'string_content') {\n text += child.text\n sawContent = true\n } else {\n return null // interpolation / escape / anything non-literal\n }\n }\n return sawContent ? text : ''\n}\n\n// The value nodes of a call's positional arguments. tree-sitter-php wraps each in\n// an `argument`; the value is its last named child (a PHP-8 named argument\n// `name: value` carries a `name` field that precedes the value).\nfunction phpArgumentValues(argsNode: Parser.SyntaxNode | null | undefined): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n if (!argsNode) return out\n for (let i = 0; i < argsNode.namedChildCount; i++) {\n const arg = argsNode.namedChild(i)\n if (!arg || arg.type !== 'argument') continue\n const val = arg.namedChild(arg.namedChildCount - 1)\n if (val) out.push(val)\n }\n return out\n}\n\n// The first positional argument as a static string (the route path / resource\n// name), or null.\nfunction phpFirstString(argsNode: Parser.SyntaxNode | null | undefined): string | null {\n const vals = phpArgumentValues(argsNode)\n return vals.length > 0 ? phpStaticString(vals[0]) : null\n}\n\n// Join path parts into a canonical `/`-leading, no-trailing-slash template,\n// trimming any embedded leading/trailing slashes on each part (Laravel's own\n// prefix-composition discipline). An empty set of parts is the root, `/`.\nfunction laravelJoinPath(...parts: string[]): string {\n const segs: string[] = []\n for (const part of parts) {\n for (const s of part.split('/')) {\n if (s.length > 0) segs.push(s)\n }\n }\n return '/' + segs.join('/')\n}\n\ninterface LaravelSeg {\n method: string\n args: Parser.SyntaxNode | null\n node: Parser.SyntaxNode\n}\n\n// Flatten a `Route::…->…->…` fluent chain into its ordered (method, args)\n// segments, innermost first. `Route::prefix('admin')->group(fn)` →\n// [prefix, group]; a bare `Route::get('/x')` → [get]. Returns null when the chain\n// doesn't root at the `Route` facade (bare, leading-backslash, or the\n// fully-qualified `Illuminate\\Support\\Facades\\Route`).\nfunction laravelUnrollChain(node: Parser.SyntaxNode): LaravelSeg[] | null {\n const segs: LaravelSeg[] = []\n let cur: Parser.SyntaxNode | null = node\n while (cur && cur.type === 'member_call_expression') {\n const name = cur.childForFieldName('name')?.text\n if (!name) return null\n segs.unshift({ method: name, args: cur.childForFieldName('arguments'), node: cur })\n cur = cur.childForFieldName('object')\n }\n if (!cur || cur.type !== 'scoped_call_expression') return null\n const method = cur.childForFieldName('name')?.text\n if (!method) return null\n segs.unshift({ method, args: cur.childForFieldName('arguments'), node: cur })\n const scopeText = cur.childForFieldName('scope')?.text ?? ''\n const rooted = scopeText === 'Route' || scopeText.endsWith('\\\\Route')\n return rooted ? segs : null\n}\n\n// The closure a `->group(…)` call carries — a `function () { … }`\n// (`anonymous_function_creation_expression`) or a `fn () => …` (`arrow_function`).\nfunction laravelGroupClosure(argsNode: Parser.SyntaxNode | null): Parser.SyntaxNode | null {\n for (const v of phpArgumentValues(argsNode)) {\n if (v.type === 'anonymous_function_creation_expression' || v.type === 'arrow_function') return v\n }\n return null\n}\n\n// The route-call statements inside a group closure body — a `{ … }` block of\n// `expression_statement`s, or a single-expression arrow body.\nfunction laravelClosureCalls(closure: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n const push = (n: Parser.SyntaxNode | null): void => {\n if (n && (n.type === 'member_call_expression' || n.type === 'scoped_call_expression')) out.push(n)\n }\n const body = closure.childForFieldName('body')\n if (!body) return out\n if (closure.type === 'arrow_function') {\n push(body)\n return out\n }\n for (let i = 0; i < body.namedChildCount; i++) {\n const stmt = body.namedChild(i)\n if (stmt?.type === 'expression_statement') push(stmt.namedChild(0))\n }\n return out\n}\n\nfunction laravelEmit(out: ExtractedRoute[], method: string, rawPath: string, line: number): void {\n // An optional param `{id?}` drops its `?` so the stored template stays clean —\n // `canonicalizeTemplate` reads a literal `?` as a query-string delimiter and\n // would truncate `/users/{id?}` to `/users/{id`. Fusion is unaffected either\n // way (`normalizePathTemplate` collapses every `{…}` segment to `:param`); this\n // only keeps the declared template readable and honest.\n const cleaned = rawPath.replace(/\\{([^}?]+)\\?\\}/g, '{$1}')\n out.push({\n method,\n pathTemplate: canonicalizeTemplate(cleaned === '' ? '/' : cleaned),\n line,\n framework: 'laravel',\n })\n}\n\n// `Route::resource('photos', …)` / `apiResource` — Laravel's resourceful\n// expansion. A dotted name (`photos.comments`) composes into a nested path with\n// the parent param stamped on (`/photos/{photo}/comments/{comment}`), best-effort.\nfunction laravelResource(\n seg: LaravelSeg,\n prefix: string,\n out: ExtractedRoute[],\n api: boolean,\n): void {\n const name = phpFirstString(seg.args)\n if (name === null || name.length === 0) return\n const line = seg.node.startPosition.row + 1\n const parts = name.split('.')\n const leaf = parts[parts.length - 1]!\n let base = prefix\n for (let i = 0; i < parts.length - 1; i++) {\n base = laravelJoinPath(base, parts[i]!, `{${laravelSingularize(parts[i]!)}}`)\n }\n base = laravelJoinPath(base, leaf)\n const param = laravelSingularize(leaf)\n for (const row of LARAVEL_RESOURCE_ROWS) {\n if (api && LARAVEL_API_RESOURCE_SKIP.has(row.action)) continue\n const suffix = row.suffix.replace('{param}', `{${param}}`)\n const template = laravelJoinPath(base, suffix)\n for (const m of row.methods) laravelEmit(out, m, template, line)\n }\n}\n\n// Process one `Route::…` statement in a routing context (the accumulated path\n// prefix). A `->group(…)` composes its `prefix('x')` contributions onto the\n// prefix and recurses into the closure; a verb emits a route; `any` is a\n// method-agnostic `ALL`; `resource`/`apiResource` fan out. Per-route modifiers\n// chained after a verb (`->name(…)`, `->where(…)`, `->middleware(…)`) don't\n// change the template, so they fall out — the verb sits at the base of the chain.\nfunction laravelProcessCall(callNode: Parser.SyntaxNode, prefix: string, out: ExtractedRoute[]): void {\n const segs = laravelUnrollChain(callNode)\n if (!segs) return\n\n const groupSeg = segs.find((s) => s.method === 'group')\n if (groupSeg) {\n let composed = prefix\n for (const s of segs) {\n if (s === groupSeg) break\n if (s.method === 'prefix') {\n const p = phpFirstString(s.args)\n if (p !== null) composed = laravelJoinPath(composed, p)\n }\n }\n const closure = laravelGroupClosure(groupSeg.args)\n if (!closure) return\n for (const child of laravelClosureCalls(closure)) {\n laravelProcessCall(child, composed, out)\n }\n return\n }\n\n const base = segs[0]!\n const line = base.node.startPosition.row + 1\n if (LARAVEL_VERBS.has(base.method)) {\n const p = phpFirstString(base.args)\n if (p === null) return\n laravelEmit(out, base.method.toUpperCase(), laravelJoinPath(prefix, p), line)\n return\n }\n if (base.method === 'any') {\n const p = phpFirstString(base.args)\n if (p === null) return\n laravelEmit(out, 'ALL', laravelJoinPath(prefix, p), line)\n return\n }\n if (base.method === 'resource' || base.method === 'apiResource') {\n laravelResource(base, prefix, out, base.method === 'apiResource')\n }\n}\n\n// Read `routes/web.php` (no prefix) or `routes/api.php` (`basePrefix` = '/api',\n// the framework's automatic prefix, not in the source). Walk the top-level route\n// statements; groups nest and are recursed into explicitly.\nexport function laravelRoutesFromSource(\n source: string,\n parser: Parser,\n basePrefix = '',\n): ExtractedRoute[] {\n const tree = parseSource(parser, source)\n const out: ExtractedRoute[] = []\n const root = tree.rootNode\n for (let i = 0; i < root.namedChildCount; i++) {\n const stmt = root.namedChild(i)\n if (stmt?.type !== 'expression_statement') continue\n const call = stmt.namedChild(0)\n if (call && (call.type === 'member_call_expression' || call.type === 'scoped_call_expression')) {\n laravelProcessCall(call, basePrefix, out)\n }\n }\n return out\n}\n\n// ── Express cross-file mount-prefix composition (ADR-160) ───────────────────\n\n// Real Express apps mount a router under a prefix — `app.use('/api', router)` —\n// with the router and its routes defined in other files. The per-file scan above\n// captures each leaf path (`/tags`) without the prefix, so a production span for\n// `/api/tags` never fuses onto the static route. This whole-program pass composes\n// the prefix onto the routes it mounts, resolving the mounted router across files\n// through the same import graph `imports.ts` walks (the ADR-149 mechanism the\n// Mongoose cross-file pass established).\n//\n// The discipline is the ADR-149 discipline: a prefix that isn't a `/`-leading\n// string literal (a config symbol or computed expression), or a mounted router\n// that resolves to no file, leaves the leaf path un-prefixed rather than guessing.\n// A route at the wrong grain is an honest partial; a fabricated one is not.\n\n// One `<router>.use(<prefix>, <mounted>)` mount. `prefix` is '' when the router is\n// mounted at root (`app.use(router)`); `target` is the mounted binding's local\n// name, or null when the argument isn't a bare identifier (middleware, a computed\n// prefix followed by a router, a factory call — none of which we compose).\ninterface Mount {\n prefix: string\n target: string | null\n}\n\ninterface RouterVarInfo {\n declares: boolean // has a direct `<var>.get('/…')` route call\n mounts: Mount[] // the routers this var mounts, via `.use()`\n aliasOf?: string // `const r2 = r1` — r2 is r1 under another name\n}\n\ninterface RawBinding {\n local: string\n specifier: string\n sel: string // 'default' | 'namespace' | an export name\n}\n\ninterface FileRouterInfo {\n dir: string // absolute directory of the file, for import resolution\n routerVars: Map<string, RouterVarInfo>\n appVars: Set<string> // vars bound to `express()` — the recursion roots\n exportDefaultName: string | null // routerVars key the default export resolves to\n exportNamed: Map<string, string> // export name → routerVars key\n rawBindings: RawBinding[] // imports, before cross-file resolution\n importedRouters: Map<string, { file: string; sel: string }> // local → target file + export\n}\n\n// The named arguments of a call, comments skipped.\nfunction namedArgs(argsNode: Parser.SyntaxNode | null | undefined): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n if (!argsNode) return out\n for (let i = 0; i < argsNode.namedChildCount; i++) {\n const c = argsNode.namedChild(i)\n if (c && c.type !== 'comment') out.push(c)\n }\n return out\n}\n\n// Parse a `.use(...)` call into a Mount, or null when it mounts no resolvable\n// router. `.use('/api', router)` → prefix '/api'; `.use(router)` → prefix '';\n// `.use(cors())` / `.use(PREFIX, router)` / `.use(mw, router)` → null. A computed\n// (non-literal) prefix is deliberately dropped — we compose only a literal one.\nfunction parseUseMount(callNode: Parser.SyntaxNode): Mount | null {\n const args = namedArgs(callNode.childForFieldName('arguments'))\n if (args.length === 0) return null\n const first = args[0]!\n const firstStr =\n first.type === 'string' || first.type === 'template_string' ? staticStringText(first) : null\n if (firstStr !== null && firstStr.startsWith('/')) {\n const prefix = canonicalizeTemplate(firstStr)\n const second = args[1]\n const target = second && second.type === 'identifier' ? second.text : null\n return { prefix: prefix === '/' ? '' : prefix, target }\n }\n // A single bare identifier is a router mounted at root (`app.use(router)`).\n // Two args led by an identifier is a computed prefix or a middleware — not\n // something we can compose, so we leave it alone.\n if (args.length === 1 && first.type === 'identifier') return { prefix: '', target: first.text }\n return null\n}\n\n// Unwrap a router expression to its base and the `.use()` mounts chained onto it.\n// `Router().use(a).use('/x', b)` → base 'newRouter', mounts [a, /x→b]; `express()`\n// → base 'app'; a bare `router` identifier → base { alias: 'router' }. Returns\n// null when the expression isn't a router value.\nfunction unwrapRouterExpr(\n node: Parser.SyntaxNode,\n expressLocals: Set<string>,\n routerCtors: Set<string>,\n): { base: 'app' | 'newRouter' | { alias: string }; mounts: Mount[] } | null {\n if (node.type === 'identifier') return { base: { alias: node.text }, mounts: [] }\n if (node.type !== 'call_expression') return null\n const fn = node.childForFieldName('function')\n if (!fn) return null\n\n if (fn.type === 'member_expression') {\n const prop = fn.childForFieldName('property')?.text\n const obj = fn.childForFieldName('object')\n if (!prop || !obj) return null\n if (prop === 'use') {\n const inner = unwrapRouterExpr(obj, expressLocals, routerCtors)\n if (!inner) return null\n const mount = parseUseMount(node)\n return { base: inner.base, mounts: mount ? [...inner.mounts, mount] : inner.mounts }\n }\n // `express.Router()`\n if (prop === 'Router' && obj.type === 'identifier' && expressLocals.has(obj.text)) {\n return { base: 'newRouter', mounts: [] }\n }\n return null\n }\n\n if (fn.type === 'identifier') {\n if (expressLocals.has(fn.text)) return { base: 'app', mounts: [] } // express()\n if (routerCtors.has(fn.text)) return { base: 'newRouter', mounts: [] } // Router()\n }\n return null\n}\n\n// Import bindings + which locals name express() / the Router ctor. Both ESM\n// (`import express, { Router } from 'express'`) and CJS (`const { Router } =\n// require('express')`) forms.\nfunction collectExpressImports(root: Parser.SyntaxNode): {\n expressLocals: Set<string>\n routerCtors: Set<string>\n bindings: RawBinding[]\n} {\n const expressLocals = new Set<string>()\n const routerCtors = new Set<string>()\n const bindings: RawBinding[] = []\n\n const addFromExpress = (local: string, sel: string, exported: string): void => {\n if (sel === 'default' || sel === 'namespace') expressLocals.add(local)\n else if (exported === 'Router') routerCtors.add(local)\n }\n\n walk(root, (node) => {\n if (node.type === 'import_statement') {\n const source = node.childForFieldName('source')\n const spec = source ? staticStringText(source) : null\n if (!spec) return\n let clause: Parser.SyntaxNode | null = null\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c?.type === 'import_clause') clause = c\n }\n if (!clause) return\n for (let i = 0; i < clause.namedChildCount; i++) {\n const c = clause.namedChild(i)\n if (!c) continue\n if (c.type === 'identifier') {\n if (spec === 'express') addFromExpress(c.text, 'default', 'default')\n else bindings.push({ local: c.text, specifier: spec, sel: 'default' })\n } else if (c.type === 'namespace_import') {\n const id = c.namedChild(0)\n if (id?.type === 'identifier') {\n if (spec === 'express') addFromExpress(id.text, 'namespace', 'namespace')\n else bindings.push({ local: id.text, specifier: spec, sel: 'namespace' })\n }\n } else if (c.type === 'named_imports') {\n for (let j = 0; j < c.namedChildCount; j++) {\n const s = c.namedChild(j)\n if (s?.type !== 'import_specifier') continue\n const name = s.childForFieldName('name')?.text\n if (!name) continue\n const local = s.childForFieldName('alias')?.text ?? name\n if (spec === 'express') addFromExpress(local, name, name)\n else bindings.push({ local, specifier: spec, sel: name })\n }\n }\n }\n return\n }\n\n if (node.type === 'variable_declarator') {\n const value = node.childForFieldName('value')\n if (value?.type !== 'call_expression') return\n const fn = value.childForFieldName('function')\n if (fn?.type !== 'identifier' || fn.text !== 'require') return\n const arg = namedArgs(value.childForFieldName('arguments'))[0]\n const spec = arg ? staticStringText(arg) : null\n if (!spec) return\n const name = node.childForFieldName('name')\n if (name?.type === 'identifier') {\n if (spec === 'express') expressLocals.add(name.text)\n else bindings.push({ local: name.text, specifier: spec, sel: 'default' }) // require = module.exports\n } else if (name?.type === 'object_pattern') {\n for (let i = 0; i < name.namedChildCount; i++) {\n const el = name.namedChild(i)\n if (!el) continue\n let local: string | undefined\n let exported: string | undefined\n if (el.type === 'shorthand_property_identifier_pattern') {\n local = el.text\n exported = el.text\n } else if (el.type === 'pair_pattern') {\n exported = el.childForFieldName('key')?.text\n local = el.childForFieldName('value')?.text ?? exported\n }\n if (!local || !exported) continue\n if (spec === 'express') addFromExpress(local, exported, exported)\n else bindings.push({ local, specifier: spec, sel: exported })\n }\n }\n }\n })\n\n return { expressLocals, routerCtors, bindings }\n}\n\n// Analyse one file's router topology: which vars are routers, what each mounts,\n// which declare routes, and what the file exports.\nfunction analyzeExpressFile(root: Parser.SyntaxNode, dir: string): FileRouterInfo {\n const { expressLocals, routerCtors, bindings } = collectExpressImports(root)\n const routerVars = new Map<string, RouterVarInfo>()\n const appVars = new Set<string>()\n const exportNamed = new Map<string, string>()\n let exportDefaultName: string | null = null\n\n const getVar = (name: string): RouterVarInfo => {\n let rv = routerVars.get(name)\n if (!rv) {\n rv = { declares: false, mounts: [] }\n routerVars.set(name, rv)\n }\n return rv\n }\n\n // Resolve an exported expression to a routerVars key. A bare identifier names\n // a var directly; a `Router().use(...)` chain becomes a synthetic entry keyed\n // `key`, so every export is expressible as a var reference.\n const refFromExpr = (expr: Parser.SyntaxNode, key: string): string | null => {\n if (expr.type === 'identifier') return expr.text\n const u = unwrapRouterExpr(expr, expressLocals, routerCtors)\n if (!u) return null\n const rv = getVar(key)\n for (const m of u.mounts) rv.mounts.push(m)\n if (typeof u.base === 'object') rv.aliasOf = u.base.alias\n return key\n }\n\n const isExported = (declarator: Parser.SyntaxNode): boolean => {\n const decl = declarator.parent // lexical_declaration / variable_declaration\n return decl?.parent?.type === 'export_statement'\n }\n\n walk(root, (node) => {\n if (node.type === 'variable_declarator') {\n const value = node.childForFieldName('value')\n const name = node.childForFieldName('name')\n if (name?.type !== 'identifier' || !value) return\n if (value.type === 'call_expression') {\n const fn = value.childForFieldName('function')\n if (fn?.type === 'identifier' && fn.text === 'require') return // an import, handled above\n }\n const u = unwrapRouterExpr(value, expressLocals, routerCtors)\n if (!u) return\n const rv = getVar(name.text)\n for (const m of u.mounts) rv.mounts.push(m)\n if (u.base === 'app') appVars.add(name.text)\n else if (typeof u.base === 'object') rv.aliasOf = u.base.alias\n if (isExported(node)) exportNamed.set(name.text, name.text)\n return\n }\n\n if (node.type === 'call_expression') {\n const fn = node.childForFieldName('function')\n if (fn?.type !== 'member_expression') return\n const obj = fn.childForFieldName('object')\n const prop = fn.childForFieldName('property')?.text\n if (obj?.type !== 'identifier' || !prop) return\n if (prop === 'use') {\n const m = parseUseMount(node)\n if (m) getVar(obj.text).mounts.push(m)\n } else if (ROUTER_METHODS.has(prop.toLowerCase())) {\n const first = namedArgs(node.childForFieldName('arguments'))[0]\n const p = first ? staticStringText(first) : null\n if (p !== null && p.startsWith('/')) getVar(obj.text).declares = true\n }\n return\n }\n\n if (node.type === 'export_statement') {\n // Named clause: `export { router, r as default }`.\n let clause: Parser.SyntaxNode | null = null\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c?.type === 'export_clause') clause = c\n }\n if (clause) {\n for (let i = 0; i < clause.namedChildCount; i++) {\n const spec = clause.namedChild(i)\n if (spec?.type !== 'export_specifier') continue\n const local = spec.childForFieldName('name')?.text\n if (!local) continue\n const exportedAs = spec.childForFieldName('alias')?.text ?? local\n if (exportedAs === 'default') exportDefaultName = local\n else exportNamed.set(exportedAs, local)\n }\n return\n }\n // `export const NAME = …` — the declarator handler registers it.\n if (node.childForFieldName('declaration')) return\n // `export default <expr>`.\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c && c.type !== 'export_clause') {\n exportDefaultName = refFromExpr(c, '#default')\n break\n }\n }\n return\n }\n\n if (node.type === 'assignment_expression') {\n const left = node.childForFieldName('left')\n const right = node.childForFieldName('right')\n if (left?.type !== 'member_expression' || !right) return\n const lobj = left.childForFieldName('object')?.text\n const lprop = left.childForFieldName('property')?.text\n if (lobj === 'module' && lprop === 'exports') exportDefaultName = refFromExpr(right, '#default')\n else if (lobj === 'exports' && lprop) {\n const key = refFromExpr(right, `#exp:${lprop}`)\n if (key) exportNamed.set(lprop, key)\n }\n }\n })\n\n return {\n dir,\n routerVars,\n appVars,\n exportDefaultName,\n exportNamed,\n rawBindings: bindings,\n importedRouters: new Map(),\n }\n}\n\n/**\n * Whole-program pass (ADR-160): compose Express cross-file mount prefixes onto\n * the routes they mount. Returns a map from a route-declaring file's\n * service-relative path to the prefix its routes serve under — e.g. a controller\n * mounted through `app.use(routes)` → `Router().use('/api', api)` → `.use(ctrl)`\n * lands `'/api'`. Files with no composed prefix are absent (their leaf paths\n * stand as declared). `files` should be the service's source files; only Express\n * `.js`/`.ts` files are analysed.\n */\nexport async function expressMountPrefixes(\n files: SourceFile[],\n serviceDir: string,\n tsPaths: TsPathConfig | null,\n): Promise<Map<string, string>> {\n const jsParser = makeJsParser()\n const fileInfo = new Map<string, FileRouterInfo>()\n\n for (const f of files) {\n if (!JS_ROUTE_EXTENSIONS.has(path.extname(f.path))) continue\n if (isTestPath(f.path)) continue\n const rel = toPosix(path.relative(serviceDir, f.path))\n try {\n const tree = parseSource(jsParser, f.content)\n fileInfo.set(rel, analyzeExpressFile(tree.rootNode, path.dirname(f.path)))\n } catch {\n // A file that won't parse contributes no mounts; leave its routes bare.\n }\n }\n if (fileInfo.size === 0) return new Map()\n\n // Resolve each import binding to its defining file through the import graph —\n // the same specifier→file resolution imports.ts uses (ADR-149). A specifier\n // that leaves the service (node_modules, a Node builtin) resolves to null and\n // is dropped, so it can never carry a prefix.\n for (const info of fileInfo.values()) {\n for (const b of info.rawBindings) {\n const resolved = await resolveJsImport(b.specifier, info.dir, serviceDir, tsPaths)\n if (!resolved || !fileInfo.has(resolved)) continue\n info.importedRouters.set(b.local, { file: resolved, sel: b.sel === 'namespace' ? 'default' : b.sel })\n }\n }\n\n // Resolve a mounted identifier to (file, routerVars-key) — a local router var,\n // or a cross-file import's selected export. null when it names no router we can\n // reach, which leaves the leaf un-prefixed.\n const resolveTarget = (name: string, file: string): { file: string; name: string } | null => {\n const info = fileInfo.get(file)\n if (!info) return null\n if (info.routerVars.has(name)) return { file, name }\n const imp = info.importedRouters.get(name)\n if (!imp) return null\n const target = fileInfo.get(imp.file)\n if (!target) return null\n const key = imp.sel === 'default' ? target.exportDefaultName : target.exportNamed.get(imp.sel)\n if (!key) return null\n return { file: imp.file, name: key }\n }\n\n const filePrefix = new Map<string, string>()\n const conflicted = new Set<string>()\n const apply = (file: string, prefix: string): void => {\n if (conflicted.has(file)) return\n const existing = filePrefix.get(file)\n if (existing === undefined) filePrefix.set(file, prefix)\n else if (existing !== prefix) {\n // The same file reached at two different prefixes — genuinely ambiguous.\n // Drop it rather than pick one (ADR-160 discipline).\n filePrefix.delete(file)\n conflicted.add(file)\n }\n }\n\n const visited = new Set<string>()\n const collect = (file: string, name: string, accPrefix: string): void => {\n const key = `${file}|${name}|${accPrefix}`\n if (visited.has(key)) return\n visited.add(key)\n const info = fileInfo.get(file)\n const rv = info?.routerVars.get(name)\n if (!info || !rv) return\n // A file that also hosts an express() app serves its declared routes at the\n // app root; don't push a mount prefix onto them.\n if (rv.declares && info.appVars.size === 0) apply(file, accPrefix)\n for (const m of rv.mounts) {\n if (!m.target) continue\n const t = resolveTarget(m.target, file)\n if (t) collect(t.file, t.name, accPrefix + m.prefix)\n }\n if (rv.aliasOf) {\n const t = resolveTarget(rv.aliasOf, file)\n if (t) collect(t.file, t.name, accPrefix)\n }\n }\n\n // Root the walk at each express() app. What the app mounts is what the server\n // actually serves, so the prefix path from the app down is the real one.\n for (const [rel, info] of fileInfo) {\n for (const appVar of info.appVars) collect(rel, appVar, '')\n }\n\n const out = new Map<string, string>()\n for (const [file, prefix] of filePrefix) if (prefix && prefix !== '/') out.set(file, prefix)\n return out\n}\n\n// ── producer ────────────────────────────────────────────────────────────────\n\nexport async function addRoutes(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const jsParser = makeJsParser()\n const pyParser = makePyParser()\n const goParser = makeGoParser()\n const rubyParser = makeRubyParser()\n const phpParser = makePhpParser()\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const deps = {\n ...(service.pkg.dependencies ?? {}),\n ...(service.pkg.devDependencies ?? {}),\n }\n const hasExpress = deps['express'] !== undefined\n const hasFastify = deps['fastify'] !== undefined\n const hasHono = deps['hono'] !== undefined\n const hasNext = deps['next'] !== undefined\n const hasNestjs = deps['@nestjs/core'] !== undefined\n // FastAPI / Flask are discovered on a Python service the same dependency-gated\n // way: the manifest reader (extract/python.ts) strips the `fastapi[standard]`\n // extra to the bare `fastapi` distribution name.\n const hasFastapi = deps['fastapi'] !== undefined\n const hasFlask = deps['flask'] !== undefined\n const hasDjango = deps['django'] !== undefined\n const hasGin = deps['github.com/gin-gonic/gin'] !== undefined\n // Echo and Fiber join Go's route recognizers (ADR-181): the go.mod require\n // path carries the major-version suffix, so gate on both the versioned module\n // and the pre-modules path. Each declares routes the same shape Gin does.\n const hasEcho =\n deps['github.com/labstack/echo/v4'] !== undefined ||\n deps['github.com/labstack/echo'] !== undefined\n const hasFiber =\n deps['github.com/gofiber/fiber/v2'] !== undefined ||\n deps['github.com/gofiber/fiber/v3'] !== undefined\n // Chi joins the Go recognizers (ADR-182); the go.mod require path carries the\n // major-version suffix, so gate on both the versioned module and the\n // pre-modules path. Its verbs read like Fiber's; its `Route` prefix\n // composition is closure-scoped (a stack, not a var map).\n const hasChi =\n deps['github.com/go-chi/chi/v5'] !== undefined ||\n deps['github.com/go-chi/chi'] !== undefined\n // net/http is stdlib — there's no dependency to gate on, so a service can't be\n // pre-selected for it the way a framework service is. Any Go service may carry\n // ServeMux routes, so a Go service is admitted on its language alone and the\n // net/http recognizer self-gates per file (import + method-pattern shape).\n const isGoService = service.node.language === 'go'\n // Rails is discovered from the Gemfile (extract/ruby.ts) the same\n // dependency-gated way; its routes live in one conventional file (ADR-173).\n const hasRails = deps['rails'] !== undefined\n // Laravel is discovered from composer.json (extract/php.ts) the same\n // dependency-gated way; its routes live in routes/web.php + api.php (ADR-177).\n const hasLaravel = deps['laravel/framework'] !== undefined\n if (\n !hasExpress &&\n !hasFastify &&\n !hasHono &&\n !hasNext &&\n !hasNestjs &&\n !hasFastapi &&\n !hasFlask &&\n !hasDjango &&\n !hasGin &&\n !hasEcho &&\n !hasFiber &&\n !hasChi &&\n !isGoService &&\n !hasRails &&\n !hasLaravel\n )\n continue\n\n const files = await loadSourceFiles(service.dir)\n // Cross-file Express mount-prefix composition (ADR-160). Resolved once per\n // service after the file list is loaded; the per-file scan below prepends the\n // composed prefix so a mounted controller's leaf path (`/tags`) becomes the\n // full path a production span carries (`/api/tags`), and the two fuse.\n const mountPrefixes = hasExpress\n ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir))\n : new Map<string, string>()\n\n for (const file of files) {\n // ADR-065 #1 — test-scope exclusion. A test that spins up a router isn't\n // the service's declared route surface.\n if (isTestPath(file.path)) continue\n const ext = path.extname(file.path)\n const isPy = ext === '.py'\n const isGo = ext === '.go'\n const isRb = ext === '.rb'\n const isPhp = ext === '.php'\n if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo && !isRb && !isPhp) continue\n const relFile = toPosix(path.relative(service.dir, file.path))\n\n let routes: ExtractedRoute[]\n try {\n if (isPhp) {\n // Laravel declares its HTTP surface in two conventional files; every\n // route in routes/api.php gets the framework's automatic `/api` prefix\n // (not in the source), passed as the base prefix. Other `.php` files\n // carry no routes to read (ADR-177).\n routes =\n hasLaravel && (relFile === 'routes/web.php' || relFile === 'routes/api.php')\n ? laravelRoutesFromSource(\n file.content,\n phpParser,\n relFile === 'routes/api.php' ? '/api' : '',\n )\n : []\n } else if (isRb) {\n // Rails declares its whole route table in one conventional file; other\n // `.rb` files carry no routes to read (ADR-173).\n routes =\n hasRails && relFile === 'config/routes.rb'\n ? railsRoutesFromSource(file.content, rubyParser)\n : []\n } else if (isGo) {\n // One framework recognizer per service — Gin, Echo, Fiber, and Chi are\n // dependency-gated, so a file is read by whichever the manifest names.\n if (hasGin) routes = ginRoutesFromSource(file.content, goParser)\n else if (hasEcho) routes = echoRoutesFromSource(file.content, goParser)\n else if (hasFiber) routes = fiberRoutesFromSource(file.content, goParser)\n else if (hasChi) routes = chiRoutesFromSource(file.content, goParser)\n else routes = []\n // net/http has no dependency to gate on, so it runs additively on every\n // Go file and self-gates on the `net/http` import plus the\n // method-prefixed ServeMux pattern shape (ADR-182). A framework file\n // won't carry that shape, so there's no double count.\n routes = routes.concat(netHttpRoutesFromSource(file.content, goParser))\n } else if (isPy) {\n routes =\n hasFastapi || hasFlask\n ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? 'fastapi' : 'flask')\n : []\n // Django's URLconf shape (a urlpatterns list) is independent of the\n // decorator shape, so a Django service's routes come from here too.\n if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser))\n } else if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {\n routes = nextRoutesFromFile(file.content, relFile, jsParser)\n } else if (hasNestjs) {\n routes = nestjsRoutesFromSource(file.content, jsParser)\n } else if (hasExpress || hasFastify || hasHono) {\n routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono)\n } else {\n routes = []\n }\n } catch (err) {\n recordExtractionError('route extraction', file.path, err)\n continue\n }\n if (routes.length === 0) continue\n\n // A cross-file Express mount prefix for this file's routes, if one resolved.\n const mountPrefix = mountPrefixes.get(relFile)\n\n for (const route of routes) {\n const pathTemplate = mountPrefix\n ? canonicalizeTemplate(mountPrefix + route.pathTemplate)\n : route.pathTemplate\n const rid = routeId(service.pkg.name, route.method, pathTemplate)\n if (!graph.hasNode(rid)) {\n const node: RouteNode = {\n id: rid,\n type: NodeType.RouteNode,\n name: `${route.method} ${pathTemplate}`,\n service: service.pkg.name,\n method: route.method,\n pathTemplate,\n path: relFile,\n line: route.line,\n framework: route.framework,\n discoveredVia: 'static',\n }\n graph.addNode(rid, node)\n nodesAdded++\n }\n // `service ──CONTAINS──▶ route` — the service owns its routes the same\n // way it owns its files (file-awareness.md §2). Structural ownership,\n // evidence pinned to the defining file:line.\n const containsId = extractedEdgeId(service.node.id, rid, EdgeType.CONTAINS)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: service.node.id,\n target: rid,\n type: EdgeType.CONTAINS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: relFile,\n line: route.line,\n snippet: snippet(file.content, route.line),\n },\n }\n graph.addEdgeWithKey(containsId, service.node.id, rid, edge)\n edgesAdded++\n }\n }\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { parse as parseYaml } from 'yaml'\nimport type { ServiceNode } from '@neat.is/types'\n\nexport interface PackageJson {\n name: string\n version?: string\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n engines?: { node?: string }\n}\n\nexport interface DiscoveredService {\n pkg: PackageJson\n dir: string\n node: ServiceNode\n}\n\nexport const SERVICE_FILE_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.py', '.go', '.rb', '.php'])\nexport const CONFIG_FILE_EXTENSIONS = new Set(['.yaml', '.yml'])\nexport const IGNORED_DIRS = new Set([\n 'node_modules',\n '.git',\n '.turbo',\n 'dist',\n 'build',\n '.next',\n // Python virtualenv shapes (issue #344). Walking into a venv pulls in the\n // entire CPython stdlib + every installed package as if it were first-party\n // service code — 20k+ files, none of which the user wrote. The shape names\n // cover the three common venv tools (`venv` / `python -m venv`,\n // `virtualenv`) and the tox + PEP 582 conventions.\n '.venv',\n 'venv',\n '__pypackages__',\n '.tox',\n // `site-packages` shows up nested inside venvs that don't carry one of the\n // outer names (system Python on macOS, in-place pyenv layouts). Listing it\n // here means we stop the walk at the boundary even when the wrapper dir\n // wasn't recognisable.\n 'site-packages',\n])\n\n// Marker file `python -m venv` and `virtualenv` both drop at the venv root.\n// Used by the walkers to recognise venvs whose top-level directory doesn't\n// happen to match one of the canonical `IGNORED_DIRS` names (issue #344).\n// Cached per process to keep the recursion hot path off the fs after the\n// first walk; venv contents don't change inside one extract pass.\nconst PYVENV_MARKER_CACHE = new Map<string, boolean>()\n\nexport async function isPythonVenvDir(dir: string): Promise<boolean> {\n const cached = PYVENV_MARKER_CACHE.get(dir)\n if (cached !== undefined) return cached\n try {\n const stat = await fs.stat(path.join(dir, 'pyvenv.cfg'))\n const ok = stat.isFile()\n PYVENV_MARKER_CACHE.set(dir, ok)\n return ok\n } catch {\n PYVENV_MARKER_CACHE.set(dir, false)\n return false\n }\n}\n\nexport function isConfigFile(name: string): { match: boolean; fileType: string } {\n const ext = path.extname(name)\n if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) }\n // .env, .env.local, .env.production. Bare filename or any dotted-suffix\n // variant; folder names get filtered upstream by walking files only.\n // ADR-065 #4 filters .env.template / .env.example / .env.sample (and the\n // dotted-suffix variants) at the producer level — those are documentation,\n // not runtime config.\n if (name === '.env' || name.startsWith('.env.')) {\n if (isEnvTemplateFile(name)) return { match: false, fileType: '' }\n // NEAT writes `.env.neat` itself during `--apply` (ADR-069 §4). Ingesting\n // it would record our own instrumentation env as if it were the user's\n // declared config — self-pollution. Skip it the same way the template\n // filter skips onboarding docs.\n if (isNeatAuthoredEnvFile(name)) return { match: false, fileType: '' }\n return { match: true, fileType: 'env' }\n }\n return { match: false, fileType: '' }\n}\n\n// NEAT-authored artifacts the installer drops into the user's tree during\n// `--apply` (ADR-069). Extraction skips them: they're our own runtime hooks,\n// not first-party config or source. Ingesting them pollutes the graph with\n// NEAT's instrumentation as though the user had written it.\n//\n// `.env.neat` — per-package env carrying `OTEL_SERVICE_NAME`.\n// `otel-init.{ext}` — generated SDK bootstrap (js/cjs/mjs/ts), including\n// the framework variants (`src/otel-init.*`,\n// `server/plugins/otel-init.*`) — basename is stable.\nexport function isNeatAuthoredEnvFile(name: string): boolean {\n return name === '.env.neat'\n}\n\nexport function isNeatAuthoredSourceFile(name: string): boolean {\n return /^otel-init\\.(?:js|cjs|mjs|ts|tsx)$/i.test(name) || name === 'neat_otel.go'\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// ADR-065 precision-filter helpers. Pre-emit gates inside the producer pass.\n// Filtered candidates are never written to the graph (idempotency intact).\n// ─────────────────────────────────────────────────────────────────────────\n\n// ADR-065 #1 — test-scope exclusion. Returns true when the file path matches\n// any test-scope pattern. Path is normalised to forward slashes before\n// matching so callers can pass either form.\n//\n// Patterns:\n// - any segment named __tests__, __fixtures__, or integration-tests\n// - basename matches *.spec.{ts,tsx,js,jsx,mjs,cjs,py}\n// - basename matches *.test.{ts,tsx,js,jsx,mjs,cjs,py}\nexport function isTestPath(filePath: string): boolean {\n const normalised = filePath.replace(/\\\\/g, '/')\n const segments = normalised.split('/')\n for (const seg of segments) {\n if (seg === '__tests__' || seg === '__fixtures__' || seg === 'integration-tests') {\n return true\n }\n }\n const base = segments[segments.length - 1] ?? ''\n if (base.endsWith('_test.go')) return true\n return /\\.(spec|test)\\.(?:tsx?|jsx?|mjs|cjs|py)$/i.test(base)\n}\n\n// ADR-065 #4 — `.env.template` exclusion. Matches:\n// .env.template / .env.example / .env.sample\n// .env.*.template / .env.*.example / .env.*.sample\n// These are docs/onboarding artifacts, not runtime config. ConfigNodes are\n// bound to runtime existence (ADR-016); templates fail that test.\nexport function isEnvTemplateFile(name: string): boolean {\n if (\n name === '.env.template' ||\n name === '.env.example' ||\n name === '.env.sample'\n ) {\n return true\n }\n // `.env.*.template` / `.env.*.example` / `.env.*.sample`\n return /^\\.env\\.[^.]+\\.(?:template|example|sample)$/i.test(name)\n}\n\n// ADR-065 #2 — comment-body exclusion. Replaces every JS/TS comment span in\n// the source with an equal-length run of spaces, preserving line/column for\n// downstream line-mapping. Strings that contain `//` sequences (URLs) are\n// preserved by tracking the string context as we scan.\n//\n// Not a full parser — good enough for the medusa-shape failures. The HTTP\n// extractor's AST walk already gets comment-awareness for free; this helper\n// is for the regex-based extractors (redis, kafka, aws, grpc).\nexport function maskCommentsInSource(src: string): string {\n const len = src.length\n const out: string[] = new Array(len)\n let i = 0\n // String context: ' \" ` (template) — open-quote char, 0 when not in a string.\n let inString: string | 0 = 0\n let escaped = false\n while (i < len) {\n const c = src[i]!\n if (inString !== 0) {\n out[i] = c\n if (escaped) {\n escaped = false\n } else if (c === '\\\\') {\n escaped = true\n } else if (c === inString) {\n inString = 0\n }\n i++\n continue\n }\n if (c === '/' && i + 1 < len) {\n const next = src[i + 1]!\n if (next === '/') {\n out[i] = ' '\n out[i + 1] = ' '\n let j = i + 2\n while (j < len && src[j] !== '\\n') {\n out[j] = ' '\n j++\n }\n i = j\n continue\n }\n if (next === '*') {\n out[i] = ' '\n out[i + 1] = ' '\n let j = i + 2\n while (j < len) {\n if (src[j] === '\\n') {\n out[j] = '\\n'\n j++\n continue\n }\n if (src[j] === '*' && j + 1 < len && src[j + 1] === '/') {\n out[j] = ' '\n out[j + 1] = ' '\n j += 2\n break\n }\n out[j] = ' '\n j++\n }\n i = j\n continue\n }\n }\n out[i] = c\n if (c === \"'\" || c === '\"' || c === '`') inString = c\n i++\n }\n return out.join('')\n}\n\n// ADR-065 #5 — exact hostname match for cross-service URL inference. Returns\n// true if `urlString` looks like an actual URL — has an explicit `scheme://`\n// or starts with a scheme-relative `//` — and its hostname matches `host`\n// exactly (case-insensitive). No `.includes()` containment, and no bare-string\n// matching: a literal like `'admin-bundler'` would otherwise parse as\n// `http://admin-bundler` and match the basename of every service directory,\n// which is how the v0.3.3 medusa pre-check produced 279 false positives.\n//\n// Accepts a `host` that may include a port (`api.example.com:8080`); in that\n// case the URL's hostname AND port must both match.\nconst URL_LIKE = /^(?:[a-z][a-z0-9+.-]*:)?\\/\\//i\n\nexport function urlMatchesHost(urlString: string, host: string): boolean {\n if (typeof urlString !== 'string' || urlString.length === 0) return false\n // Require the literal to look like a URL — scheme + `://` or scheme-relative\n // `//host`. Bare hostnames are rejected; they're the load-bearing source of\n // false positives.\n if (!URL_LIKE.test(urlString)) return false\n const [wantedHost, wantedPort] = host.split(':')\n let parsed: URL\n try {\n // For scheme-relative `//host/path`, prepend `http:` so URL accepts it.\n const candidate = urlString.startsWith('//') ? `http:${urlString}` : urlString\n parsed = new URL(candidate)\n } catch {\n return false\n }\n if (parsed.hostname.toLowerCase() !== (wantedHost ?? '').toLowerCase()) return false\n if (wantedPort && parsed.port !== wantedPort) return false\n return true\n}\n\n// Strip semver range prefixes (^, ~, >=, etc.) and bare \"v\" so the extracted\n// version is usable for compat checks. We don't try to resolve ranges to actual\n// installed versions — that's a published-lockfile concern, not extraction's job.\nexport function cleanVersion(raw: string | undefined): string | undefined {\n if (!raw) return undefined\n return raw.replace(/^[\\^~><=v\\s]+/, '').trim() || undefined\n}\n\nexport async function readJson<T>(filePath: string): Promise<T> {\n const raw = await fs.readFile(filePath, 'utf8')\n return JSON.parse(raw) as T\n}\n\nexport async function readYaml<T>(filePath: string): Promise<T> {\n const raw = await fs.readFile(filePath, 'utf8')\n return parseYaml(raw) as T\n}\n\nexport async function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p)\n return true\n } catch {\n return false\n }\n}\n\n// Thin re-export so existing callers (calls/, configs.ts, databases/, infra/)\n// keep their import path. Wire format lives in @neat.is/types/identity.ts per\n// ADR-029.\nexport { extractedEdgeId as makeEdgeId } from '@neat.is/types'\n","// ADR-065 — loud failure mode for static extraction.\n//\n// Per-file extraction failures used to land as a single `console.warn` line\n// with no aggregate count and no on-disk record. The 2026-05-12 medusa\n// experiment ran `neat init` against ~90 files that all silently failed\n// extraction with \"Invalid argument\"; the snapshot shipped with those files\n// missing, the user had no signal it had happened, and the divergence query\n// couldn't surface gaps it didn't know it had.\n//\n// This module is the failure-mode plumbing: a process-local sink that every\n// producer appends to when a per-file parse fails, with helpers to drain the\n// sink to `<projectDir>/neat-out/errors.ndjson` and to surface the aggregate\n// count in init / watch banners.\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { ExtractionCoverage } from '@neat.is/types'\n\nexport interface ExtractionError {\n // Producer name (e.g. \"http\", \"services\", \"infra docker-compose\"). Stable\n // human-readable identifier for logs and contract assertions.\n producer: string\n // Absolute or relative path of the file that failed. Stored verbatim from\n // the call site; no normalisation here.\n file: string\n // The error's `.message`. Stringified if the throw value wasn't an Error.\n error: string\n // Optional stack trace (captured at the call site). Useful for diagnosing\n // tree-sitter \"Invalid argument\" cases where the message alone is generic.\n stack?: string\n // ISO timestamp of when the error was recorded.\n ts: string\n // Discriminator for `errors.ndjson` consumers — separates extract failures\n // from OTel error events that share the same file (ADR-033).\n source: 'extract'\n}\n\nconst sink: ExtractionError[] = []\n\n// Record a per-file extraction failure. Preserves the visible warn line so\n// existing scripts/CI consumers still see a per-file message; appends to the\n// process-local sink so the aggregate count + sidecar write can fire later.\nexport function recordExtractionError(\n producer: string,\n file: string,\n err: unknown,\n): void {\n const e = err instanceof Error ? err : new Error(String(err))\n sink.push({\n producer,\n file,\n error: e.message,\n stack: e.stack,\n ts: new Date().toISOString(),\n source: 'extract',\n })\n // Visible warn preserved (pre-ADR-065 callers logged this). Banners\n // aggregate, but per-file context still useful for tail -f sessions.\n console.warn(`[neat] ${producer} skipped ${file}: ${e.message}`)\n}\n\n// Drain all queued errors. Idempotent — subsequent calls return an empty\n// array until new errors land. Callers (extractFromDirectory) drain at start\n// to clear stale state from prior runs, then drain again at end to collect\n// the pass's failures.\nexport function drainExtractionErrors(): ExtractionError[] {\n return sink.splice(0, sink.length)\n}\n\n// Read-only count of currently-queued errors. Used by callers that want the\n// count without consuming the sink (the banner case — we want the count for\n// display and the entries for `errors.ndjson`).\nexport function pendingExtractionErrors(): number {\n return sink.length\n}\n\n// Append the drained entries to `<projectDir>/neat-out/errors.ndjson`. The\n// file is shared with OTel error events (per ADR-033); the `source: 'extract'`\n// discriminator separates them for consumers. Creates the directory if\n// missing. Append-only — never rewritten.\nexport async function writeExtractionErrors(\n errors: ExtractionError[],\n errorsPath: string,\n): Promise<void> {\n if (errors.length === 0) return\n await fs.mkdir(path.dirname(errorsPath), { recursive: true })\n const lines = errors.map((e) => JSON.stringify(e)).join('\\n') + '\\n'\n await fs.appendFile(errorsPath, lines, 'utf8')\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// #883 — extraction coverage in the query surface.\n//\n// errors.ndjson is append-only and shared with OTel error events, so it can't\n// answer \"is the CURRENT graph complete?\" — its extract lines accumulate across\n// every pass. This sidecar is overwritten each full pass (zero included), so it\n// always reflects the latest coverage, and a daemon reads it to put the count on\n// /health. Without it, a partial extraction is invisible to anything querying\n// the graph.\n// ─────────────────────────────────────────────────────────────────────────\n\n// The health sidecar sits beside errors.ndjson and mirrors its per-project\n// naming: errors.ndjson → extraction-health.json;\n// errors.<project>.ndjson → extraction-health.<project>.json.\nexport function extractionHealthPathFor(errorsPath: string): string {\n const dir = path.dirname(errorsPath)\n const suffix = path.basename(errorsPath).replace(/^errors/, '').replace(/\\.ndjson$/, '')\n return path.join(dir, `extraction-health${suffix}.json`)\n}\n\n// Up to this many failed-file paths are sampled into the sidecar; the count is\n// always exact, the list is a bounded aid.\nconst HEALTH_FILE_SAMPLE = 100\n\n// Overwrite the sidecar with this pass's coverage. Called on every full pass so\n// a clean re-run clears a prior gap (skippedFiles: 0). Best-effort at the call\n// site; failure to write never fails extraction.\nexport async function writeExtractionHealth(\n errors: ExtractionError[],\n healthPath: string,\n now: string = new Date().toISOString(),\n): Promise<void> {\n const byProducer: Record<string, number> = {}\n for (const e of errors) byProducer[e.producer] = (byProducer[e.producer] ?? 0) + 1\n const coverage: ExtractionCoverage = {\n skippedFiles: errors.length,\n byProducer,\n files: errors.slice(0, HEALTH_FILE_SAMPLE).map((e) => e.file),\n updatedAt: now,\n }\n await fs.mkdir(path.dirname(healthPath), { recursive: true })\n await fs.writeFile(healthPath, JSON.stringify(coverage), 'utf8')\n}\n\n// Read the coverage sidecar. Returns undefined when absent or malformed —\n// discovery is convenience, never fatal, so a missing/garbled file just means\n// \"no recorded coverage,\" not an error.\nexport async function readExtractionHealth(\n healthPath: string,\n): Promise<ExtractionCoverage | undefined> {\n let raw: string\n try {\n raw = await fs.readFile(healthPath, 'utf8')\n } catch {\n return undefined\n }\n try {\n const obj = JSON.parse(raw) as ExtractionCoverage\n if (typeof obj?.skippedFiles !== 'number' || !obj.byProducer || !Array.isArray(obj.files)) {\n return undefined\n }\n return obj\n } catch {\n return undefined\n }\n}\n\n// ADR-065 — `NEAT_STRICT_EXTRACTION=1` makes any per-file extraction failure\n// cause the calling command to exit non-zero. Default is forgiving (banner\n// only). The check is at the caller (cli.ts / server.ts / watch.ts) so the\n// extraction phase itself stays library-shaped.\nexport function isStrictExtractionEnabled(): boolean {\n const raw = process.env.NEAT_STRICT_EXTRACTION\n return raw === '1' || raw === 'true'\n}\n\n// Format the unconditional summary banner. Zero errors is a positive signal\n// (\"0 files skipped\") so callers print this even on clean runs.\nexport function formatExtractionBanner(count: number): string {\n if (count === 1) return `[neat] 1 file skipped due to parse errors`\n return `[neat] ${count} files skipped due to parse errors`\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// ADR-066 — precision-floor drop accounting.\n//\n// EXTRACTED candidates whose graded confidence falls below\n// NEAT_EXTRACTED_PRECISION_FLOOR (default 0.7) are computed but never added\n// to the graph. The drop count surfaces on the extraction banner;\n// NEAT_EXTRACTED_REJECTED_LOG=1 routes the per-candidate detail to\n// `<projectDir>/neat-out/rejected.ndjson`. The default keeps the sidecar\n// surface quiet.\n// ─────────────────────────────────────────────────────────────────────────\n\nexport interface DroppedExtractedEdge {\n source: string\n target: string\n type: string\n confidence: number\n confidenceKind: string\n evidence: { file: string; line?: number; snippet?: string }\n}\n\nconst droppedSink: DroppedExtractedEdge[] = []\n\nexport function noteExtractedDropped(edge: DroppedExtractedEdge): void {\n droppedSink.push(edge)\n}\n\nexport function drainDroppedExtracted(): DroppedExtractedEdge[] {\n return droppedSink.splice(0, droppedSink.length)\n}\n\nexport function pendingDroppedExtracted(): number {\n return droppedSink.length\n}\n\nexport function isRejectedLogEnabled(): boolean {\n const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG\n return raw === '1' || raw === 'true'\n}\n\nexport async function writeRejectedExtracted(\n drops: DroppedExtractedEdge[],\n rejectedPath: string,\n): Promise<void> {\n if (drops.length === 0) return\n await fs.mkdir(path.dirname(rejectedPath), { recursive: true })\n const lines = drops.map((d) => JSON.stringify({ ...d, ts: new Date().toISOString() })).join('\\n') + '\\n'\n await fs.appendFile(rejectedPath, lines, 'utf8')\n}\n\nexport function formatPrecisionFloorBanner(count: number): string {\n if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`\n return `[neat] ${count} extracted edges dropped below precision floor`\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { EdgeEvidence, ExtractedConfidenceKind, FileNode, GraphEdge } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n fileId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport {\n IGNORED_DIRS,\n SERVICE_FILE_EXTENSIONS,\n isNeatAuthoredSourceFile,\n isPythonVenvDir,\n type DiscoveredService,\n} from '../shared.js'\n\n// Host → owning ServiceNode id, the cross-service resolution the HTTP call-site\n// producers share (ADR-065 #5, ADR-119). A service is reachable by either its\n// directory basename or its manifest name; both map to the same node id. Reused\n// by http.ts (host-level CALLS edges) and route-match.ts (client↔route\n// matching) so the two producers resolve a URL's host to a service identically.\nexport interface ServiceHostIndex {\n knownHosts: Set<string>\n hostToNodeId: Map<string, string>\n}\n\nexport function buildServiceHostIndex(services: DiscoveredService[]): ServiceHostIndex {\n const knownHosts = new Set<string>()\n const hostToNodeId = new Map<string, string>()\n for (const service of services) {\n const base = path.basename(service.dir)\n knownHosts.add(base)\n knownHosts.add(service.pkg.name)\n hostToNodeId.set(base, service.node.id)\n hostToNodeId.set(service.pkg.name, service.node.id)\n }\n return { knownHosts, hostToNodeId }\n}\n\nexport interface SourceFile {\n path: string\n content: string\n}\n\nexport interface ExternalEndpoint {\n // Stable id of the InfraNode this evidence implies. Format\n // `infra:<kind>:<name>` so the orchestrator can dedupe across services.\n infraId: string\n // Display name on the InfraNode (e.g., \"orders\" for kafka-topic:orders).\n name: string\n kind: string\n edgeType: 'CALLS' | 'PUBLISHES_TO' | 'CONSUMES_FROM'\n evidence: EdgeEvidence\n // Confidence grade per ADR-066 — set by the per-shape detector. The\n // orchestrator (calls/index.ts) writes this onto the EXTRACTED edge and\n // applies the precision floor before adding the edge to the graph.\n confidenceKind: ExtractedConfidenceKind\n // Declared schema columns for a `sql-table` endpoint (ADR-157 §3), at\n // database-name fidelity. Set only by a schema-column producer (the Drizzle\n // recognizer today); the orchestrator folds these onto the table node's\n // `columns` list with EXTRACTED provenance. Absent on every other endpoint.\n columns?: string[]\n // Per-written-field SDK tags for a `firestore-collection` endpoint (ADR-167):\n // which SDK (`client` = firebase/firestore, `admin` = firebase-admin/firestore)\n // wrote each field. Set only by calls/firestore.ts; the orchestrator folds these\n // onto the collection node's columns via `foldSdkWrites` (the parallel of the\n // `columns` fold — `foldColumns` stays untouched). The write-SDK dimension is the\n // seam the field-guard policy (ADR-169) joins on. Absent on every other endpoint.\n sdkWrites?: Record<string, ('client' | 'admin')[]>\n}\n\n// A foreign-key relationship between two SQL tables (ADR-161) — the data-axis\n// sibling of a symbol INHERITS edge. `childTable` declares the FK, `parentTable`\n// is the referenced table; both are the DATABASE table name (the fusion key\n// `infra:sql-table:<name>` the column/table extractors and OTLP already target),\n// reproduced verbatim the way the ORM names the table, not the code model name.\n// A schema-FK producer (Drizzle / Prisma / SQLAlchemy) emits one per resolved\n// reference; a computed or unresolvable parent is left unclaimed (the producer\n// returns nothing), never guessed. `table-edges.ts` mints the EXTRACTED\n// `child ──REFERENCES──▶ parent` edge from these, with `evidence` pinned to the\n// FK declaration site.\nexport interface TableReference {\n childTable: string\n parentTable: string\n evidence: EdgeEvidence\n}\n\nexport async function walkSourceFiles(dir: string): Promise<string[]> {\n const out: string[] = []\n async function walk(current: string): Promise<void> {\n const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n const full = path.join(current, entry.name)\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name)) continue\n if (await isPythonVenvDir(full)) continue\n await walk(full)\n } else if (\n entry.isFile() &&\n SERVICE_FILE_EXTENSIONS.has(path.extname(entry.name)) &&\n // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it\n // would attribute our instrumentation imports to the user's service.\n !isNeatAuthoredSourceFile(entry.name)\n ) {\n out.push(full)\n }\n }\n }\n await walk(dir)\n return out\n}\n\nexport async function loadSourceFiles(dir: string): Promise<SourceFile[]> {\n const paths = await walkSourceFiles(dir)\n const out: SourceFile[] = []\n for (const p of paths) {\n try {\n const content = await fs.readFile(p, 'utf8')\n out.push({ path: p, content })\n } catch {\n // unreadable, skip\n }\n }\n return out\n}\n\n// Locate the line of the first occurrence of `needle` in `text`, 1-indexed.\n// Falls back to line 1 if the needle isn't found verbatim — better to point at\n// the file than to drop the evidence entirely.\nexport function lineOf(text: string, needle: string): number {\n const idx = text.indexOf(needle)\n if (idx < 0) return 1\n return text.slice(0, idx).split('\\n').length\n}\n\nexport function snippet(text: string, line: number): string {\n const lines = text.split('\\n')\n return (lines[line - 1] ?? '').trim()\n}\n\n// Forward-slash a path so a FileNode id is byte-stable across platforms (the\n// `relPath` segment of `file:<service>:<relPath>` must not vary by OS).\nexport function toPosix(p: string): string {\n return p.split('\\\\').join('/')\n}\n\n// Extension → language tag for a FileNode. Returns undefined for extensions we\n// don't name rather than guessing — evidence is never fabricated (§6).\nexport function languageForPath(relPath: string): string | undefined {\n switch (path.extname(relPath).toLowerCase()) {\n case '.py':\n return 'python'\n case '.go':\n return 'go'\n case '.rb':\n return 'ruby'\n case '.php':\n return 'php'\n case '.ts':\n case '.tsx':\n return 'typescript'\n case '.js':\n case '.jsx':\n case '.mjs':\n case '.cjs':\n return 'javascript'\n default:\n return undefined\n }\n}\n\n// File-first emission (file-awareness.md §1–2). Ensure the FileNode for\n// `relPath` and the owning `service ──CONTAINS──▶ file` edge both exist, then\n// return the FileNode id so the caller can originate a relationship from it.\n// `relPath` must already be service-relative and forward-slashed (use toPosix).\n// CONTAINS is structural ownership — graded at the 'structural' tier like\n// CONFIGURED_BY, never a flat value. Idempotent: re-running extraction over an\n// unchanged file is a no-op.\nexport function ensureFileNode(\n graph: NeatGraph,\n serviceName: string,\n serviceNodeId: string,\n relPath: string,\n): { fileNodeId: string; nodesAdded: number; edgesAdded: number } {\n let nodesAdded = 0\n let edgesAdded = 0\n const fileNodeId = fileId(serviceName, relPath)\n if (!graph.hasNode(fileNodeId)) {\n const language = languageForPath(relPath)\n const node: FileNode = {\n id: fileNodeId,\n type: NodeType.FileNode,\n service: serviceName,\n path: relPath,\n ...(language ? { language } : {}),\n discoveredVia: 'static',\n }\n graph.addNode(fileNodeId, node)\n nodesAdded++\n }\n const containsId = extractedEdgeId(serviceNodeId, fileNodeId, EdgeType.CONTAINS)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: serviceNodeId,\n target: fileNodeId,\n type: EdgeType.CONTAINS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: relPath },\n }\n graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge)\n edgesAdded++\n }\n return { fileNodeId, nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport { promises as fs } from 'node:fs'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport Python from 'tree-sitter-python'\nimport Go from 'tree-sitter-go'\nimport type { GraphEdge } from '@neat.is/types'\nimport {\n EdgeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n fileId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport { isTestPath, type DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { loadSourceFiles, toPosix } from './calls/shared.js'\n\n// Phase 2 — import graph extraction (ADR-092, file-awareness.md §10). Walks\n// every source file's AST for import / require statements and emits IMPORTS\n// edges between FileNodes within the same service. Cross-service and\n// unresolvable specifiers are silent skips — Phase 3's CALLS producers cover\n// the cross-service case where an external-call pattern matches.\n\n// Same chunked-parse approach as calls/shared.ts — tree-sitter's bare string\n// path throws \"Invalid argument\" once the source clears ~32K code units. The\n// callback form streams the source in bounded slices instead.\nconst PARSE_CHUNK = 16384\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nfunction makeJsParser(): Parser {\n const p = new Parser()\n p.setLanguage(JavaScript)\n return p\n}\n\nfunction makePyParser(): Parser {\n const p = new Parser()\n p.setLanguage(Python)\n return p\n}\n\nfunction makeGoParser(): Parser {\n const p = new Parser()\n p.setLanguage(Go)\n return p\n}\n\n// ── string-literal text ────────────────────────────────────────────────────\n\n// The interior text of a string literal node, stripped of quote characters.\n// tree-sitter-javascript wraps the content in a `string_fragment` child;\n// fall back to slicing the raw text when that shape isn't present (e.g. an\n// empty string literal has no fragment child at all).\nfunction stringLiteralText(node: Parser.SyntaxNode): string | null {\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i)\n if (child?.type === 'string_fragment') return child.text\n }\n const raw = node.text\n if (raw.length >= 2) return raw.slice(1, -1)\n return raw.length === 0 ? null : ''\n}\n\nfunction clipSnippet(text: string): string {\n const oneLine = text.split('\\n')[0] ?? text\n return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine\n}\n\n// ── JS / TS import collection ──────────────────────────────────────────────\n\ninterface RawImport {\n specifier: string\n line: number // 1-indexed\n snippet: string\n}\n\nfunction collectGoImports(node: Parser.SyntaxNode, out: RawImport[]): void {\n if (node.type === 'import_spec') {\n const pathNode = node.childForFieldName('path')\n if (pathNode) {\n const specifier = pathNode.text.replace(/^`|`$/g, '').replace(/^\"|\"$/g, '')\n if (specifier) out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) })\n }\n return\n }\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) collectGoImports(child, out)\n }\n}\n\n// Walks the AST for `import ... from 'spec'` / `import 'spec'` (ES modules)\n// and `require('spec')` (CommonJS). Doesn't recurse into import_statement —\n// its only string child is the source specifier, never a nested import.\nfunction collectJsImports(node: Parser.SyntaxNode, out: RawImport[]): void {\n if (node.type === 'import_statement') {\n const source = node.childForFieldName('source')\n if (source) {\n const specifier = stringLiteralText(source)\n if (specifier) {\n out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) })\n }\n }\n return\n }\n\n if (node.type === 'call_expression') {\n const fn = node.childForFieldName('function')\n if (fn?.type === 'identifier' && fn.text === 'require') {\n const args = node.childForFieldName('arguments')\n const firstArg = args?.namedChild(0)\n if (firstArg?.type === 'string') {\n const specifier = stringLiteralText(firstArg)\n if (specifier) {\n out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) })\n }\n }\n }\n }\n\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) collectJsImports(child, out)\n }\n}\n\n// ── Python import collection ───────────────────────────────────────────────\n\ninterface RawPyImport {\n modulePath: string // dotted path with the leading dots stripped, e.g. 'utils.auth'\n level: number // count of leading dots; 0 = absolute import\n names: string[] // the imported names after `import` — each may be a submodule\n line: number\n snippet: string\n}\n\n// Collects the imported names from the tail of a `from`-import — the part\n// after the `import` keyword. Pushes each `name` (and the original name of an\n// `as`-aliased import); descends into a parenthesised list so `from app import\n// (config, db)` reads the same as the unparenthesised form. A wildcard `*`\n// has no name and is left for the module-file fallback in resolution.\nfunction collectImportedNames(node: Parser.SyntaxNode, out: string[]): void {\n if (node.type === 'aliased_import') {\n const nameNode = node.childForFieldName('name')\n if (nameNode) out.push(nameNode.text)\n return\n }\n if (node.type === 'dotted_name') {\n out.push(node.text)\n return\n }\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) collectImportedNames(child, out)\n }\n}\n\n// Walks the AST for `from X import a, b` / `from .X import a`. Bare `import X`\n// statements name modules without resolving to a single file (`import os.path`\n// doesn't tell us which file in `os` got used), so Phase 2 limits itself to\n// the `from`-form per the resolution rules in the contract. The imported names\n// matter: `from app import config` names the `config` submodule, not a symbol\n// on `app/__init__.py`, so resolution keys on them (see resolvePyImport).\nfunction collectPyImports(node: Parser.SyntaxNode, out: RawPyImport[]): void {\n if (node.type === 'import_from_statement') {\n let level = 0\n let modulePath = ''\n const names: string[] = []\n let pastFrom = false\n let pastImport = false\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i)\n if (!child) continue\n if (!pastFrom) {\n if (child.type === 'from') pastFrom = true\n continue\n }\n if (!pastImport) {\n if (child.type === 'import') {\n pastImport = true\n continue\n }\n // The `from`-module spec, before the `import` keyword.\n if (child.type === 'relative_import') {\n for (let j = 0; j < child.childCount; j++) {\n const rc = child.child(j)\n if (!rc) continue\n // tree-sitter-python groups every leading dot into a single\n // import_prefix node — `..` parses as one import_prefix with two\n // `.` children, not two import_prefix nodes. Count the `.` tokens,\n // not the prefix nodes, or multi-dot imports under-count `level`\n // and resolve onto the wrong (sometimes self) target (#457).\n if (rc.type === 'import_prefix') {\n for (let k = 0; k < rc.childCount; k++) {\n if (rc.child(k)?.type === '.') level++\n }\n } else if (rc.type === 'dotted_name') modulePath = rc.text\n }\n } else if (child.type === 'dotted_name') {\n modulePath = child.text\n }\n continue\n }\n // Past the `import` keyword — the imported names.\n collectImportedNames(child, names)\n }\n\n if (level > 0 || modulePath) {\n out.push({ modulePath, level, names, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) })\n }\n }\n\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) collectPyImports(child, out)\n }\n}\n\n// ── filesystem resolution helpers ──────────────────────────────────────────\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await fs.access(p)\n return true\n } catch {\n return false\n }\n}\n\n// An import that escapes the service directory (`../../other-service/x`)\n// isn't an intra-service edge — Phase 2 is scoped to within-service module\n// dependencies (file-awareness.md §10).\nfunction isWithinServiceDir(candidate: string, serviceDir: string): boolean {\n const rel = path.relative(serviceDir, candidate)\n return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel)\n}\n\nconst JS_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']\nconst JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`)\n\n// Try `base`, `base.<ext>`, and `base/index.<ext>` in TypeScript-resolution\n// order. Returns the service-relative posix path of the first hit.\nasync function firstExistingCandidate(\n base: string,\n serviceDir: string,\n): Promise<string | null> {\n for (const ext of JS_EXTENSIONS) {\n const candidate = base + ext\n if (isWithinServiceDir(candidate, serviceDir) && (await fileExists(candidate))) {\n return toPosix(path.relative(serviceDir, candidate))\n }\n }\n for (const indexFile of JS_INDEX_FILES) {\n const candidate = path.join(base, indexFile)\n if (isWithinServiceDir(candidate, serviceDir) && (await fileExists(candidate))) {\n return toPosix(path.relative(serviceDir, candidate))\n }\n }\n return null\n}\n\nexport interface TsPathConfig {\n paths: Record<string, string[]>\n baseDir: string // absolute directory compilerOptions.baseUrl resolves against\n}\n\n// `tsconfig.json` at the service root. The contract names scanPath as a\n// fallback location too, but every discovered service already carries its own\n// root — the scanPath fallback only matters for configs that live above the\n// service tree, which the resolver doesn't have a path back to from here.\nexport async function loadTsPathConfig(serviceDir: string): Promise<TsPathConfig | null> {\n const tsconfigPath = path.join(serviceDir, 'tsconfig.json')\n let raw: string\n try {\n raw = await fs.readFile(tsconfigPath, 'utf8')\n } catch {\n return null\n }\n try {\n const parsed = JSON.parse(raw) as {\n compilerOptions?: { paths?: Record<string, string[]>; baseUrl?: string }\n }\n const paths = parsed.compilerOptions?.paths\n if (!paths || Object.keys(paths).length === 0) return null\n const baseUrl = parsed.compilerOptions?.baseUrl\n return { paths, baseDir: baseUrl ? path.resolve(serviceDir, baseUrl) : serviceDir }\n } catch (err) {\n recordExtractionError('import alias resolution', tsconfigPath, err)\n return null\n }\n}\n\n// Resolve a bare specifier against `compilerOptions.paths`. Matches the first\n// alias whose pattern fits — exact (`@db`) or wildcard (`@db/*`) — and tries\n// each mapped target in declaration order. No match anywhere → null, the\n// silent-skip the contract calls for.\nasync function resolveTsAlias(\n specifier: string,\n config: TsPathConfig,\n serviceDir: string,\n): Promise<string | null> {\n for (const [pattern, targets] of Object.entries(config.paths)) {\n let suffix: string | null = null\n if (pattern === specifier) {\n suffix = ''\n } else if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -1) // keep the trailing '/'\n if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length)\n }\n if (suffix === null) continue\n\n for (const target of targets) {\n const targetBase = target.endsWith('/*') ? target.slice(0, -2) : target.replace(/\\*$/, '')\n const resolvedBase = path.resolve(config.baseDir, targetBase, suffix)\n const hit = await firstExistingCandidate(resolvedBase, serviceDir)\n if (hit) return hit\n // The candidate may already carry its own extension (`@db/mongo.ts`).\n if (isWithinServiceDir(resolvedBase, serviceDir) && (await fileExists(resolvedBase))) {\n return toPosix(path.relative(serviceDir, resolvedBase))\n }\n }\n }\n return null\n}\n\n// Resolves a JS/TS module specifier to a service-relative posix path, or\n// null when it names something outside the service (node_modules, Node\n// builtins, an alias with no tsconfig match, an escaping relative path).\n// Exported so the cross-file mongoose resolver (ADR-149) can reuse the same\n// specifier→FileNode resolution — extensions, index/barrel files, the .js→.ts\n// sibling convention — instead of re-implementing it. Relative specifiers\n// resolve without `tsPaths`; a bare specifier needs the alias table (pass null\n// to skip aliases).\nexport async function resolveJsImport(\n specifier: string,\n importerDir: string,\n serviceDir: string,\n tsPaths: TsPathConfig | null,\n): Promise<string | null> {\n if (!specifier) return null\n\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n const base = path.resolve(importerDir, specifier)\n const ext = path.extname(specifier)\n\n if (ext) {\n // `./foo.js` written against a `.ts` source — the TypeScript ESM\n // convention of naming the post-build extension in source. Try the\n // TS sibling before the literal path.\n if (ext === '.js' || ext === '.jsx') {\n const tsExt = ext === '.jsx' ? '.tsx' : '.ts'\n const tsSibling = base.slice(0, -ext.length) + tsExt\n if (isWithinServiceDir(tsSibling, serviceDir) && (await fileExists(tsSibling))) {\n return toPosix(path.relative(serviceDir, tsSibling))\n }\n }\n if (isWithinServiceDir(base, serviceDir) && (await fileExists(base))) {\n return toPosix(path.relative(serviceDir, base))\n }\n // `path.extname` reads the tail of a dotted module name — `foo.controller`,\n // `bar.model`, `x.service` (the NestJS / Angular / RealWorld convention) — as\n // an extension, but the file on disk is `foo.controller.ts`. When the literal\n // path doesn't exist and the \"extension\" isn't one we compile, the dot is part\n // of the module name: resolve the whole specifier as extensionless.\n if (!JS_EXTENSIONS.includes(ext)) {\n return firstExistingCandidate(base, serviceDir)\n }\n return null\n }\n\n return firstExistingCandidate(base, serviceDir)\n }\n\n // Bare specifier — only a registered TS path alias can make this\n // intra-service. Anything else is node_modules / a Node builtin.\n if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir)\n return null\n}\n\n// Resolves a Python `from`-import to the service-relative posix paths it\n// touches — one per imported module. Relative imports (level > 0) walk up from\n// the importing module's directory; absolute imports match against the service\n// source tree directly.\n//\n// `from app import config` names the `config` *submodule* (app/config.py), not\n// a symbol re-exported by app/__init__.py. Resolving the whole statement onto\n// the package's __init__.py was wrong: every intra-package import collapsed\n// onto one target and the per-(source,target) edge dedup then made the second\n// such dependency invisible. So we key on each imported name, resolving it to\n// its own module file, and fall back to the package/module file only for names\n// that aren't submodules (plain symbols, or a `*` wildcard).\nasync function resolvePyImport(\n imp: RawPyImport,\n importerPath: string,\n serviceDir: string,\n): Promise<string[]> {\n let baseDir: string\n if (imp.level > 0) {\n baseDir = path.dirname(importerPath)\n for (let i = 1; i < imp.level; i++) baseDir = path.dirname(baseDir)\n } else {\n baseDir = serviceDir\n }\n\n // The on-disk base of the `from`-module. `from app import x` → app/ ;\n // `from . import x` → the importer's own package directory (modulePath empty).\n const moduleBase = imp.modulePath\n ? path.join(baseDir, imp.modulePath.split('.').join('/'))\n : baseDir\n\n const resolved = new Set<string>()\n let needModuleFile = imp.names.length === 0 // wildcard / unparsed → the module itself\n\n for (const name of imp.names) {\n const submoduleFile = path.join(moduleBase, `${name}.py`)\n const subpackageInit = path.join(moduleBase, name, '__init__.py')\n if (isWithinServiceDir(submoduleFile, serviceDir) && (await fileExists(submoduleFile))) {\n resolved.add(toPosix(path.relative(serviceDir, submoduleFile)))\n } else if (isWithinServiceDir(subpackageInit, serviceDir) && (await fileExists(subpackageInit))) {\n resolved.add(toPosix(path.relative(serviceDir, subpackageInit)))\n } else {\n // Not a submodule — the name is a symbol defined in the module itself.\n needModuleFile = true\n }\n }\n\n if (needModuleFile) {\n // An empty modulePath (`from . import x`) has no module file of its own —\n // the package is the directory, so only its __init__.py applies.\n const moduleFileCandidates = imp.modulePath\n ? [`${moduleBase}.py`, path.join(moduleBase, '__init__.py')]\n : [path.join(moduleBase, '__init__.py')]\n for (const candidate of moduleFileCandidates) {\n if (isWithinServiceDir(candidate, serviceDir) && (await fileExists(candidate))) {\n resolved.add(toPosix(path.relative(serviceDir, candidate)))\n break\n }\n }\n }\n\n return [...resolved]\n}\n\nasync function resolveGoImport(\n specifier: string,\n modulePath: string,\n serviceDir: string,\n): Promise<string | null> {\n if (specifier !== modulePath && !specifier.startsWith(`${modulePath}/`)) return null\n const suffix = specifier === modulePath ? '' : specifier.slice(modulePath.length + 1)\n const dir = path.join(serviceDir, suffix)\n const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => [])\n const candidates = entries\n .filter((entry) => entry.isFile() && entry.name.endsWith('.go') && !entry.name.endsWith('_test.go'))\n .map((entry) => path.join(dir, entry.name))\n if (candidates.length !== 1) return null\n return toPosix(path.relative(serviceDir, candidates[0]!))\n}\n\n// ── edge emission ──────────────────────────────────────────────────────────\n\nfunction emitImportEdge(\n graph: NeatGraph,\n serviceName: string,\n importerFileId: string,\n importerRelPath: string,\n importeeRelPath: string,\n line: number,\n snippet: string,\n): number {\n const importeeFileId = fileId(serviceName, importeeRelPath)\n // Phase 1 enumerates every source file unconditionally; a resolved path\n // that isn't a FileNode is outside SERVICE_FILE_EXTENSIONS or otherwise\n // wasn't walked — not an intra-service module edge.\n if (!graph.hasNode(importeeFileId)) return 0\n\n const edgeId = extractedEdgeId(importerFileId, importeeFileId, EdgeType.IMPORTS)\n if (graph.hasEdge(edgeId)) return 0\n\n const edge: GraphEdge = {\n id: edgeId,\n source: importerFileId,\n target: importeeFileId,\n type: EdgeType.IMPORTS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: importerRelPath, line, snippet },\n }\n graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge)\n return 1\n}\n\n// ── producer ───────────────────────────────────────────────────────────────\n\nexport async function addImports(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const jsParser = makeJsParser()\n const pyParser = makePyParser()\n const goParser = makeGoParser()\n let edgesAdded = 0\n\n for (const service of services) {\n const tsPaths = await loadTsPathConfig(service.dir)\n const files = await loadSourceFiles(service.dir)\n\n for (const file of files) {\n // ADR-065 §1 — test-scope exclusion. The file stays a FileNode (Phase 1);\n // only its outbound module edges are filtered (file-awareness.md §10).\n if (isTestPath(file.path)) continue\n\n const relFile = toPosix(path.relative(service.dir, file.path))\n const importerFileId = fileId(service.pkg.name, relFile)\n const isPython = path.extname(file.path) === '.py'\n const isGo = path.extname(file.path) === '.go'\n\n // Ruby is a route-and-service-grain pilot (ADR-173): `.rb` files are\n // FileNodes and their routes.rb is read, but Ruby's autoload-driven\n // require graph isn't modelled yet — parsing it with the JS grammar would\n // fabricate nothing useful, so skip. Ruby import resolution is a later rung.\n if (path.extname(file.path) === '.rb') continue\n\n // PHP is a route-and-service-grain pilot (ADR-177): `.php` files are\n // FileNodes and Laravel's routes/*.php are read, but PHP's PSR-4 autoload\n // require graph isn't modelled yet — parsing it with the JS grammar would\n // fabricate nothing useful, so skip. PHP import resolution is a later rung.\n if (path.extname(file.path) === '.php') continue\n\n if (isGo) {\n let goImports: RawImport[] = []\n try {\n const tree = parseSource(goParser, file.content)\n collectGoImports(tree.rootNode, goImports)\n } catch (err) {\n recordExtractionError('import extraction', file.path, err)\n continue\n }\n const goMod = await fs.readFile(path.join(service.dir, 'go.mod'), 'utf8').catch(() => '')\n const modulePath = goMod.match(/^\\s*module\\s+(\\S+)\\s*$/m)?.[1]\n if (!modulePath) continue\n for (const imp of goImports) {\n const resolved = await resolveGoImport(imp.specifier, modulePath, service.dir)\n if (!resolved) continue\n edgesAdded += emitImportEdge(graph, service.pkg.name, importerFileId, relFile, resolved, imp.line, imp.snippet)\n }\n continue\n }\n\n if (isPython) {\n let pyImports: RawPyImport[] = []\n try {\n const tree = parseSource(pyParser, file.content)\n collectPyImports(tree.rootNode, pyImports)\n } catch (err) {\n recordExtractionError('import extraction', file.path, err)\n continue\n }\n for (const imp of pyImports) {\n const resolvedPaths = await resolvePyImport(imp, file.path, service.dir)\n for (const resolved of resolvedPaths) {\n edgesAdded += emitImportEdge(\n graph,\n service.pkg.name,\n importerFileId,\n relFile,\n resolved,\n imp.line,\n imp.snippet,\n )\n }\n }\n continue\n }\n\n let jsImports: RawImport[] = []\n try {\n const tree = parseSource(jsParser, file.content)\n collectJsImports(tree.rootNode, jsImports)\n } catch (err) {\n recordExtractionError('import extraction', file.path, err)\n continue\n }\n for (const imp of jsImports) {\n const resolved = await resolveJsImport(imp.specifier, path.dirname(file.path), service.dir, tsPaths)\n if (!resolved) continue\n edgesAdded += emitImportEdge(\n graph,\n service.pkg.name,\n importerFileId,\n relFile,\n resolved,\n imp.line,\n imp.snippet,\n )\n }\n }\n }\n\n return { nodesAdded: 0, edgesAdded }\n}\n","// Column-grain provenance fold (ADR-157). Columns are a provenanced attribute\n// list on a `sql-table` InfraNode, never their own nodes. This module is the one\n// place that folds a column presence onto that list; the graph mutation itself\n// stays with the two lifecycle authorities that call it — `ingest.ts` for the\n// OBSERVED read off `db.statement`, and `extract/calls/*` for the EXTRACTED read\n// off a schema definition (lifecycle.md §3). The divergence detector\n// (divergences.ts) only reads the list. Keeping the fold pure here (no graph\n// handle) lets extract call in without importing ingest — ingest already imports\n// extract, so the reverse would be a cycle.\n//\n// A column records each provenance it has been seen with in a deduped\n// `provenances` set: a column that is both declared and observed carries\n// `[EXTRACTED, OBSERVED]`, and the column-drift query (ADR-157 §4) reads the\n// declared set and the observed set straight off that one entry. `confidence` is\n// the strongest evidence's grade (max) — a column seen on both sides is the most\n// certain, and a one-sided (drift) column's confidence is just that side's grade.\n\nimport { Provenance, type ColumnAttr, type ProvenanceValue } from '@neat.is/types'\n\n// OBSERVED columns are read verbatim from a parameterized `db.statement`: the\n// value is redacted but the column NAME is present in the text, so the read is\n// near-certain. Graded just below 1.0 to acknowledge the parser's conservative\n// reach (it degrades on JOIN / subquery / `SELECT *`) rather than claim total\n// coverage. EXTRACTED columns carry their producer's grade instead.\nexport const OBSERVED_COLUMN_CONFIDENCE = 0.9\n\n// Deterministic, deduped provenance order so a node's serialized shape is stable\n// regardless of the order extract / ingest passes ran in. Alphabetical happens to\n// read EXTRACTED, INFERRED, OBSERVED, STALE.\nfunction normalizeProvenances(provenances: ProvenanceValue[]): ProvenanceValue[] {\n return [...new Set(provenances)].sort()\n}\n\n// Fold a set of column names onto a table node's `columns` list at one\n// provenance, returning a fresh array (pure — the caller writes it back). Column\n// names are lowercased so the declared read fuses with the observed read, which\n// already lowercases: an unquoted SQL identifier is case-folded by the database\n// anyway, so lowercasing both sides is the fusion-safe normalization and stops a\n// case difference from minting a phantom drift. An unseen column is added with the\n// one provenance; a column already present gains this provenance in its set\n// (deduped) and keeps the stronger confidence — never duplicated. Order-stable and\n// idempotent, so an OBSERVED fold and an EXTRACTED fold compose either way round.\nexport function foldColumns(\n existing: readonly ColumnAttr[] | undefined,\n names: readonly string[],\n provenance: ProvenanceValue,\n confidence: number,\n): ColumnAttr[] {\n const out: ColumnAttr[] = (existing ?? []).map((c) => ({\n ...c,\n provenances: [...c.provenances],\n }))\n const byName = new Map(out.map((c) => [c.name, c]))\n for (const raw of names) {\n const name = raw.toLowerCase()\n const prior = byName.get(name)\n if (!prior) {\n const col: ColumnAttr = { name, provenances: [provenance], confidence }\n byName.set(name, col)\n out.push(col)\n continue\n }\n if (!prior.provenances.includes(provenance)) {\n prior.provenances = normalizeProvenances([...prior.provenances, provenance])\n }\n if (confidence > prior.confidence) prior.confidence = confidence\n }\n return out\n}\n\n// Fold per-field write-SDK tags onto a table/collection node's `columns` list\n// (ADR-167), returning a fresh array — the parallel of `foldColumns`, kept\n// separate so `foldColumns` (shared by Drizzle / Prisma / SQLAlchemy / OBSERVED\n// ingest) stays byte-identical. `writes` maps a field name to the SDK(s) that\n// wrote it (`client` = firebase/firestore, `admin` = firebase-admin/firestore); it\n// merges into each column's `sdkWrites` set, deduped and order-stable the way\n// `provenances` is. Field names are lowercased to match `foldColumns` (which the\n// column entries went through first), so the tag lands on the right column. A\n// written field with no existing column entry is skipped — the columns fold runs\n// first with the same field set, so the entry is already present; this is the\n// defensive path. This is the declared interface ADR-169's field-guard policy\n// consumes (`col.sdkWrites ?? []`).\nexport function foldSdkWrites(\n existing: readonly ColumnAttr[] | undefined,\n writes: Readonly<Record<string, readonly ('client' | 'admin')[]>>,\n): ColumnAttr[] {\n const out: ColumnAttr[] = (existing ?? []).map((c) => ({\n ...c,\n provenances: [...c.provenances],\n ...(c.sdkWrites ? { sdkWrites: [...c.sdkWrites] } : {}),\n }))\n const byName = new Map(out.map((c) => [c.name, c]))\n for (const [raw, sdks] of Object.entries(writes)) {\n const col = byName.get(raw.toLowerCase())\n if (!col) continue\n const merged = new Set<'client' | 'admin'>([...(col.sdkWrites ?? []), ...sdks])\n col.sdkWrites = [...merged].sort()\n }\n return out\n}\n\n// Read helpers the divergence detector uses so the \"is this column declared /\n// observed\" test lives with the model rather than being re-derived per caller.\nexport function columnIsDeclared(col: ColumnAttr): boolean {\n return col.provenances.includes(Provenance.EXTRACTED)\n}\nexport function columnIsObserved(col: ColumnAttr): boolean {\n return col.provenances.includes(Provenance.OBSERVED)\n}\n","import type {\n BlastRadiusAffectedNode,\n BlastRadiusResult,\n DatabaseNode,\n ErrorEvent,\n GraphEdge,\n GraphNode,\n ObservedDependenciesResult,\n RootCauseResult,\n ServiceNode,\n TransitiveDependenciesResult,\n TransitiveDependency,\n} from '@neat.is/types'\nimport { codeFilepathOf, codeLinenoOf } from './ingest.js'\nimport {\n BlastRadiusResultSchema,\n EdgeType,\n NodeType,\n ObservedDependenciesResultSchema,\n PROV_RANK,\n Provenance,\n RootCauseResultSchema,\n TransitiveDependenciesResultSchema,\n} from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\nimport {\n checkCompatibility,\n checkNodeEngineConstraint,\n checkPackageConflict,\n compatPairs,\n nodeEngineConstraints,\n packageConflicts,\n} from './compat.js'\n\n// Contract anchors (see /docs/contracts.md + docs/contracts/provenance.md):\n// * Rule 2 — Coexistence: walk by provenance priority, never collapse edges.\n// * Rule 3 — FrontierNodes terminate traversal — edges to/from FrontierNodes\n// are skipped, not merely deprioritized. If a node's only neighbour is a\n// FrontierNode, traversal stops there. ADR-068 makes node-type the gating\n// property, independent of edge provenance.\n// * Rule 5 — Validate results against RootCauseResultSchema /\n// BlastRadiusResultSchema before returning.\n// * Rule 8 — No demo-name hardcoding: driver/engine identifiers come from\n// node properties + compatPairs(), never literals.\n// * ADR-029 — PROV_RANK is the canonical provenance ranking, imported\n// from @neat.is/types so consumers (traversal, MCP, policies) all agree.\n\nconst ROOT_CAUSE_MAX_DEPTH = 5\nconst BLAST_RADIUS_DEFAULT_DEPTH = 10\n\nfunction isFrontierNode(graph: NeatGraph, nodeId: string): boolean {\n if (!graph.hasNode(nodeId)) return false\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n return attrs.type === NodeType.FrontierNode\n}\n\n// Resolve a node on the walk path to the ServiceNode that carries the compat\n// evidence (declared dependencies + node engine). A ServiceNode resolves to\n// itself; a FileNode resolves to its owning service via the inbound\n// `service ──CONTAINS──▶ file` edge (file-awareness.md §2); a SymbolNode\n// resolves one containment level deeper, up the inbound CONTAINS chain\n// `symbol ◀─CONTAINS─ file ◀─CONTAINS─ service` (file-awareness.md §3, ADR-158\n// §7). In a file-first, symbol-deep graph the caller on the path is a File- or\n// SymbolNode, but the dependency declaration lives on the service that owns it —\n// the file or symbol stays on the traversal path, the service is only named as\n// the compat carrier. Anything else has no service to resolve to. Returns the\n// resolved ServiceNode's id + attributes, or null.\nfunction resolveOwningService(\n graph: NeatGraph,\n nodeId: string,\n): { id: string; svc: ServiceNode } | null {\n if (!graph.hasNode(nodeId)) return null\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n if (attrs.type === NodeType.ServiceNode) {\n return { id: nodeId, svc: attrs as ServiceNode }\n }\n // A FileNode is owned by its service directly; a SymbolNode is owned by its\n // file, which is in turn owned by the service. Walk the inbound CONTAINS edge\n // either way — the owner is a ServiceNode for a file (return it), or a FileNode\n // for a symbol (resolve that file the same way, one hop further).\n if (attrs.type === NodeType.FileNode || attrs.type === NodeType.SymbolNode) {\n for (const edgeId of graph.inboundEdges(nodeId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== EdgeType.CONTAINS) continue\n const owner = graph.getNodeAttributes(e.source) as GraphNode\n if (owner.type === NodeType.ServiceNode) {\n return { id: e.source, svc: owner as ServiceNode }\n }\n if (owner.type === NodeType.FileNode) {\n return resolveOwningService(graph, e.source)\n }\n }\n }\n return null\n}\n\n// Multiple edges between the same pair coexist by provenance (EXTRACTED next to\n// OBSERVED next to INFERRED). Traversal walks the system as the graph \"sees it\n// best\", so for any neighbour pair we pick the highest-provenance edge.\n// Edges connecting to FrontierNodes are skipped at the node level (ADR-068):\n// FrontierNodes are unresolved peers, traversal terminates at them rather than\n// pretending the path continues into unknown territory.\nfunction bestEdgeBySource(graph: NeatGraph, edgeIds: string[]): Map<string, GraphEdge> {\n const best = new Map<string, GraphEdge>()\n for (const id of edgeIds) {\n const e = graph.getEdgeAttributes(id) as GraphEdge\n if (isFrontierNode(graph, e.source)) continue\n const cur = best.get(e.source)\n if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {\n best.set(e.source, e)\n }\n }\n return best\n}\n\nfunction bestEdgeByTarget(graph: NeatGraph, edgeIds: string[]): Map<string, GraphEdge> {\n const best = new Map<string, GraphEdge>()\n for (const id of edgeIds) {\n const e = graph.getEdgeAttributes(id) as GraphEdge\n if (isFrontierNode(graph, e.target)) continue\n const cur = best.get(e.target)\n if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {\n best.set(e.target, e)\n }\n }\n return best\n}\n\n// Per-edge confidence is provenance × volume × recency × cleanliness.\n// * provenance gives a ceiling: OBSERVED 1.0, INFERRED 0.7, EXTRACTED 0.5,\n// STALE 0.3.\n// * volume: log-scaled span count, saturating quickly so 1 span ≈ 0.55 and\n// ~1k spans ≈ 1.0.\n// * recency: 1.0 within an hour; decays toward 0.5 by 24h, toward 0.3 past.\n// * cleanliness: error rate above ~10% pulls the score down — a flapping\n// edge with thousands of spans shouldn't outrank a clean low-traffic one.\n// Bounded to [0, 1]. Walks of multiple edges multiply per-edge confidences.\nconst PROVENANCE_CEILING: Record<string, number> = {\n OBSERVED: 1.0,\n INFERRED: 0.7,\n EXTRACTED: 0.5,\n STALE: 0.3,\n}\n\nfunction volumeWeight(spanCount: number | undefined): number {\n if (!spanCount || spanCount <= 0) return 0.5\n // log10 saturating around ~1000 spans → ~1.0.\n const w = 0.5 + Math.log10(spanCount + 1) / 3\n return Math.min(1, w)\n}\n\nfunction recencyWeight(ageMs: number | undefined): number {\n if (ageMs === undefined) return 0.8\n const hour = 60 * 60 * 1000\n if (ageMs <= hour) return 1.0\n if (ageMs <= 24 * hour) {\n const t = (ageMs - hour) / (23 * hour)\n return 1.0 - 0.5 * t\n }\n return 0.3\n}\n\nfunction cleanlinessWeight(spanCount: number | undefined, errorCount: number | undefined): number {\n if (!spanCount || spanCount <= 0) return 1\n const rate = (errorCount ?? 0) / spanCount\n if (rate <= 0.01) return 1\n if (rate >= 0.5) return 0.3\n return 1 - rate * 1.4\n}\n\nexport function confidenceForEdge(edge: GraphEdge, now = Date.now()): number {\n const ceiling = PROVENANCE_CEILING[edge.provenance] ?? 0.5\n\n // No runtime signal yet → the provenance ceiling is all we have. This keeps\n // EXTRACTED-only graphs returning the same coarse 0.3/0.5/0.7/1.0 ladder\n // they always have, while letting OBSERVED edges with real OTel data move\n // off the ceiling once ingest starts populating signal counters.\n const spanCount = edge.signal?.spanCount ?? edge.callCount\n const ageMs = edge.signal?.lastObservedAgeMs ?? lastObservedAge(edge, now)\n if (spanCount === undefined && ageMs === undefined && edge.signal === undefined) {\n return ceiling\n }\n\n const v = volumeWeight(spanCount)\n const r = recencyWeight(ageMs)\n const c = cleanlinessWeight(spanCount, edge.signal?.errorCount)\n return Math.max(0, Math.min(1, ceiling * v * r * c))\n}\n\nfunction lastObservedAge(edge: GraphEdge, now: number): number | undefined {\n if (!edge.lastObserved) return undefined\n const t = Date.parse(edge.lastObserved)\n if (!Number.isFinite(t)) return undefined\n return Math.max(0, now - t)\n}\n\n// Path-level confidence is the *product* of per-edge confidences (ADR-036).\n// Each hop is independent evidence and uncertainty compounds — a 3-hop path\n// of edges at confidence 0.8 each gives 0.512, not 0.8. Multiplying punishes\n// long walks accordingly, which is the contract's intent: traversal should\n// surface the cumulative trust the graph actually has, not the weakest link\n// alone.\nfunction confidenceFromMix(edges: GraphEdge[], now = Date.now()): number {\n if (edges.length === 0) return 1.0\n let product = 1\n for (const e of edges) {\n product *= confidenceForEdge(e, now)\n }\n return Math.max(0, Math.min(1, product))\n}\n\ninterface Walk {\n path: string[]\n edges: GraphEdge[]\n}\n\n// DFS along incoming edges from start, depth-bounded. Returns the longest path\n// reachable, picking best-provenance edges per neighbour pair so the walk\n// reflects the system as the graph knows it most reliably.\nfunction longestIncomingWalk(graph: NeatGraph, start: string, maxDepth: number): Walk {\n let best: Walk = { path: [start], edges: [] }\n const visited = new Set<string>([start])\n\n function step(node: string, path: string[], edges: GraphEdge[]): void {\n if (path.length > best.path.length) {\n best = { path: [...path], edges: [...edges] }\n }\n if (path.length - 1 >= maxDepth) return\n\n const incoming = bestEdgeBySource(graph, graph.inboundEdges(node))\n for (const [srcId, edge] of incoming) {\n if (visited.has(srcId)) continue\n visited.add(srcId)\n path.push(srcId)\n edges.push(edge)\n step(srcId, path, edges)\n path.pop()\n edges.pop()\n visited.delete(srcId)\n }\n }\n\n step(start, [start], [])\n return best\n}\n\n// Per-shape match result. Each shape walks the same incoming `walk.path` but\n// looks for a different class of incompatibility. Adding a new shape (e.g. a\n// future ConfigNode \"missing required env var\" rule) is one entry in\n// `rootCauseShapes` plus its match function — no restructure to getRootCause.\ninterface RootCauseMatch {\n rootCauseNode: string\n rootCauseReason: string\n fixRecommendation?: string\n}\n\ntype RootCauseShape = (\n graph: NeatGraph,\n origin: GraphNode,\n walk: Walk,\n) => RootCauseMatch | null\n\n// DatabaseNode origin → driver/engine compat (the original v0.1.x behavior,\n// preserved verbatim). The walk ignores non-ServiceNodes; the first upstream\n// service whose declared driver fails compat against the origin DB's\n// (engine, engineVersion) wins.\nfunction databaseRootCauseShape(\n graph: NeatGraph,\n origin: GraphNode,\n walk: Walk,\n): RootCauseMatch | null {\n const targetDb = origin as DatabaseNode\n // Pairs that could possibly hit on this engine — narrowed once outside the\n // walk so we don't re-scan the matrix for every service we visit.\n const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine)\n if (candidatePairs.length === 0) return null\n\n for (const id of walk.path) {\n // The compat carrier is a service: a ServiceNode resolves to itself, a\n // FileNode on the path resolves to its owning service via CONTAINS\n // (file-awareness.md §2). In a file-first graph the caller on the walk is\n // the FileNode that holds the CALLS edge, but the declared driver lives on\n // the service that owns it.\n const owner = resolveOwningService(graph, id)\n if (!owner) continue\n const { id: serviceId, svc } = owner\n const deps = svc.dependencies ?? {}\n for (const pair of candidatePairs) {\n const declared = deps[pair.driver]\n if (!declared) continue\n const result = checkCompatibility(\n pair.driver,\n declared,\n targetDb.engine,\n targetDb.engineVersion,\n )\n if (!result.compatible) {\n return {\n rootCauseNode: serviceId,\n rootCauseReason: result.reason ?? 'incompatible driver',\n ...(result.minDriverVersion\n ? {\n fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`,\n }\n : {}),\n }\n }\n }\n }\n return null\n}\n\n// ServiceNode origin → node-engine + package-conflict shapes from compat.ts.\n// The check is over each ServiceNode along the incoming walk (the origin\n// itself + any upstream callers): a node-engine constraint failing against\n// the service's `engines.node`, or a package-conflict where a declared dep\n// requires a peer at a higher version than the service has.\nfunction serviceRootCauseShape(\n graph: NeatGraph,\n _origin: GraphNode,\n walk: Walk,\n): RootCauseMatch | null {\n for (const id of walk.path) {\n // ServiceNode → itself; FileNode → owning service via CONTAINS\n // (file-awareness.md §2). The compat evidence (declared deps, node engine)\n // lives on the service, even when the caller on the walk is a file.\n const owner = resolveOwningService(graph, id)\n if (!owner) continue\n const { id: serviceId, svc } = owner\n const deps = svc.dependencies ?? {}\n const serviceNodeEngine = svc.nodeEngine\n\n for (const constraint of nodeEngineConstraints()) {\n const declared = deps[constraint.package]\n if (!declared) continue\n const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine)\n if (!result.compatible && result.reason) {\n return {\n rootCauseNode: serviceId,\n rootCauseReason: result.reason,\n ...(result.requiredNodeVersion\n ? {\n fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`,\n }\n : {}),\n }\n }\n }\n\n for (const conflict of packageConflicts()) {\n const declared = deps[conflict.package]\n if (!declared) continue\n const requiredDeclared = deps[conflict.requires.name]\n const result = checkPackageConflict(conflict, declared, requiredDeclared)\n if (!result.compatible && result.reason) {\n return {\n rootCauseNode: serviceId,\n rootCauseReason: result.reason,\n fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`,\n }\n }\n }\n }\n return null\n}\n\n// FileNode origin → resolve the file to its owning service (file-awareness.md\n// §2) and run the service shape. In a file-first graph an error can land on a\n// FileNode (the file that holds the failing CALLS edge); the incompatibility,\n// if any, is still a property of the service that owns the file's declared\n// dependencies. The owning service is folded into the origin's position so the\n// service shape scans it alongside the upstream walk.\nfunction fileRootCauseShape(\n graph: NeatGraph,\n origin: GraphNode,\n walk: Walk,\n): RootCauseMatch | null {\n const owner = resolveOwningService(graph, origin.id)\n if (!owner) return null\n return serviceRootCauseShape(graph, owner.svc, walk)\n}\n\n// SymbolNode origin → resolve the symbol to its owning service through the\n// inbound CONTAINS chain (symbol ◀─CONTAINS─ file ◀─CONTAINS─ service,\n// file-awareness.md §3 / ADR-158 §7) and run the service shape — one grain finer\n// than the FileNode case. A failure can land on a SymbolNode (the function that\n// holds the failing edge); the incompatibility, if any, is still a property of\n// the service that owns the symbol's declared dependencies. The symbol stays the\n// origin on the traversal path; the service is only the resolved carrier.\nfunction symbolRootCauseShape(\n graph: NeatGraph,\n origin: GraphNode,\n walk: Walk,\n): RootCauseMatch | null {\n const owner = resolveOwningService(graph, origin.id)\n if (!owner) return null\n return serviceRootCauseShape(graph, owner.svc, walk)\n}\n\n// Dispatch by origin node type per ADR-037. Origin types not present here\n// (InfraNode, ConfigNode, FrontierNode) cleanly return null — getRootCause\n// needs an explicit shape to know what an \"incompatibility\" looks like for\n// that origin, and those types don't have one yet. Adding the SymbolNode shape\n// (ADR-158 §7) is one entry: symbols ride the same service-carrier resolution\n// files do, one containment level deeper.\nconst rootCauseShapes: Partial<Record<GraphNode['type'], RootCauseShape>> = {\n [NodeType.DatabaseNode]: databaseRootCauseShape,\n [NodeType.ServiceNode]: serviceRootCauseShape,\n [NodeType.FileNode]: fileRootCauseShape,\n [NodeType.SymbolNode]: symbolRootCauseShape,\n}\n\nexport function getRootCause(\n graph: NeatGraph,\n errorNodeId: string,\n errorEvent?: ErrorEvent,\n incidents?: ErrorEvent[],\n): RootCauseResult | null {\n if (!graph.hasNode(errorNodeId)) return null\n const origin = graph.getNodeAttributes(errorNodeId) as GraphNode\n const shape = rootCauseShapes[origin.type]\n\n if (shape) {\n const walk = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH)\n const match = shape(graph, origin, walk)\n if (match) {\n const reason = errorEvent\n ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})`\n : match.rootCauseReason\n\n // Schema-validate before return (ADR-036, #139). A drift in the result\n // shape becomes a runtime throw at the call site rather than a silently\n // malformed payload reaching MCP / REST consumers.\n return RootCauseResultSchema.parse({\n rootCauseNode: match.rootCauseNode,\n rootCauseReason: reason,\n traversalPath: walk.path,\n edgeProvenances: walk.edges.map((e) => e.provenance),\n confidence: confidenceFromMix(walk.edges),\n fixRecommendation: match.fixRecommendation,\n })\n }\n }\n\n // A service surfacing a failure may be the entry point of a cross-service\n // 500 that actually originates downstream. Nothing calls the entry service,\n // so the incoming walk above is empty — but its own OBSERVED CALLS edge to\n // the callee carries the failure. Follow that outbound failing CALLS chain to\n // the real culprit's handler before self-attributing the caller's mislabelled\n // CLIENT span (#589). Only null-returns here when no downstream call is\n // failing, i.e. the failure is in process at the origin.\n if (origin.type === NodeType.ServiceNode) {\n const crossService = crossServiceRootCause(graph, errorNodeId, incidents, errorEvent)\n if (crossService) return crossService\n }\n\n // No graph edge carried an incompatibility and no downstream call is failing —\n // but a service can fail in process (a 500 thrown inside its own handler)\n // without that failure ever crossing an edge, so the walk above sees a\n // healthy-looking node. The recorded incident store is the OBSERVED evidence\n // the graph can't carry: it localizes the failure to the file:line / route the\n // failing span captured. Consulting it here keeps root-cause useful for the\n // in-process case instead of reporting \"healthy\" over a pile of 500s (#584).\n return rootCauseFromIncidents(errorNodeId, incidents, errorEvent)\n}\n\n// OBSERVED-grade confidence for an incident-localized cause. The incident is a\n// real captured runtime fact (where the failure surfaced), but it names the\n// surface, not a proven upstream incompatibility — so it sits below an\n// edge-walked compat result yet well above an EXTRACTED guess.\nconst INCIDENT_ROOT_CAUSE_CONFIDENCE = 0.6\n\n// Match an incident to the queried node the same way the REST incident-history\n// read does (api.ts): an exact affectedNode hit, or a service match when the\n// node is the service the incident was recorded against. A file-grained\n// affectedNode (file:<svc>:<path>) still matches the owning service this way.\nfunction incidentMatchesNode(ev: ErrorEvent, nodeId: string): boolean {\n return ev.affectedNode === nodeId || ev.service === nodeId.replace(/^service:/, '')\n}\n\n// A failure localized to a node through the incident store: which node carries\n// the cause, the human reason, the file the failure surfaced in (when the\n// incident captured a `code.*` call site), and the derived fix.\ninterface IncidentLocalization {\n rootCauseNode: string\n rootCauseReason: string\n // The FileNode the failure surfaced in, present only when the incident\n // localized to a file grain. Callers walk node → file as a single OBSERVED\n // hop when this is set.\n fileNode?: string\n fixRecommendation?: string\n}\n\n// Pick the most recent incident affecting `nodeId` and localize the failure to\n// the file:line / route it captured. Returns null when no incident touches the\n// node. Shared by the in-process fallback and the cross-service chain (#589) so\n// both describe a culprit's handler the same way.\nfunction localizeFromIncidents(\n nodeId: string,\n incidents: ErrorEvent[] | undefined,\n errorEvent: ErrorEvent | undefined,\n): IncidentLocalization | null {\n const pool = incidents && incidents.length > 0 ? incidents : errorEvent ? [errorEvent] : []\n const relevant = pool.filter((ev) => incidentMatchesNode(ev, nodeId))\n if (relevant.length === 0) return null\n\n // Most recent incident is the representative; ISO timestamps sort lexically.\n const latest = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0]!\n const attrs = latest.attributes ?? {}\n // Read the call site through the shared dual-name helper (semconv ≥1.33 stable\n // names + the prior ones), so incident localization survives either generation.\n const filepath = codeFilepathOf(attrs)\n const lineno = codeLinenoOf(attrs)\n const route = typeof attrs['http.route'] === 'string' ? attrs['http.route'] : undefined\n const location = filepath ? `${filepath}${lineno !== undefined ? `:${lineno}` : ''}` : undefined\n\n // Count the incidents of *this* failure mode, not every incident on the node.\n // The reason names one message (`latest.errorMessage`); pairing it with the\n // node's total incident count reads as though that one error happened N times\n // when the node may be failing several different ways. Scope the count to the\n // records sharing this message so \"3 recorded incidents\" means three of the\n // failure the reason actually describes (issue #624).\n const sameMode = relevant.filter((ev) => ev.errorMessage === latest.errorMessage)\n const count = sameMode.length\n const tail = count > 1 ? ` (${count} recorded incidents)` : ' (1 recorded incident)'\n const reasonParts = [`${latest.service}: ${latest.errorMessage}`]\n if (location) reasonParts.push(`surfaced at ${location}`)\n const rootCauseReason = `${reasonParts.join(' — ')}${tail}`\n\n // When the incident localized to a file (affectedNode is a file id), name\n // that file as the root cause. The \"edge\" the caller walks is the captured\n // runtime attribution; OBSERVED is honest because the file came from a real\n // `code.*` on the failing span. Otherwise the cause sits on the node itself.\n const localizesToFile = latest.affectedNode !== nodeId && latest.affectedNode.startsWith('file:')\n const fileNode = localizesToFile ? latest.affectedNode : undefined\n\n const fixRecommendation = location\n ? `Inspect ${location}${route ? ` handling ${route}` : ''}`\n : route\n ? `Inspect ${latest.service}'s handler for ${route}`\n : undefined\n\n return {\n rootCauseNode: fileNode ?? nodeId,\n rootCauseReason,\n ...(fileNode ? { fileNode } : {}),\n ...(fixRecommendation ? { fixRecommendation } : {}),\n }\n}\n\n// Build a root-cause result from the recorded incident store when the graph\n// walk found nothing. Localizes the failure to the queried node itself (or the\n// file it surfaced in). Returns null when no incident touches the node — the\n// honest \"nothing to say\" answer.\nfunction rootCauseFromIncidents(\n nodeId: string,\n incidents: ErrorEvent[] | undefined,\n errorEvent: ErrorEvent | undefined,\n): RootCauseResult | null {\n const loc = localizeFromIncidents(nodeId, incidents, errorEvent)\n if (!loc) return null\n\n const traversalPath = loc.fileNode ? [nodeId, loc.fileNode] : [nodeId]\n const edgeProvenances = loc.fileNode ? [Provenance.OBSERVED] : []\n\n return RootCauseResultSchema.parse({\n rootCauseNode: loc.rootCauseNode,\n rootCauseReason: loc.rootCauseReason,\n traversalPath,\n edgeProvenances,\n confidence: INCIDENT_ROOT_CAUSE_CONFIDENCE,\n ...(loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}),\n })\n}\n\n// A CALLS edge counts as failing when its OBSERVED signal recorded at least one\n// error. This is the signal the cross-service chain follows: the caller's call\n// to the callee returned a 5xx (#589).\nfunction isFailingCallEdge(e: GraphEdge): boolean {\n return e.type === EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0\n}\n\n// Every node id that can originate an outbound CALLS edge on a service's behalf:\n// the service itself, each FileNode it CONTAINS, and each SymbolNode those files\n// CONTAIN. A file-first graph anchors the caller's CALLS edge on the call-site\n// file (file-awareness.md §4); one grain finer, an OBSERVED CALLS edge lands on\n// the calling SymbolNode (ADR-158 §4), so an entry service's failing call may\n// hang off a file it owns or a symbol that file owns, not the bare service node.\n// Descending the two CONTAINS levels keeps the cross-service chain symbol-aware\n// without the reasoning ever branching on grain.\nfunction callSourcesForService(graph: NeatGraph, serviceId: string): string[] {\n const ids = [serviceId]\n for (const edgeId of graph.outboundEdges(serviceId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== EdgeType.CONTAINS) continue\n const tgt = graph.getNodeAttributes(e.target) as GraphNode\n if (tgt.type !== NodeType.FileNode) continue\n ids.push(e.target)\n for (const symEdgeId of graph.outboundEdges(e.target)) {\n const se = graph.getEdgeAttributes(symEdgeId) as GraphEdge\n if (se.type !== EdgeType.CONTAINS) continue\n const sym = graph.getNodeAttributes(se.target) as GraphNode\n if (sym.type === NodeType.SymbolNode) ids.push(se.target)\n }\n }\n return ids\n}\n\n// Did edge `e` to service `id` beat the current best failing call? Most recorded\n// errors win; ties break on PROV_RANK, then target id — deterministic.\nfunction failingCallDominates(\n e: GraphEdge,\n id: string,\n curEdge: GraphEdge,\n curId: string,\n): boolean {\n const ec = e.signal?.errorCount ?? 0\n const cc = curEdge.signal?.errorCount ?? 0\n if (ec !== cc) return ec > cc\n if (PROV_RANK[e.provenance] !== PROV_RANK[curEdge.provenance]) {\n return PROV_RANK[e.provenance] > PROV_RANK[curEdge.provenance]\n }\n return id < curId\n}\n\n// The dominant failing outbound CALLS from a service: among the service's own\n// edges and those of the files it owns, the failing CALLS edge to another\n// service with the most recorded errors. Returns the next-hop service id and the\n// edge, or null when no downstream call is failing — meaning the failure is in\n// process here, not relayed from deeper.\nfunction dominantFailingCall(\n graph: NeatGraph,\n serviceId: string,\n visited: Set<string>,\n): { nextService: string; edge: GraphEdge } | null {\n let best: { nextService: string; edge: GraphEdge } | null = null\n for (const src of callSourcesForService(graph, serviceId)) {\n for (const edgeId of graph.outboundEdges(src)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (!isFailingCallEdge(e)) continue\n if (isFrontierNode(graph, e.target)) continue\n const owner = resolveOwningService(graph, e.target)\n if (!owner || visited.has(owner.id)) continue\n if (!best || failingCallDominates(e, owner.id, best.edge, best.nextService)) {\n best = { nextService: owner.id, edge: e }\n }\n }\n }\n return best\n}\n\n// Walk the failing CALLS chain outbound from an entry service to the deepest\n// still-failing callee — the service whose own downstream calls are clean and\n// whose handler therefore threw (#589). Returns the path of service ids, the\n// failing edges along it, and the culprit, or null when nothing downstream is\n// failing.\nfunction followFailingCallChain(\n graph: NeatGraph,\n originServiceId: string,\n maxDepth: number,\n): { path: string[]; edges: GraphEdge[]; culprit: string } | null {\n const path = [originServiceId]\n const edges: GraphEdge[] = []\n const visited = new Set<string>([originServiceId])\n let current = originServiceId\n\n for (let depth = 0; depth < maxDepth; depth++) {\n const hop = dominantFailingCall(graph, current, visited)\n if (!hop) break\n path.push(hop.nextService)\n edges.push(hop.edge)\n visited.add(hop.nextService)\n current = hop.nextService\n }\n\n if (edges.length === 0) return null\n return { path, edges, culprit: current }\n}\n\n// Localize a cross-service failure (#589). An entry ServiceNode surfaces a 500\n// that originates downstream: follow the failing CALLS chain to the culprit and\n// describe its handler, never the caller's mis-attributed CLIENT span. Returns\n// null when no outbound call is failing — the failure is in process here and the\n// caller falls through to the origin's own incident store.\nfunction crossServiceRootCause(\n graph: NeatGraph,\n originId: string,\n incidents: ErrorEvent[] | undefined,\n errorEvent: ErrorEvent | undefined,\n): RootCauseResult | null {\n const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH)\n if (!chain) return null\n\n const culprit = chain.culprit\n const path = [...chain.path]\n const edgeProvenances = chain.edges.map((e) => e.provenance)\n\n // Cross-service confidence cascades over the failing CALLS edges and the\n // incident-localization hop, so it lands below an edge-walked compat result.\n const baseConfidence = confidenceFromMix(chain.edges)\n const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE))\n\n const loc = localizeFromIncidents(culprit, incidents, errorEvent)\n if (loc) {\n let rootCauseNode = culprit\n if (loc.fileNode) {\n path.push(loc.fileNode)\n edgeProvenances.push(Provenance.OBSERVED)\n rootCauseNode = loc.fileNode\n }\n return RootCauseResultSchema.parse({\n rootCauseNode,\n rootCauseReason: loc.rootCauseReason,\n traversalPath: path,\n edgeProvenances,\n confidence,\n ...(loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}),\n })\n }\n\n // No recorded incident for the culprit — still better than blaming the caller.\n // Name the culprit service and read the reason off the failing edge.\n const lastEdge = chain.edges[chain.edges.length - 1]!\n const errs = lastEdge.signal?.errorCount ?? 0\n const culpritName = culprit.replace(/^service:/, '')\n return RootCauseResultSchema.parse({\n rootCauseNode: culprit,\n rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? '' : 's'})`,\n traversalPath: path,\n edgeProvenances,\n confidence,\n fixRecommendation: `Inspect ${culpritName}'s failing handler`,\n })\n}\n\n// BFS along *inbound* edges from origin — the origin's dependents, i.e. what\n// breaks if the origin changes or fails (get-blast-radius.md, superseding\n// ADR-038's outbound direction). An edge `A ──depends-on──▶ B` means A breaks\n// when B changes, so the blast radius of B walks back along inbound edges to A\n// and everything that transitively depends on it. For an inbound edge the\n// neighbour is the edge's `source` (the dependent), so selection uses\n// bestEdgeBySource — the same machinery getRootCause walks inbound with.\n// Records each reachable dependent with the shortest distance back to origin\n// and the provenance of the edge that brought us to it. A sink (a database,\n// shared lib, leaf util) has no outbound edges but does have inbound ones, so\n// this is what makes its blast radius non-empty.\nexport function getBlastRadius(\n graph: NeatGraph,\n nodeId: string,\n maxDepth = BLAST_RADIUS_DEFAULT_DEPTH,\n): BlastRadiusResult {\n if (!graph.hasNode(nodeId)) {\n return BlastRadiusResultSchema.parse({ origin: nodeId, affectedNodes: [], totalAffected: 0 })\n }\n\n // Each frame carries its full predecessor chain so the affected-node payload\n // can surface `path` (origin → ... → nodeId) and `confidence` (cascaded over\n // every edge along that path). The BFS visits each reachable node once on\n // its shortest-distance path; later frames at greater distance are dropped.\n interface Frame {\n nodeId: string\n distance: number\n path: string[]\n pathEdges: GraphEdge[]\n }\n\n const seen = new Map<string, BlastRadiusAffectedNode>()\n const queue: Frame[] = [{ nodeId, distance: 0, path: [nodeId], pathEdges: [] }]\n const enqueued = new Set<string>([nodeId])\n\n while (queue.length > 0) {\n const frame = queue.shift()!\n if (frame.distance > 0 && frame.pathEdges.length > 0) {\n const lastEdge = frame.pathEdges[frame.pathEdges.length - 1]!\n // Blast radius KEEPS CONTAINS: walked inbound, `file ◀─CONTAINS─ service`\n // means the service owns an affected file, so the service is genuinely in\n // the blast radius (file-awareness §36 — file-grained dependents plus the\n // owning service). Only get_dependencies filters CONTAINS (ADR-140), where\n // it's walked outbound and a service doesn't depend on its own files.\n seen.set(frame.nodeId, {\n nodeId: frame.nodeId,\n distance: frame.distance,\n edgeProvenance: lastEdge.provenance,\n path: frame.path,\n confidence: confidenceFromMix(frame.pathEdges),\n })\n }\n if (frame.distance >= maxDepth) continue\n\n const incoming = bestEdgeBySource(graph, graph.inboundEdges(frame.nodeId))\n for (const [srcId, edge] of incoming) {\n if (enqueued.has(srcId)) continue\n enqueued.add(srcId)\n queue.push({\n nodeId: srcId,\n distance: frame.distance + 1,\n path: [...frame.path, srcId],\n pathEdges: [...frame.pathEdges, edge],\n })\n }\n }\n\n const affectedNodes = [...seen.values()].sort(\n (a, b) => a.distance - b.distance || a.nodeId.localeCompare(b.nodeId),\n )\n return BlastRadiusResultSchema.parse({\n origin: nodeId,\n affectedNodes,\n totalAffected: affectedNodes.length,\n })\n}\n\n// Default + max depth for transitive get_dependencies (issue #144). Default\n// 3 keeps the output legible at the agent layer; the contract caps the\n// caller-supplied value at 10 to prevent BFS blow-up on dense graphs.\nexport const TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH = 3\nexport const TRANSITIVE_DEPENDENCIES_MAX_DEPTH = 10\n\n// Transitive get_dependencies (ADR-039 / #144). BFS outbound from origin to\n// `depth` hops, returning a flat list with distance, edgeType, and provenance\n// per dependency. Origin is never in the list. Direct-only consumers pass\n// depth=1; the MCP get_dependencies tool defaults to 3.\n//\n// Reuses bestEdgeByTarget (FRONTIER filtered, PROV_RANK-best per pair) so\n// dedup behavior matches the rest of traversal. Result is schema-validated\n// before return per ADR-036 §Result schema validation.\nexport function getTransitiveDependencies(\n graph: NeatGraph,\n nodeId: string,\n depth: number = TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH,\n): TransitiveDependenciesResult {\n if (!graph.hasNode(nodeId)) {\n return TransitiveDependenciesResultSchema.parse({\n origin: nodeId,\n depth,\n dependencies: [],\n total: 0,\n })\n }\n\n interface Frame {\n nodeId: string\n distance: number\n edge: GraphEdge | null\n }\n\n const seen = new Map<string, TransitiveDependency>()\n const queue: Frame[] = [{ nodeId, distance: 0, edge: null }]\n const enqueued = new Set<string>([nodeId])\n\n while (queue.length > 0) {\n const frame = queue.shift()!\n // Traverse THROUGH CONTAINS to reach a service's file-grained targets, but\n // never REPORT a CONTAINS edge as a dependency: a service doesn't depend on\n // its own files (file-awareness §36 refinement, ADR-140). The real target\n // reached via a genuine dependency edge downstream still surfaces.\n if (frame.distance > 0 && frame.edge && frame.edge.type !== EdgeType.CONTAINS) {\n seen.set(frame.nodeId, {\n nodeId: frame.nodeId,\n distance: frame.distance,\n edgeType: frame.edge.type,\n provenance: frame.edge.provenance,\n })\n }\n if (frame.distance >= depth) continue\n\n const outgoing = bestEdgeByTarget(graph, graph.outboundEdges(frame.nodeId))\n for (const [tgtId, edge] of outgoing) {\n if (enqueued.has(tgtId)) continue\n enqueued.add(tgtId)\n queue.push({ nodeId: tgtId, distance: frame.distance + 1, edge })\n }\n }\n\n const dependencies = [...seen.values()].sort(\n (a, b) => a.distance - b.distance || a.nodeId.localeCompare(b.nodeId),\n )\n return TransitiveDependenciesResultSchema.parse({\n origin: nodeId,\n depth,\n dependencies,\n total: dependencies.length,\n })\n}\n\n// Observed-only dependencies (issue #578). \"What does this node actually call at\n// runtime?\" — its OBSERVED outbound edges, file-grained.\n//\n// The subtlety the previous edges-only query missed: the call-site processor\n// lands OBSERVED CALLS on the FileNode that made the call, not on the owning\n// ServiceNode (file-awareness §4). So a query that starts at a ServiceNode sees\n// only its structural `CONTAINS` edges and reports \"no runtime traffic,\" while\n// the real dependency sits one hop away on a file it owns. When the origin is a\n// ServiceNode we therefore also read the OBSERVED outbound of the FileNodes it\n// `CONTAINS` and surface those file→target edges. This is not a service rollup\n// (file-awareness §3): the edges stay file-grained with the owning file as the\n// source; the service is only the grouping the query entered through.\n//\n// `observed` and `inboundObservedCount` separate two cases the old copy\n// conflated: a pure receiver — a node runtime hits but which calls nothing\n// downstream — has zero dependencies yet is plainly seen by OTel, so the caller\n// must not ask \"is OTel running?\" at it. That question is honest only when there\n// is no OBSERVED traffic at all and EXTRACTED outbound edges exist\n// (`hasExtractedOutbound`).\nexport function getObservedDependencies(\n graph: NeatGraph,\n nodeId: string,\n): ObservedDependenciesResult {\n if (!graph.hasNode(nodeId)) {\n return ObservedDependenciesResultSchema.parse({\n origin: nodeId,\n dependencies: [],\n observed: false,\n inboundObservedCount: 0,\n hasExtractedOutbound: false,\n })\n }\n\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n\n // The origin plus, when it's a service, the files it owns — the set of nodes\n // whose OBSERVED edges belong to \"what this thing does at runtime.\"\n const scope: string[] = [nodeId]\n if (attrs.type === NodeType.ServiceNode) {\n for (const edgeId of graph.outboundEdges(nodeId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type !== EdgeType.CONTAINS) continue\n const owned = graph.getNodeAttributes(e.target) as GraphNode\n if (owned.type === NodeType.FileNode) scope.push(e.target)\n }\n }\n\n const dependencies: GraphEdge[] = []\n const seenEdge = new Set<string>()\n let hasExtractedOutbound = false\n for (const src of scope) {\n for (const edgeId of graph.outboundEdges(src)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n // CONTAINS is structural ownership, never a runtime dependency.\n if (e.type === EdgeType.CONTAINS) continue\n if (e.provenance === Provenance.OBSERVED) {\n if (!seenEdge.has(e.id)) {\n seenEdge.add(e.id)\n dependencies.push(e)\n }\n } else if (e.provenance === Provenance.EXTRACTED) {\n hasExtractedOutbound = true\n }\n }\n }\n\n // Was this node (or a file it owns) seen receiving traffic? Counting OBSERVED\n // inbound edges is the pure-receiver signal — the \"hit N times, calls nothing\"\n // shape that must read differently from \"never observed.\"\n let inboundObservedCount = 0\n for (const tgt of scope) {\n for (const edgeId of graph.inboundEdges(tgt)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type === EdgeType.CONTAINS) continue\n if (e.provenance === Provenance.OBSERVED) inboundObservedCount += 1\n }\n }\n\n dependencies.sort(\n (a, b) =>\n a.target.localeCompare(b.target) ||\n a.source.localeCompare(b.source) ||\n a.id.localeCompare(b.id),\n )\n\n return ObservedDependenciesResultSchema.parse({\n origin: nodeId,\n dependencies,\n observed: dependencies.length > 0 || inboundObservedCount > 0,\n inboundObservedCount,\n hasExtractedOutbound,\n })\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport ignore, { type Ignore } from 'ignore'\nimport { minimatch } from 'minimatch'\nimport type { ServiceNode } from '@neat.is/types'\nimport { NodeType, serviceId } from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport {\n IGNORED_DIRS,\n exists,\n isPythonVenvDir,\n readJson,\n type DiscoveredService,\n type PackageJson,\n} from './shared.js'\nimport { discoverPythonService, pythonToPackage } from './python.js'\nimport { discoverGoService } from './go.js'\nimport { discoverRubyService } from './ruby.js'\nimport { discoverPhpService } from './php.js'\nimport { computeServiceOwner, loadCodeowners } from './owners.js'\nimport { recordExtractionError } from './errors.js'\n\nconst DEFAULT_SCAN_DEPTH = 5\n\ninterface RootPackageJson extends PackageJson {\n workspaces?: string[] | { packages?: string[] }\n}\n\nfunction parseScanDepth(): number {\n const raw = process.env.NEAT_SCAN_DEPTH\n if (!raw) return DEFAULT_SCAN_DEPTH\n const n = Number.parseInt(raw, 10)\n return Number.isFinite(n) && n >= 0 ? n : DEFAULT_SCAN_DEPTH\n}\n\nfunction workspaceGlobs(pkg: RootPackageJson): string[] | null {\n const ws = pkg.workspaces\n if (!ws) return null\n if (Array.isArray(ws)) return ws.length > 0 ? ws : null\n if (Array.isArray(ws.packages)) return ws.packages.length > 0 ? ws.packages : null\n return null\n}\n\nasync function hasPythonManifest(dir: string): Promise<boolean> {\n return (\n (await exists(path.join(dir, 'pyproject.toml'))) ||\n (await exists(path.join(dir, 'requirements.txt'))) ||\n (await exists(path.join(dir, 'setup.py')))\n )\n}\n\nasync function hasGoManifest(dir: string): Promise<boolean> {\n return exists(path.join(dir, 'go.mod'))\n}\n\nasync function hasRubyManifest(dir: string): Promise<boolean> {\n return exists(path.join(dir, 'Gemfile'))\n}\n\nasync function hasPhpManifest(dir: string): Promise<boolean> {\n return exists(path.join(dir, 'composer.json'))\n}\n\nasync function loadGitignore(scanPath: string): Promise<Ignore | null> {\n const gitignorePath = path.join(scanPath, '.gitignore')\n if (!(await exists(gitignorePath))) return null\n const raw = await fs.readFile(gitignorePath, 'utf8')\n return ignore().add(raw)\n}\n\ninterface WalkOptions {\n maxDepth: number\n ig: Ignore | null\n}\n\nasync function walkDirs(\n start: string,\n scanPath: string,\n options: WalkOptions,\n visit: (dir: string) => Promise<void> | void,\n): Promise<void> {\n async function recurse(current: string, depth: number): Promise<void> {\n if (depth > options.maxDepth) return\n const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n if (IGNORED_DIRS.has(entry.name)) continue\n const child = path.join(current, entry.name)\n if (options.ig) {\n const rel = path.relative(scanPath, child).split(path.sep).join('/')\n // Trailing slash so `ignore` evaluates the entry as a directory; without\n // it, gitignore patterns like `dist/` won't match because the lib\n // distinguishes file vs. directory tests.\n if (rel && options.ig.ignores(rel + '/')) continue\n }\n // Issue #344 — `pyvenv.cfg` marks every directory that `python -m venv`\n // or `virtualenv` creates, regardless of what the wrapper dir is called.\n // The `IGNORED_DIRS` name list catches the common shapes (`.venv`,\n // `venv`, `.tox`); this catches the long-tail ones (`env-3.11`,\n // `.direnv`, ad-hoc names).\n if (await isPythonVenvDir(child)) continue\n await visit(child)\n await recurse(child, depth + 1)\n }\n }\n await recurse(start, 0)\n}\n\nasync function expandWorkspaceGlobs(\n scanPath: string,\n globs: string[],\n): Promise<string[]> {\n const found = new Set<string>()\n const scanDepth = parseScanDepth()\n\n for (const raw of globs) {\n const pattern = raw.replace(/^\\.\\//, '')\n\n if (!pattern.includes('*')) {\n const candidate = path.join(scanPath, pattern)\n if (await exists(path.join(candidate, 'package.json'))) found.add(candidate)\n continue\n }\n\n const segments = pattern.split('/')\n const staticSegments: string[] = []\n for (const seg of segments) {\n if (seg.includes('*')) break\n staticSegments.push(seg)\n }\n const start = path.join(scanPath, ...staticSegments)\n if (!(await exists(start))) continue\n\n const hasDoubleStar = pattern.includes('**')\n const walkDepth = hasDoubleStar\n ? scanDepth\n : Math.max(0, segments.length - staticSegments.length - 1)\n\n await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {\n const rel = path.relative(scanPath, dir).split(path.sep).join('/')\n if (minimatch(rel, pattern) && (await exists(path.join(dir, 'package.json')))) {\n found.add(dir)\n }\n })\n }\n\n return [...found]\n}\n\n// Framework detection from package.json deps (ADR-074 §3). Mirrors the\n// installer dispatch precedence so a project the installer recognises as\n// Remix records `framework: 'remix'` on its ServiceNode. The static\n// extractor sees only manifest data, so detection is dep-presence based —\n// it doesn't crack open config files. Detection precedence: Next → Remix\n// → SvelteKit → Nuxt → Astro → NestJS → vanilla Node.\nfunction detectJsFramework(pkg: PackageJson): string | undefined {\n const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }\n if (deps['next'] !== undefined) return 'next'\n if (deps['remix'] !== undefined) return 'remix'\n for (const k of Object.keys(deps)) {\n if (k.startsWith('@remix-run/')) return 'remix'\n }\n if (deps['@sveltejs/kit'] !== undefined) return 'sveltekit'\n if (deps['nuxt'] !== undefined) return 'nuxt'\n if (deps['astro'] !== undefined) return 'astro'\n if (deps['@nestjs/core'] !== undefined) return 'nestjs'\n return undefined\n}\n\n// A JS-manifest service is TypeScript when its package depends on `typescript`\n// or carries a tsconfig — otherwise we default to javascript. The per-file\n// extractor already routes by extension (`languageForPath`); this only sets the\n// ServiceNode's own `language` field, which the static walker can't read off a\n// single file. One readdir, conservative on the javascript side.\nasync function detectJsServiceLanguage(\n dir: string,\n pkg: PackageJson,\n): Promise<'typescript' | 'javascript'> {\n const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }\n if (deps['typescript'] !== undefined) return 'typescript'\n const entries = await fs.readdir(dir).catch(() => [] as string[])\n if (entries.some((name) => /^tsconfig(\\..+)?\\.json$/.test(name))) return 'typescript'\n return 'javascript'\n}\n\nasync function discoverNodeService(\n scanPath: string,\n dir: string,\n): Promise<DiscoveredService | null> {\n const pkgPath = path.join(dir, 'package.json')\n if (!(await exists(pkgPath))) return null\n let pkg: PackageJson\n try {\n pkg = await readJson<PackageJson>(pkgPath)\n } catch (err) {\n recordExtractionError('services', path.relative(scanPath, pkgPath), err)\n return null\n }\n if (!pkg.name) return null\n const framework = detectJsFramework(pkg)\n const language = await detectJsServiceLanguage(dir, pkg)\n const node: ServiceNode = {\n id: serviceId(pkg.name),\n type: NodeType.ServiceNode,\n name: pkg.name,\n language,\n version: pkg.version,\n dependencies: pkg.dependencies ?? {},\n repoPath: path.relative(scanPath, dir),\n ...(pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}),\n ...(framework ? { framework } : {}),\n }\n return { pkg, dir, node }\n}\n\nasync function discoverPyService(\n scanPath: string,\n dir: string,\n): Promise<DiscoveredService | null> {\n const py = await discoverPythonService(dir)\n if (!py) return null\n const pkg = pythonToPackage(py)\n const node: ServiceNode = {\n id: serviceId(py.name),\n type: NodeType.ServiceNode,\n name: py.name,\n language: 'python',\n version: py.version,\n dependencies: py.dependencies,\n repoPath: path.relative(scanPath, dir),\n }\n return { pkg, dir, node }\n}\n\n// Phase 1 — discover service directories under scanPath. A service is any\n// directory containing a JS/TS manifest (`package.json`) or a Python manifest\n// (`pyproject.toml` / `requirements.txt` / `setup.py`). JS wins on tie.\n//\n// If the root `package.json` declares `workspaces`, those globs are\n// authoritative — we don't fall back to a free recursive walk. Otherwise we\n// walk recursively, depth-bounded by `NEAT_SCAN_DEPTH` (default 5), skipping\n// `IGNORED_DIRS` and anything matched by the root `.gitignore`.\n//\n// Two manifests sharing a `name` collapse to one node per ADR-010; the\n// duplicate logs a warning naming both paths.\nexport async function discoverServices(scanPath: string): Promise<DiscoveredService[]> {\n const rootPkgPath = path.join(scanPath, 'package.json')\n let rootPkg: RootPackageJson | null = null\n if (await exists(rootPkgPath)) {\n try {\n rootPkg = await readJson<RootPackageJson>(rootPkgPath)\n } catch (err) {\n recordExtractionError(\n 'services workspaces',\n path.relative(scanPath, rootPkgPath),\n err,\n )\n }\n }\n const wsGlobs = rootPkg ? workspaceGlobs(rootPkg) : null\n\n const candidateDirs: string[] = []\n if (wsGlobs) {\n candidateDirs.push(...(await expandWorkspaceGlobs(scanPath, wsGlobs)))\n } else {\n if (rootPkg && rootPkg.name) {\n candidateDirs.push(scanPath)\n } else if (\n (await hasPythonManifest(scanPath)) ||\n (await hasGoManifest(scanPath)) ||\n (await hasRubyManifest(scanPath)) ||\n (await hasPhpManifest(scanPath))\n ) {\n // A Python / Go / Rails / Laravel project commonly keeps its manifest at\n // the repo root with the code in a subpackage and no package.json\n // anywhere. The walk only visits descendants, so without this the root\n // manifest is never seen and the whole project discovers zero services.\n candidateDirs.push(scanPath)\n }\n const ig = await loadGitignore(scanPath)\n await walkDirs(\n scanPath,\n scanPath,\n { maxDepth: parseScanDepth(), ig },\n async (dir) => {\n if (await exists(path.join(dir, 'package.json'))) {\n candidateDirs.push(dir)\n } else if (\n (await hasPythonManifest(dir)) ||\n (await hasGoManifest(dir)) ||\n (await hasRubyManifest(dir)) ||\n (await hasPhpManifest(dir))\n ) {\n candidateDirs.push(dir)\n }\n },\n )\n }\n\n candidateDirs.sort()\n\n const seen = new Map<string, string>()\n const out: DiscoveredService[] = []\n for (const dir of candidateDirs) {\n const service =\n (await discoverNodeService(scanPath, dir)) ??\n (await discoverPyService(scanPath, dir)) ??\n (await discoverGoService(scanPath, dir)) ??\n (await discoverRubyService(scanPath, dir)) ??\n (await discoverPhpService(scanPath, dir))\n if (!service) continue\n\n const existingDir = seen.get(service.node.name)\n if (existingDir !== undefined) {\n const a = path.relative(scanPath, existingDir) || '.'\n const b = path.relative(scanPath, dir) || '.'\n console.warn(\n `[neat] duplicate package name \"${service.node.name}\" — keeping ${a}, ignoring ${b}`,\n )\n continue\n }\n seen.set(service.node.name, dir)\n out.push(service)\n }\n\n // Owner extraction (ADR-054). CODEOWNERS first, package.json `author`\n // fallback, undefined otherwise. Read once per discovery pass; the file\n // is small and parsing it per-service would be wasteful.\n const codeowners = await loadCodeowners(scanPath)\n for (const service of out) {\n const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir)\n if (owner !== undefined) service.node.owner = owner\n }\n\n return out\n}\n\nexport function addServiceNodes(graph: NeatGraph, services: DiscoveredService[]): number {\n let nodesAdded = 0\n for (const service of services) {\n if (!graph.hasNode(service.node.id)) {\n graph.addNode(service.node.id, { ...service.node, discoveredVia: 'static' })\n nodesAdded++\n continue\n }\n // OTel ingest may have auto-created a minimal node at this id. Merge per\n // ADR-033 / identity contract: static fields override OTel-derived fields,\n // and discoveredVia flips to 'merged' when both layers contributed.\n const existing = graph.getNodeAttributes(service.node.id) as ServiceNode\n const mergedDiscoveredVia: 'static' | 'otel' | 'merged' =\n existing.discoveredVia === 'otel' ? 'merged' : 'static'\n graph.replaceNodeAttributes(service.node.id, {\n ...existing,\n ...service.node,\n discoveredVia: mergedDiscoveredVia,\n })\n }\n return nodesAdded\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { parse as parseToml } from 'smol-toml'\nimport { exists, type PackageJson } from './shared.js'\n\n// Lines like `psycopg2==2.7.0`, `psycopg2 == 2.7.0`, `psycopg2[extras]==2.7`,\n// or `psycopg2~=2.7,<3`. We capture the package name and the first version\n// that follows an `==` operator. Anything else (range, no pin, no version) is\n// recorded with an empty version — the compat matrix's semver coercer treats\n// those as \"can't reason\" and under-flags rather than over-flags.\nconst REQUIREMENT_LINE = /^\\s*([A-Za-z0-9_.-]+)(?:\\[[^\\]]*\\])?\\s*(?:(==)\\s*([A-Za-z0-9_.+-]+))?/\n\nfunction parseRequirementsTxt(content: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const rawLine of content.split('\\n')) {\n const line = rawLine.split('#')[0]?.trim()\n if (!line) continue\n if (line.startsWith('-')) continue // -r requirements-dev.txt etc.\n const match = REQUIREMENT_LINE.exec(line)\n if (!match) continue\n const name = match[1]!.toLowerCase()\n const version = match[3] ?? ''\n out[name] = version\n }\n return out\n}\n\ninterface PyProjectFile {\n project?: {\n name?: string\n version?: string\n dependencies?: string[]\n }\n tool?: {\n poetry?: {\n name?: string\n version?: string\n dependencies?: Record<string, string | { version?: string }>\n }\n }\n}\n\nfunction depsFromPyProject(pyproject: PyProjectFile): Record<string, string> {\n const out: Record<string, string> = {}\n\n // PEP 621 — [project] dependencies = [\"psycopg2==2.7.0\", \"requests\"]\n for (const entry of pyproject.project?.dependencies ?? []) {\n const match = REQUIREMENT_LINE.exec(entry)\n if (!match) continue\n out[match[1]!.toLowerCase()] = match[3] ?? ''\n }\n\n // Poetry — [tool.poetry.dependencies] psycopg2 = \"2.7.0\"\n const poetryDeps = pyproject.tool?.poetry?.dependencies ?? {}\n for (const [name, value] of Object.entries(poetryDeps)) {\n if (name.toLowerCase() === 'python') continue\n const raw = typeof value === 'string' ? value : (value?.version ?? '')\n out[name.toLowerCase()] = raw.replace(/^[\\^~><=v\\s]+/, '')\n }\n return out\n}\n\nexport interface PythonService {\n name: string\n version?: string\n dependencies: Record<string, string>\n}\n\n// Detect a Python service by the conventional manifest files. We try\n// pyproject.toml first because it can name the package; fallback to the\n// directory name when only requirements.txt or setup.py is present.\nexport async function discoverPythonService(serviceDir: string): Promise<PythonService | null> {\n const pyprojectPath = path.join(serviceDir, 'pyproject.toml')\n const requirementsPath = path.join(serviceDir, 'requirements.txt')\n const setupPath = path.join(serviceDir, 'setup.py')\n\n const hasPyproject = await exists(pyprojectPath)\n const hasRequirements = await exists(requirementsPath)\n const hasSetup = await exists(setupPath)\n if (!hasPyproject && !hasRequirements && !hasSetup) return null\n\n let name = path.basename(serviceDir)\n let version: string | undefined\n const dependencies: Record<string, string> = {}\n\n if (hasPyproject) {\n const raw = await fs.readFile(pyprojectPath, 'utf8')\n const pyproject = parseToml(raw) as PyProjectFile\n name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name\n version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? undefined\n Object.assign(dependencies, depsFromPyProject(pyproject))\n }\n\n if (hasRequirements) {\n const raw = await fs.readFile(requirementsPath, 'utf8')\n Object.assign(dependencies, parseRequirementsTxt(raw))\n }\n\n return { name, version, dependencies }\n}\n\n// Build the same `pkg`-shaped shim the JS path uses so downstream phases\n// (databases, calls, etc.) can keep reading service.pkg.dependencies and\n// service.pkg.name without caring which language produced the service.\nexport function pythonToPackage(service: PythonService): PackageJson {\n return {\n name: service.name,\n version: service.version,\n dependencies: service.dependencies,\n }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { ServiceNode } from '@neat.is/types'\nimport { NodeType, serviceId } from '@neat.is/types'\nimport type { DiscoveredService, PackageJson } from './shared.js'\n\nexport interface GoModule {\n module: string\n goVersion?: string\n dependencies: Record<string, string>\n}\n\nexport function parseGoMod(source: string): GoModule | null {\n const module = source.match(/^\\s*module\\s+(\\S+)\\s*$/m)?.[1]\n if (!module) return null\n const goVersion = source.match(/^\\s*go\\s+(\\S+)\\s*$/m)?.[1]\n const dependencies: Record<string, string> = {}\n const block = source.match(/^\\s*require\\s*\\(([^]*?)^\\s*\\)/m)?.[1] ?? ''\n for (const line of block.split(/\\r?\\n/)) {\n const m = line.replace(/\\/\\/.*$/, '').trim().match(/^(\\S+)\\s+(v\\S+)$/)\n if (m) dependencies[m[1]!] = m[2]!\n }\n for (const m of source.matchAll(/^\\s*require\\s+(\\S+)\\s+(v\\S+)(?:\\s*\\/\\/.*)?$/gm)) {\n dependencies[m[1]!] = m[2]!\n }\n return { module, ...(goVersion ? { goVersion } : {}), dependencies }\n}\n\n// The web framework a Go service declares, for the ServiceNode's `framework`\n// label — the same package-name → label discipline the JS/Python readers use.\n// The go.mod require path carries the major-version suffix (`.../echo/v4`), so\n// the versioned and pre-modules module paths both map to one label.\nfunction goFramework(deps: Record<string, string>): string | undefined {\n if (deps['github.com/gin-gonic/gin']) return 'gin'\n if (deps['github.com/labstack/echo/v4'] || deps['github.com/labstack/echo']) return 'echo'\n if (deps['github.com/gofiber/fiber/v2'] || deps['github.com/gofiber/fiber/v3']) return 'fiber'\n if (deps['github.com/go-chi/chi/v5'] || deps['github.com/go-chi/chi']) return 'chi'\n return undefined\n}\n\nexport async function discoverGoService(\n scanPath: string,\n dir: string,\n): Promise<DiscoveredService | null> {\n let raw: string\n try {\n raw = await fs.readFile(path.join(dir, 'go.mod'), 'utf8')\n } catch {\n return null\n }\n const mod = parseGoMod(raw)\n if (!mod) return null\n const name = mod.module.split('/').filter(Boolean).pop() ?? mod.module\n const pkg: PackageJson = { name, dependencies: mod.dependencies }\n const framework = goFramework(mod.dependencies)\n const node: ServiceNode = {\n id: serviceId(name),\n type: NodeType.ServiceNode,\n name,\n language: 'go',\n dependencies: mod.dependencies,\n repoPath: path.relative(scanPath, dir),\n ...(framework ? { framework } : {}),\n }\n return { pkg, dir, node }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { ServiceNode } from '@neat.is/types'\nimport { NodeType, serviceId } from '@neat.is/types'\nimport type { DiscoveredService, PackageJson } from './shared.js'\n\n// Ruby service discovery (ADR-173), the Gemfile analog of extract/go.ts's\n// `go.mod` reader. A Gemfile names no project, so the service takes its\n// directory's basename; the `gem` lines become dependency gates the way a\n// `go.mod` `require` block does, so the route producer can gate on `rails`.\n\nexport interface Gemfile {\n dependencies: Record<string, string>\n}\n\n// Read the gems a Gemfile declares. `gem 'rails', '~> 7.1'` → { rails: '~> 7.1' }.\n// A version is optional (`gem 'puma'` → ''); a gem with only non-version options\n// (`gem 'pg', require: false`) still registers, at ''. Commented lines are\n// skipped. This is a line scan, not a Ruby parse — the same discipline go.ts's\n// `go.mod` reader and proto.ts's `.proto` scan use for polyglot manifest data.\nexport function parseGemfile(source: string): Gemfile {\n const dependencies: Record<string, string> = {}\n for (const raw of source.split(/\\r?\\n/)) {\n const line = raw.replace(/#.*$/, '').trim()\n const m = line.match(/^gem\\s+(['\"])([^'\"]+)\\1\\s*(?:,\\s*(['\"])([^'\"]+)\\3)?/)\n if (!m) continue\n const name = m[2]!\n const version = m[4] && /\\d/.test(m[4]) ? m[4] : ''\n dependencies[name] = version\n }\n return { dependencies }\n}\n\nexport async function discoverRubyService(\n scanPath: string,\n dir: string,\n): Promise<DiscoveredService | null> {\n let raw: string\n try {\n raw = await fs.readFile(path.join(dir, 'Gemfile'), 'utf8')\n } catch {\n return null\n }\n const gemfile = parseGemfile(raw)\n const name = path.basename(dir)\n const pkg: PackageJson = { name, dependencies: gemfile.dependencies }\n const node: ServiceNode = {\n id: serviceId(name),\n type: NodeType.ServiceNode,\n name,\n language: 'ruby',\n dependencies: gemfile.dependencies,\n repoPath: path.relative(scanPath, dir),\n ...(gemfile.dependencies['rails'] !== undefined ? { framework: 'rails' } : {}),\n }\n return { pkg, dir, node }\n}\n","import path from 'node:path'\nimport type { ServiceNode } from '@neat.is/types'\nimport { NodeType, serviceId } from '@neat.is/types'\nimport { readJson, type DiscoveredService, type PackageJson } from './shared.js'\n\n// PHP service discovery (ADR-177), the `composer.json` analog of extract/ruby.ts's\n// Gemfile reader and extract/go.ts's `go.mod` reader. A composer.json does name a\n// project (`vendor/package`), but we keep the basename-as-service-name convention\n// the Gemfile/go.mod readers use so a PHP service sits next to the others without\n// a vendor slash in its id; the `require` block becomes the dependency gates the\n// route producer reads, so it can gate on `laravel/framework`.\n\ninterface ComposerJson {\n name?: string\n require?: Record<string, string>\n 'require-dev'?: Record<string, string>\n}\n\nexport interface Composer {\n dependencies: Record<string, string>\n}\n\n// Read the packages a composer.json declares, merging `require` and\n// `require-dev` the way the JS reader merges dependencies and devDependencies.\n// `laravel/framework` in `require` is the gate the Laravel route recognizer uses.\nexport function parseComposerJson(composer: ComposerJson): Composer {\n return {\n dependencies: {\n ...(composer.require ?? {}),\n ...(composer['require-dev'] ?? {}),\n },\n }\n}\n\nexport async function discoverPhpService(\n scanPath: string,\n dir: string,\n): Promise<DiscoveredService | null> {\n let composer: ComposerJson\n try {\n composer = await readJson<ComposerJson>(path.join(dir, 'composer.json'))\n } catch {\n return null\n }\n const { dependencies } = parseComposerJson(composer)\n const name = path.basename(dir)\n const pkg: PackageJson = { name, dependencies }\n const node: ServiceNode = {\n id: serviceId(name),\n type: NodeType.ServiceNode,\n name,\n language: 'php',\n dependencies,\n repoPath: path.relative(scanPath, dir),\n ...(dependencies['laravel/framework'] !== undefined ? { framework: 'laravel' } : {}),\n }\n return { pkg, dir, node }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { minimatch } from 'minimatch'\nimport { exists, readJson } from './shared.js'\n\nexport interface CodeownersRule {\n pattern: string\n owners: string\n}\n\nexport interface CodeownersFile {\n rules: CodeownersRule[]\n}\n\ninterface PackageJsonAuthor {\n author?: string | { name?: string }\n}\n\n// Read CODEOWNERS at <scanPath>/CODEOWNERS first, then <scanPath>/.github/CODEOWNERS.\n// Returns null when neither exists. ADR-054 #2.1.\nexport async function loadCodeowners(scanPath: string): Promise<CodeownersFile | null> {\n const candidates = [\n path.join(scanPath, 'CODEOWNERS'),\n path.join(scanPath, '.github', 'CODEOWNERS'),\n ]\n for (const file of candidates) {\n if (await exists(file)) {\n const raw = await fs.readFile(file, 'utf8')\n return parseCodeowners(raw)\n }\n }\n return null\n}\n\nfunction parseCodeowners(raw: string): CodeownersFile {\n const rules: CodeownersRule[] = []\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith('#')) continue\n const match = /^(\\S+)\\s+(.+)$/.exec(trimmed)\n if (!match) continue\n rules.push({ pattern: match[1]!, owners: match[2]!.trim() })\n }\n return { rules }\n}\n\n// First matching pattern wins; returns the literal RHS or null. ADR-054 #2.1.\n// Pattern matcher is minimal per ADR-054 #6 — handles `*`, `**`, and exact\n// paths; not a full gitignore-style parser.\nexport function matchOwner(file: CodeownersFile, repoPath: string): string | null {\n const normalized = repoPath.split(path.sep).join('/')\n for (const rule of file.rules) {\n if (matchesPattern(rule.pattern, normalized)) return rule.owners\n }\n return null\n}\n\nfunction matchesPattern(rawPattern: string, repoPath: string): boolean {\n let pattern = rawPattern.startsWith('/') ? rawPattern.slice(1) : rawPattern\n if (pattern === '*') return !repoPath.includes('/')\n if (pattern === '**' || pattern === '') return true\n // Trailing slash means \"everything in this directory\".\n if (pattern.endsWith('/')) pattern = pattern + '**'\n if (minimatch(repoPath, pattern, { dot: true })) return true\n // A pattern that names a directory should match files beneath it too.\n if (!pattern.includes('*') && minimatch(repoPath, pattern + '/**', { dot: true })) return true\n return false\n}\n\n// Read <serviceDir>/package.json and return the `author` field as a literal\n// string. Accepts either string form (\"Cem D <cem@example.com>\") or object\n// form ({ name: 'Cem D' }). Returns null when missing or unparseable.\n// ADR-054 #2.2.\nexport async function readPackageJsonAuthor(serviceDir: string): Promise<string | null> {\n const pkgPath = path.join(serviceDir, 'package.json')\n if (!(await exists(pkgPath))) return null\n try {\n const pkg = await readJson<PackageJsonAuthor>(pkgPath)\n if (!pkg.author) return null\n if (typeof pkg.author === 'string') return pkg.author\n if (typeof pkg.author === 'object' && typeof pkg.author.name === 'string') return pkg.author.name\n return null\n } catch {\n return null\n }\n}\n\n// Compute owner per ADR-054 priority: CODEOWNERS first, package.json `author`\n// fallback, undefined otherwise. Literal source value, no normalization (#3).\nexport async function computeServiceOwner(\n codeowners: CodeownersFile | null,\n repoPath: string | undefined,\n serviceDir: string,\n): Promise<string | undefined> {\n if (codeowners && repoPath !== undefined) {\n const owner = matchOwner(codeowners, repoPath)\n if (owner) return owner\n }\n const author = await readPackageJsonAuthor(serviceDir)\n return author ?? undefined\n}\n","import path from 'node:path'\nimport { promises as fs } from 'node:fs'\nimport { parseAllDocuments } from 'yaml'\nimport type { ServiceNode } from '@neat.is/types'\nimport { NodeType } from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport { recordExtractionError } from './errors.js'\nimport {\n CONFIG_FILE_EXTENSIONS,\n IGNORED_DIRS,\n exists,\n isPythonVenvDir,\n readYaml,\n type DiscoveredService,\n} from './shared.js'\n\n// Populate ServiceNode.aliases from sources that the OTel layer is likely to\n// see in span attributes:\n//\n// - docker-compose service names (compose-DNS).\n// - Dockerfile LABEL values that name the service.\n// - k8s metadata.name (and the cluster-DNS variants) for Service /\n// Deployment / StatefulSet whose name matches a known service.\n//\n// resolveServiceId in ingest.ts checks aliases before falling back to a\n// FRONTIER placeholder; promoteFrontierNodes uses them to retire stale\n// placeholders once they map to a real service.\n\ninterface ComposeService {\n container_name?: string\n hostname?: string\n networks?: string[] | Record<string, unknown>\n}\n\ninterface ComposeFile {\n services?: Record<string, ComposeService>\n}\n\ninterface K8sDoc {\n kind?: string\n metadata?: {\n name?: string\n namespace?: string\n labels?: Record<string, string>\n }\n spec?: {\n selector?: {\n app?: string\n matchLabels?: Record<string, string>\n }\n }\n}\n\nconst K8S_KINDS_WITH_HOSTNAMES = new Set([\n 'Service',\n 'Deployment',\n 'StatefulSet',\n 'DaemonSet',\n])\n\nfunction addAliases(graph: NeatGraph, serviceId: string, candidates: Iterable<string>): void {\n if (!graph.hasNode(serviceId)) return\n const node = graph.getNodeAttributes(serviceId) as ServiceNode & { type?: string }\n if (node.type !== NodeType.ServiceNode) return\n const set = new Set(node.aliases ?? [])\n for (const c of candidates) {\n if (!c) continue\n if (c === node.name) continue\n set.add(c)\n }\n if (set.size === 0) return\n const updated: ServiceNode = { ...node, aliases: [...set].sort() }\n graph.replaceNodeAttributes(serviceId, updated)\n}\n\nfunction indexServicesByName(services: DiscoveredService[]): Map<string, string> {\n const map = new Map<string, string>()\n for (const s of services) {\n map.set(s.node.name, s.node.id)\n map.set(path.basename(s.dir), s.node.id)\n }\n return map\n}\n\nasync function collectComposeAliases(\n graph: NeatGraph,\n scanPath: string,\n serviceIndex: Map<string, string>,\n): Promise<void> {\n let composePath: string | null = null\n for (const name of ['docker-compose.yml', 'docker-compose.yaml']) {\n const abs = path.join(scanPath, name)\n if (await exists(abs)) {\n composePath = abs\n break\n }\n }\n if (!composePath) return\n\n let compose: ComposeFile\n try {\n compose = await readYaml<ComposeFile>(composePath)\n } catch (err) {\n recordExtractionError(\n 'aliases compose',\n path.relative(scanPath, composePath),\n err,\n )\n return\n }\n if (!compose?.services) return\n\n for (const [composeName, svc] of Object.entries(compose.services)) {\n const serviceId = serviceIndex.get(composeName)\n if (!serviceId) continue\n const aliases = new Set<string>([composeName])\n if (svc.container_name) aliases.add(svc.container_name)\n if (svc.hostname) aliases.add(svc.hostname)\n addAliases(graph, serviceId, aliases)\n }\n}\n\nconst LABEL_KEYS = new Set([\n 'service',\n 'service.name',\n 'app',\n 'app.name',\n 'com.docker.compose.service',\n 'org.opencontainers.image.title',\n])\n\nfunction parseDockerfileLabels(content: string): string[] {\n const out: string[] = []\n // Support `LABEL key=value`, `LABEL key=\"value with spaces\"`, and the\n // multi-pair form `LABEL k1=v1 k2=v2`. We don't try to honour line\n // continuations — the common single-line form is enough.\n const lineRegex = /^\\s*label\\s+(.+)$/i\n for (const raw of content.split('\\n')) {\n const m = lineRegex.exec(raw)\n if (!m) continue\n const rest = m[1]!\n const pairRegex = /([\\w.-]+)\\s*=\\s*(\"([^\"]*)\"|'([^']*)'|([^\\s]+))/g\n let pair: RegExpExecArray | null\n while ((pair = pairRegex.exec(rest)) !== null) {\n const key = pair[1]!.toLowerCase()\n if (!LABEL_KEYS.has(key)) continue\n const value = pair[3] ?? pair[4] ?? pair[5] ?? ''\n if (value) out.push(value)\n }\n }\n return out\n}\n\nasync function collectDockerfileAliases(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<void> {\n for (const service of services) {\n const dockerfilePath = path.join(service.dir, 'Dockerfile')\n if (!(await exists(dockerfilePath))) continue\n let content: string\n try {\n content = await fs.readFile(dockerfilePath, 'utf8')\n } catch (err) {\n recordExtractionError('aliases dockerfile', dockerfilePath, err)\n continue\n }\n const aliases = parseDockerfileLabels(content)\n if (aliases.length > 0) addAliases(graph, service.node.id, aliases)\n }\n}\n\nasync function walkYamlFiles(start: string, depth = 0, max = 5): Promise<string[]> {\n if (depth > max) return []\n const out: string[] = []\n const entries = await fs.readdir(start, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name)) continue\n const child = path.join(start, entry.name)\n if (await isPythonVenvDir(child)) continue\n out.push(...(await walkYamlFiles(child, depth + 1, max)))\n } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path.extname(entry.name))) {\n out.push(path.join(start, entry.name))\n }\n }\n return out\n}\n\nfunction k8sHostnames(name: string, namespace: string | undefined): string[] {\n const ns = namespace ?? 'default'\n return [\n name,\n `${name}.${ns}`,\n `${name}.${ns}.svc`,\n `${name}.${ns}.svc.cluster.local`,\n ]\n}\n\nfunction k8sServiceTarget(\n doc: K8sDoc,\n byName: Map<string, string>,\n): string | null {\n // For `Service` resources, the target is whatever app the spec selects, not\n // necessarily the Service's own metadata.name. We prefer that mapping; if no\n // selector, fall back to the metadata.name match.\n const selector = doc.spec?.selector\n const selectorApp = selector?.app ?? selector?.matchLabels?.app\n if (selectorApp && byName.has(selectorApp)) return byName.get(selectorApp)!\n\n const labelApp = doc.metadata?.labels?.app\n if (labelApp && byName.has(labelApp)) return byName.get(labelApp)!\n\n const metaName = doc.metadata?.name\n if (metaName && byName.has(metaName)) return byName.get(metaName)!\n\n return null\n}\n\nasync function collectK8sAliases(\n graph: NeatGraph,\n scanPath: string,\n serviceIndex: Map<string, string>,\n): Promise<void> {\n const files = await walkYamlFiles(scanPath)\n for (const file of files) {\n const content = await fs.readFile(file, 'utf8')\n let docs: K8sDoc[]\n try {\n docs = parseAllDocuments(content).map((d) => d.toJSON() as K8sDoc)\n } catch {\n continue\n }\n for (const doc of docs) {\n if (!doc?.kind || !doc.metadata?.name) continue\n if (!K8S_KINDS_WITH_HOSTNAMES.has(doc.kind)) continue\n const target = k8sServiceTarget(doc, serviceIndex)\n if (!target) continue\n addAliases(graph, target, k8sHostnames(doc.metadata.name, doc.metadata.namespace))\n }\n }\n}\n\nexport async function addServiceAliases(\n graph: NeatGraph,\n scanPath: string,\n services: DiscoveredService[],\n): Promise<void> {\n const byName = indexServicesByName(services)\n await collectComposeAliases(graph, scanPath, byName)\n await collectDockerfileAliases(graph, services)\n await collectK8sAliases(graph, scanPath, byName)\n}\n","import type { NeatGraph } from '../graph.js'\nimport type { DiscoveredService } from './shared.js'\nimport { ensureFileNode, walkSourceFiles, toPosix } from './calls/shared.js'\nimport path from 'node:path'\n\n// Phase 1 — unconditional file enumeration (ADR-092, file-awareness.md §1).\n// Walks every source file matching SERVICE_FILE_EXTENSIONS within each service\n// and emits a FileNode + service ──CONTAINS──▶ file edge, regardless of whether\n// any call pattern fires from the file later.\nexport async function addFiles(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const filePaths = await walkSourceFiles(service.dir)\n for (const filePath of filePaths) {\n const relPath = toPosix(path.relative(service.dir, filePath))\n const { nodesAdded: n, edgesAdded: e } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relPath,\n )\n nodesAdded += n\n edgesAdded += e\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport TypeScript from 'tree-sitter-typescript'\nimport type { GraphEdge, SymbolKind, SymbolNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n symbolId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport type { DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { ensureFileNode, loadSourceFiles, snippet, toPosix } from './calls/shared.js'\n\n// Static symbol-node extraction (ADR-158, JS/TS first). Parses each JS/TS source\n// file with tree-sitter and mints a SymbolNode per function / method /\n// constructor / class *definition*, owned by its file through a\n// `file ──CONTAINS──▶ symbol` edge — the same containment spine files use under\n// services (file-awareness.md §2), one level deeper. Static-first: a symbol\n// exists in the inventory whether or not runtime ever exercised it, and it\n// carries its `{ startLine, endLine }` definition span, which is the fusion key\n// ingest joins a span's `code.line` against to land an OBSERVED edge on the\n// calling symbol (observed-first edges, ingest.ts).\n//\n// Scope is definitions only — no CALLS/INHERITS symbol edges (Phase 2). Each file\n// is parsed with the grammar that understands it: `.ts` / `.tsx` through\n// tree-sitter-typescript, `.js` / `.jsx` / `.mjs` / `.cjs` through\n// tree-sitter-javascript. The JS grammar cannot parse TypeScript type annotations\n// — it produces ERROR nodes that swallow most definitions (an all-`.ts` core file\n// yields 4 of 27 functions under the JS grammar, 27 of 27 under the TS one) — and\n// symbol extraction, unlike the string / route matchers that survive a partial\n// parse, needs a correct AST. Python and Go symbol grain are a follow-on rung.\n// Evidence carries the real `file:line`, never fabricated (file-awareness.md §6).\n\nconst PARSE_CHUNK = 16384\n\n// The grammar for each source extension NEAT symbol-extracts in this slice.\n// `.py` / `.go` are absent — symbol grain for those is a follow-on. tree-sitter-\n// typescript is a superset grammar sharing the same definition node types\n// (function_declaration / class_declaration / method_definition /\n// variable_declarator), so `collectSymbolDefs` walks TS and JS trees identically.\nexport const GRAMMAR_BY_EXT: Record<string, typeof JavaScript> = {\n '.ts': TypeScript.typescript,\n '.tsx': TypeScript.tsx,\n '.js': JavaScript,\n '.jsx': JavaScript,\n '.mjs': JavaScript,\n '.cjs': JavaScript,\n}\n\nexport function parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\n// A parsed definition, pre-identity. `qualname` is the source-declared name\n// (`OrderService.create`, `merge`); `node` carries the definition span.\nexport interface SymbolDef {\n kind: SymbolKind\n qualname: string\n startLine: number\n endLine: number\n}\n\nfunction methodName(node: Parser.SyntaxNode): string | null {\n const name = node.childForFieldName('name')\n return name ? name.text : null\n}\n\n// Walk the AST once, emitting one SymbolDef per definition. A class carries its\n// method context so a method's qualname is `Class.method`; a plain (including\n// nested) function keeps its bare declared name. Anonymous definitions with no\n// declared name are skipped rather than given a fabricated one (§6).\nexport function collectSymbolDefs(root: Parser.SyntaxNode): SymbolDef[] {\n const out: SymbolDef[] = []\n\n const push = (kind: SymbolKind, qualname: string, node: Parser.SyntaxNode): void => {\n out.push({\n kind,\n qualname,\n startLine: node.startPosition.row + 1,\n endLine: node.endPosition.row + 1,\n })\n }\n\n const visit = (node: Parser.SyntaxNode, classCtx: string | undefined): void => {\n switch (node.type) {\n case 'function_declaration':\n case 'generator_function_declaration': {\n const name = node.childForFieldName('name')?.text\n if (name) push('function', name, node)\n break\n }\n case 'class_declaration':\n case 'abstract_class_declaration':\n case 'class': {\n // `abstract class Foo` parses as its own node type in the TS grammar,\n // not `class_declaration` — miss it and every INHERITS/IMPLEMENTS to an\n // abstract base (the most common heritage target) has no symbol to land\n // on. It carries the same `name` / `body` fields, so it mints identically.\n const name = node.childForFieldName('name')?.text\n if (name) push('class', name, node)\n const body = node.childForFieldName('body')\n if (body) {\n for (let i = 0; i < body.namedChildCount; i++) {\n const child = body.namedChild(i)\n if (child) visit(child, name ?? classCtx)\n }\n }\n // Class children were walked with the class context above; skip the\n // generic recurse so its body isn't visited twice.\n return\n }\n case 'method_definition': {\n const name = methodName(node)\n if (name) {\n const kind: SymbolKind = name === 'constructor' ? 'constructor' : 'method'\n push(kind, classCtx ? `${classCtx}.${name}` : name, node)\n }\n break\n }\n case 'variable_declarator': {\n // `const foo = () => {}` / `const foo = function () {}` — the common\n // arrow/function-expression definition form. Only a plain identifier\n // name yields a symbol; a destructuring pattern names no single symbol.\n const value = node.childForFieldName('value')\n if (\n value &&\n (value.type === 'arrow_function' ||\n value.type === 'function' ||\n value.type === 'function_expression' ||\n value.type === 'generator_function')\n ) {\n const nameNode = node.childForFieldName('name')\n if (nameNode && nameNode.type === 'identifier') {\n push('function', nameNode.text, node)\n }\n }\n break\n }\n }\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) visit(child, classCtx)\n }\n }\n\n visit(root, undefined)\n return out\n}\n\n// Same-named siblings in one file (overloads, repeated anonymous-arrow names)\n// get an ordinal disambiguator in source order so the id stays collision-free\n// without inventing a name (ADR-158). A qualname that appears once keeps the\n// clean, disambiguator-free id.\nexport function disambiguate(defs: SymbolDef[]): { def: SymbolDef; disambiguator?: number }[] {\n const counts = new Map<string, number>()\n for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1)\n const seen = new Map<string, number>()\n return defs.map((def) => {\n if ((counts.get(def.qualname) ?? 0) <= 1) return { def }\n const ordinal = seen.get(def.qualname) ?? 0\n seen.set(def.qualname, ordinal + 1)\n return { def, disambiguator: ordinal }\n })\n}\n\nexport async function addSymbols(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const parsers = new Map<string, Parser>()\n const parserForExt = (ext: string): Parser | null => {\n const grammar = GRAMMAR_BY_EXT[ext]\n if (!grammar) return null\n let parser = parsers.get(ext)\n if (!parser) {\n parser = new Parser()\n parser.setLanguage(grammar)\n parsers.set(ext, parser)\n }\n return parser\n }\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const files = await loadSourceFiles(service.dir)\n for (const file of files) {\n const parser = parserForExt(path.extname(file.path))\n if (!parser) continue\n const relPath = toPosix(path.relative(service.dir, file.path))\n\n let defs: SymbolDef[]\n try {\n const tree = parseSource(parser, file.content)\n defs = collectSymbolDefs(tree.rootNode)\n } catch (err) {\n recordExtractionError('symbol extraction', file.path, err)\n continue\n }\n if (defs.length === 0) continue\n\n // The file owns its symbols; ensure the FileNode (and the owning\n // `service ──CONTAINS──▶ file` edge) exists before a symbol hangs off it.\n // Idempotent — addFiles already minted it on this pass.\n const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relPath,\n )\n nodesAdded += fn\n edgesAdded += fe\n\n for (const { def, disambiguator } of disambiguate(defs)) {\n const sid = symbolId(service.pkg.name, relPath, def.qualname, disambiguator)\n if (!graph.hasNode(sid)) {\n const node: SymbolNode = {\n id: sid,\n type: NodeType.SymbolNode,\n kind: def.kind,\n qualname: def.qualname,\n span: { startLine: def.startLine, endLine: def.endLine },\n service: service.pkg.name,\n relPath,\n discoveredVia: 'static',\n }\n graph.addNode(sid, node)\n nodesAdded++\n }\n // `file ──CONTAINS──▶ symbol` — structural ownership, the same tier and\n // shape as `service ──CONTAINS──▶ file` (file-awareness.md §2), evidence\n // pinned to the definition's file:line.\n const containsId = extractedEdgeId(fileNodeId, sid, EdgeType.CONTAINS)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: fileNodeId,\n target: sid,\n type: EdgeType.CONTAINS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: relPath,\n line: def.startLine,\n snippet: snippet(file.content, def.startLine),\n },\n }\n graph.addEdgeWithKey(containsId, fileNodeId, sid, edge)\n edgesAdded++\n }\n }\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport type { GraphEdge, SymbolKind, SymbolNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n symbolId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport type { DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { loadSourceFiles, snippet, toPosix } from './calls/shared.js'\nimport {\n GRAMMAR_BY_EXT,\n collectSymbolDefs,\n disambiguate,\n parseSource,\n} from './symbols.js'\nimport { loadTsPathConfig, resolveJsImport, type TsPathConfig } from './imports.js'\n\n// Static symbol *edges* (ADR-158 §3, Phase 2). Builds on the Phase-1 SymbolNode\n// inventory: for every class it mints `INHERITS` / `IMPLEMENTS` from the parsed\n// heritage clause, and for every call expression a symbol→symbol `CALLS` from the\n// enclosing caller symbol to the callee — but only the confident ones. The\n// load-bearing discipline (ADR-158 §3, file-awareness.md §6): an edge is emitted\n// only when its target resolves to exactly one known SymbolNode without guessing —\n// a same-file definition, or an imported binding resolved through the import graph\n// to the exact exported symbol. A callee that needs a type or a runtime value to\n// resolve (method dispatch on a receiver, a dynamic/computed call, an unresolvable\n// or re-exported import, a default/namespace import) is left unclaimed rather than\n// fuzzy-matched. A guessed symbol edge poisons the determinism the graph sells.\n//\n// Runs after `addSymbols` (the inventory it resolves against) and `addImports`\n// (so the import graph is in place), reusing the same tree-sitter grammars and\n// the `resolveJsImport` specifier→file resolver rather than reimplementing them.\n// JS/TS only, matching Phase-1 node grain; Python/Go heritage and calls are a\n// follow-on rung. Every edge grades EXTRACTED with `evidence.file`/`line` pinned\n// to the real heritage clause / call site (never fabricated, §6).\n\n// A Phase-1 symbol as this producer needs it: the id plus the definition span it\n// was minted with, so a call line can be attributed to its enclosing symbol.\ninterface LocalSymbol {\n sid: string\n qualname: string\n kind: SymbolKind\n startLine: number\n endLine: number\n}\n\n// A named import binding resolved to the intra-service file that defines it and\n// the exported name to match there. `import { Base } from './base'` →\n// `Base → { targetRelPath: 'base.ts', importedName: 'Base' }`; an alias keeps the\n// source-declared export name (`import { Shape as S }` → `S → …importedName:'Shape'`).\ninterface ResolvedImport {\n targetRelPath: string\n importedName: string\n}\n\n// The bare superclass name of a class's heritage, when it is a plain identifier\n// this producer can resolve — handling both grammar shapes: the TS grammar wraps\n// the superclass in an `extends_clause` (its `value` field), the JS grammar puts\n// the identifier directly under `class_heritage`. A qualified superclass\n// (`mod.Base`, a mixin call `mixin(Base)`) is not a bare identifier, so it returns\n// null and no INHERITS edge is guessed.\nfunction extendsInfo(\n classHeritage: Parser.SyntaxNode,\n): { name: string; line: number } | null {\n for (let i = 0; i < classHeritage.namedChildCount; i++) {\n const child = classHeritage.namedChild(i)\n if (!child) continue\n if (child.type === 'extends_clause') {\n const value = child.childForFieldName('value')\n if (value && value.type === 'identifier') {\n return { name: value.text, line: value.startPosition.row + 1 }\n }\n return null\n }\n // JS grammar: the superclass identifier hangs directly off class_heritage.\n if (child.type === 'identifier') {\n return { name: child.text, line: child.startPosition.row + 1 }\n }\n }\n return null\n}\n\n// The bare implemented-type names of a class's `implements` clause (TS only).\n// Each entry is a `type_identifier` (`implements Shape`) or a `generic_type`\n// (`implements Comparable<number>`); a qualified `nested_type_identifier`\n// (`ns.Shape`) is skipped rather than guessed. Note: an interface is not a\n// SymbolNode (ADR-158 §2 fixes the kind set to function/method/constructor/class),\n// so an IMPLEMENTS edge lands only when the implemented name resolves to a known\n// *class* symbol (TS permits `implements <class>`); an `implements <interface>`\n// resolves to nothing and emits no edge, honestly, until interface symbols exist.\nfunction implementsInfo(\n classHeritage: Parser.SyntaxNode,\n): { name: string; line: number }[] {\n const out: { name: string; line: number }[] = []\n for (let i = 0; i < classHeritage.namedChildCount; i++) {\n const clause = classHeritage.namedChild(i)\n if (!clause || clause.type !== 'implements_clause') continue\n for (let j = 0; j < clause.namedChildCount; j++) {\n const t = clause.namedChild(j)\n if (!t) continue\n if (t.type === 'type_identifier') {\n out.push({ name: t.text, line: t.startPosition.row + 1 })\n } else if (t.type === 'generic_type') {\n const nameNode = t.childForFieldName('name')\n if (nameNode && nameNode.type === 'type_identifier') {\n out.push({ name: nameNode.text, line: nameNode.startPosition.row + 1 })\n }\n }\n }\n }\n return out\n}\n\n// Collect the ES-import bindings of a file: local name → { specifier, importedName }.\n// Only named imports (including `as`-aliased) are collected — the binding names the\n// exported symbol exactly. Default imports (`import X from`) and namespace imports\n// (`import * as ns`) are skipped: resolving the former needs the target's\n// export-default declaration and the latter is member access, both deferred rather\n// than guessed. CommonJS `require` destructuring is likewise out of this slice.\nfunction collectImportBindings(\n root: Parser.SyntaxNode,\n): Map<string, { specifier: string; importedName: string }> {\n const out = new Map<string, { specifier: string; importedName: string }>()\n const visit = (node: Parser.SyntaxNode): void => {\n if (node.type === 'import_statement') {\n const source = node.childForFieldName('source')\n const specifier = source ? stringInner(source) : null\n if (specifier) {\n for (let i = 0; i < node.namedChildCount; i++) {\n const clause = node.namedChild(i)\n if (clause?.type === 'import_clause') collectClause(clause, specifier, out)\n }\n }\n return\n }\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) visit(child)\n }\n }\n visit(root)\n return out\n}\n\nfunction collectClause(\n clause: Parser.SyntaxNode,\n specifier: string,\n out: Map<string, { specifier: string; importedName: string }>,\n): void {\n for (let i = 0; i < clause.namedChildCount; i++) {\n const child = clause.namedChild(i)\n if (!child) continue\n if (child.type === 'named_imports') {\n for (let j = 0; j < child.namedChildCount; j++) {\n const spec = child.namedChild(j)\n if (spec?.type !== 'import_specifier') continue\n const nameNode = spec.childForFieldName('name')\n const aliasNode = spec.childForFieldName('alias')\n if (!nameNode) continue\n const importedName = nameNode.text\n const localName = aliasNode ? aliasNode.text : importedName\n out.set(localName, { specifier, importedName })\n }\n }\n // `identifier` (default import) and `namespace_import` are intentionally\n // skipped — see collectImportBindings.\n }\n}\n\n// The interior text of a string literal node (its `string_fragment` child), or a\n// quote-stripped fallback. Mirrors imports.ts's `stringLiteralText`.\nfunction stringInner(node: Parser.SyntaxNode): string | null {\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i)\n if (child?.type === 'string_fragment') return child.text\n }\n const raw = node.text\n return raw.length >= 2 ? raw.slice(1, -1) : null\n}\n\n// A heritage or call edge to emit once its target resolves.\ninterface EdgeRequest {\n sourceSid: string\n targetName: string\n wantKind: SymbolKind\n edgeType: typeof EdgeType.INHERITS | typeof EdgeType.IMPLEMENTS | typeof EdgeType.CALLS\n line: number\n}\n\nexport async function addSymbolEdges(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const parsers = new Map<string, Parser>()\n const parserForExt = (ext: string): Parser | null => {\n const grammar = GRAMMAR_BY_EXT[ext]\n if (!grammar) return null\n let parser = parsers.get(ext)\n if (!parser) {\n parser = new Parser()\n parser.setLanguage(grammar)\n parsers.set(ext, parser)\n }\n return parser\n }\n\n let edgesAdded = 0\n\n for (const service of services) {\n const serviceName = service.pkg.name\n const tsPaths: TsPathConfig | null = await loadTsPathConfig(service.dir)\n const files = await loadSourceFiles(service.dir)\n\n for (const file of files) {\n const parser = parserForExt(path.extname(file.path))\n if (!parser) continue\n const relPath = toPosix(path.relative(service.dir, file.path))\n const fileDir = path.dirname(file.path)\n\n let root: Parser.SyntaxNode\n try {\n root = parseSource(parser, file.content).rootNode\n } catch (err) {\n recordExtractionError('symbol edge extraction', file.path, err)\n continue\n }\n\n // Rebuild this file's symbol table (spans + ids) from the same collector\n // the node producer used, so ids and spans line up exactly.\n const disambiguated = disambiguate(collectSymbolDefs(root))\n const locals: LocalSymbol[] = disambiguated.map(({ def, disambiguator }) => ({\n sid: symbolId(serviceName, relPath, def.qualname, disambiguator),\n qualname: def.qualname,\n kind: def.kind,\n startLine: def.startLine,\n endLine: def.endLine,\n }))\n if (locals.length === 0) continue\n\n // A bare (dot-free) qualname that appears exactly once in the file — the\n // clean, disambiguator-free id. Same-name siblings (overloads, a module and\n // a nested definition sharing a name) are all disambiguated, so none lands\n // here: existence in this map *is* the \"resolves to exactly one\" test.\n const uniqueByBareName = new Map<string, LocalSymbol>()\n for (const { def, disambiguator } of disambiguated) {\n if (disambiguator !== undefined) continue\n if (def.qualname.includes('.')) continue // a method, not a callable bare name\n const local = locals.find((l) => l.qualname === def.qualname && l.startLine === def.startLine)\n if (local) uniqueByBareName.set(def.qualname, local)\n }\n\n // The symbol a definition span identifies exactly (used to find a class's own\n // node from its class_declaration span).\n const localBySpan = new Map<string, LocalSymbol>()\n for (const l of locals) localBySpan.set(`${l.startLine}:${l.endLine}`, l)\n\n // Resolve every named-import binding's specifier to its intra-service file\n // once, so target resolution during the walk stays a pure map lookup.\n const bindings = collectImportBindings(root)\n const resolvedImports = new Map<string, ResolvedImport>()\n for (const [localName, binding] of bindings) {\n const targetRelPath = await resolveJsImport(binding.specifier, fileDir, service.dir, tsPaths)\n if (targetRelPath) {\n resolvedImports.set(localName, { targetRelPath, importedName: binding.importedName })\n }\n }\n\n // Resolve a used name to exactly one SymbolNode of the wanted kind, or null.\n // Same-file first (a unique bare definition), then a resolved named import\n // whose exported symbol exists in the target file at the wanted kind.\n const resolveTarget = (name: string, wantKind: SymbolKind): string | null => {\n const local = uniqueByBareName.get(name)\n if (local) return local.kind === wantKind ? local.sid : null\n\n const imp = resolvedImports.get(name)\n if (imp) {\n const candidate = symbolId(serviceName, imp.targetRelPath, imp.importedName)\n if (graph.hasNode(candidate)) {\n const node = graph.getNodeAttributes(candidate) as SymbolNode\n if (node.type === NodeType.SymbolNode && node.kind === wantKind) return candidate\n }\n }\n return null\n }\n\n // Innermost symbol whose span contains `line` — the enclosing caller of a\n // call site. Smallest containing span wins (a call inside a method belongs to\n // the method, not the class), matching ingest's observed-landing choice.\n const enclosingSymbol = (line: number): LocalSymbol | null => {\n let best: LocalSymbol | null = null\n for (const l of locals) {\n if (line < l.startLine || line > l.endLine) continue\n if (!best || l.endLine - l.startLine < best.endLine - best.startLine) best = l\n }\n return best\n }\n\n // Walk once, collecting the confident edge requests.\n const requests: EdgeRequest[] = []\n const walk = (node: Parser.SyntaxNode): void => {\n if (\n node.type === 'class_declaration' ||\n node.type === 'abstract_class_declaration' ||\n node.type === 'class'\n ) {\n const self = localBySpan.get(`${node.startPosition.row + 1}:${node.endPosition.row + 1}`)\n if (self && self.kind === 'class') {\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (!child || child.type !== 'class_heritage') continue\n const ext = extendsInfo(child)\n if (ext) {\n requests.push({\n sourceSid: self.sid,\n targetName: ext.name,\n wantKind: 'class',\n edgeType: EdgeType.INHERITS,\n line: ext.line,\n })\n }\n for (const impl of implementsInfo(child)) {\n requests.push({\n sourceSid: self.sid,\n targetName: impl.name,\n wantKind: 'class',\n edgeType: EdgeType.IMPLEMENTS,\n line: impl.line,\n })\n }\n }\n }\n } else if (node.type === 'call_expression') {\n const fn = node.childForFieldName('function')\n // Only a bare identifier callee is resolvable without a receiver type —\n // `obj.foo()` (member_expression), `arr[k]()` (subscript), and dynamic\n // callees are left to observed / SCIP (ADR-158 §3).\n if (fn && fn.type === 'identifier') {\n const line = node.startPosition.row + 1\n const caller = enclosingSymbol(line)\n if (caller) {\n requests.push({\n sourceSid: caller.sid,\n targetName: fn.text,\n wantKind: 'function',\n edgeType: EdgeType.CALLS,\n line,\n })\n }\n }\n }\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) walk(child)\n }\n }\n walk(root)\n\n for (const req of requests) {\n const targetSid = resolveTarget(req.targetName, req.wantKind)\n if (!targetSid) continue\n if (targetSid === req.sourceSid) continue // no self-loop (recursion / self-reference)\n\n const edgeId = extractedEdgeId(req.sourceSid, targetSid, req.edgeType)\n if (graph.hasEdge(edgeId)) continue\n const edge: GraphEdge = {\n id: edgeId,\n source: req.sourceSid,\n target: targetSid,\n type: req.edgeType,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: relPath,\n line: req.line,\n snippet: snippet(file.content, req.line),\n },\n }\n graph.addEdgeWithKey(edgeId, req.sourceSid, targetSid, edge)\n edgesAdded++\n }\n }\n }\n\n return { nodesAdded: 0, edgesAdded }\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport type { GraphEdge, ServerActionNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n serverActionId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport { isTestPath, type DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { ensureFileNode, loadSourceFiles, snippet, toPosix } from './calls/shared.js'\nimport { GRAMMAR_BY_EXT, parseSource } from './symbols.js'\nimport { loadTsPathConfig, resolveJsImport, type TsPathConfig } from './imports.js'\n\n// Next.js Server Action extraction (ADR-168). A Server Action is the mutation\n// boundary of a modern App Router app: an exported async function marked\n// `\"use server\"`, either by a module-level directive (every exported async\n// function in the file is an action) or an in-body directive (that one\n// function). The client references the imported action binding — a call, an\n// `action={fn}` JSX attribute, a `useActionState(fn)` / `fn.bind(...)` argument —\n// and Next posts to it, serialising the call to an opaque `Next-Action` hash. At\n// HTTP grain the whole surface collapses onto one POST edge, so this producer\n// mints a ServerActionNode per exported action to recover the per-action\n// topology, owned by its file through `file ──CONTAINS──▶ action`, and stitches\n// `file ──CALLS──▶ action` on any client reference resolved through the import\n// graph.\n//\n// EXTRACTED-first: OBSERVED fusion is deferred (the `Next-Action` hash carries no\n// action name), the same posture GraphQLOperationNode took before its static\n// extractor. Scope is gated by the `next` manifest dependency, JS/TS only,\n// matching the SymbolNode grain the client-stitch resolves against. The\n// load-bearing discipline (file-awareness.md §6): the client-stitch fires only\n// when the imported binding resolves through `resolveJsImport` to exactly one\n// known ServerActionNode — never fuzzy-matched — so the edge is deterministic.\n\n// The interior text of a JS/TS string literal node (its `string_fragment`\n// child), quote-stripped. Mirrors imports.ts's `stringLiteralText`.\nfunction stringLiteralText(node: Parser.SyntaxNode): string | null {\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i)\n if (child?.type === 'string_fragment') return child.text\n }\n const raw = node.text\n return raw.length >= 2 ? raw.slice(1, -1) : null\n}\n\n// A `\"use server\"` directive is a leading string-literal expression statement in\n// a module body or a function's statement block. Scan only the leading run of\n// string-literal statements (the directive prologue): a `\"use strict\"` /\n// `\"use client\"` before it is skipped, and the first non-directive statement ends\n// the prologue. Returns true when one of the leading directives is `use server`.\nfunction hasUseServerDirective(container: Parser.SyntaxNode): boolean {\n for (let i = 0; i < container.namedChildCount; i++) {\n const stmt = container.namedChild(i)\n if (!stmt || stmt.type !== 'expression_statement') break\n const expr = stmt.namedChild(0)\n if (!expr || expr.type !== 'string') break\n if (stringLiteralText(expr) === 'use server') return true\n // Another leading directive (`use strict` / `use client`) — keep scanning.\n }\n return false\n}\n\n// A function node is async when it carries the `async` keyword token. tree-sitter\n// exposes it as an anonymous child of the function / arrow node.\nfunction isAsyncFunction(node: Parser.SyntaxNode): boolean {\n for (let i = 0; i < node.childCount; i++) {\n if (node.child(i)?.type === 'async') return true\n }\n return false\n}\n\n// One exported async function the file declares — the unit a `\"use server\"`\n// directive promotes to an action. `bodyNode` is the function's statement block\n// (for the in-body directive test) or null for a concise-body arrow.\ninterface ExportedAsyncFn {\n exportName: string\n fnNode: Parser.SyntaxNode\n bodyNode: Parser.SyntaxNode | null\n line: number\n}\n\n// Collect the file's directly-exported async functions: `export async function\n// f(){}` and `export const f = async () => {}` / `= async function(){}`. A\n// destructuring export, a re-export clause (`export { f }`), and a default export\n// are out of this slice — the direct-declaration forms are the idiomatic action\n// shape. Sync exports are skipped (a Server Action must be async).\nfunction collectExportedAsyncFns(root: Parser.SyntaxNode): ExportedAsyncFn[] {\n const out: ExportedAsyncFn[] = []\n const visit = (node: Parser.SyntaxNode): void => {\n if (node.type === 'export_statement') {\n const decl = node.childForFieldName('declaration')\n const line = node.startPosition.row + 1\n if (decl?.type === 'function_declaration') {\n const name = decl.childForFieldName('name')?.text\n if (name && isAsyncFunction(decl)) {\n out.push({ exportName: name, fnNode: decl, bodyNode: decl.childForFieldName('body'), line })\n }\n } else if (decl?.type === 'lexical_declaration' || decl?.type === 'variable_declaration') {\n for (let i = 0; i < decl.namedChildCount; i++) {\n const d = decl.namedChild(i)\n if (d?.type !== 'variable_declarator') continue\n const nameNode = d.childForFieldName('name')\n const value = d.childForFieldName('value')\n if (nameNode?.type !== 'identifier' || !value) continue\n if (\n (value.type === 'arrow_function' ||\n value.type === 'function' ||\n value.type === 'function_expression' ||\n value.type === 'generator_function') &&\n isAsyncFunction(value)\n ) {\n out.push({\n exportName: nameNode.text,\n fnNode: value,\n bodyNode: value.childForFieldName('body'),\n line,\n })\n }\n }\n }\n }\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) visit(child)\n }\n }\n visit(root)\n return out\n}\n\n// The named-import bindings of a file: local name → the exported name it names in\n// its source module. Only named imports (including `as`-aliased) are collected —\n// a Server Action is a named export, so a default / namespace import can't name\n// one. `import type` is skipped (a type reference is not a value call path).\nfunction collectNamedImports(\n root: Parser.SyntaxNode,\n): Map<string, { specifier: string; importedName: string }> {\n const out = new Map<string, { specifier: string; importedName: string }>()\n const visit = (node: Parser.SyntaxNode): void => {\n if (node.type === 'import_statement') {\n // `import type { … }` — skip the whole statement.\n for (let i = 0; i < node.childCount; i++) {\n if (node.child(i)?.type === 'type') return\n }\n const source = node.childForFieldName('source')\n const specifier = source ? stringLiteralText(source) : null\n if (specifier) {\n for (let i = 0; i < node.namedChildCount; i++) {\n const clause = node.namedChild(i)\n if (clause?.type !== 'import_clause') continue\n for (let j = 0; j < clause.namedChildCount; j++) {\n const named = clause.namedChild(j)\n if (named?.type !== 'named_imports') continue\n for (let k = 0; k < named.namedChildCount; k++) {\n const spec = named.namedChild(k)\n if (spec?.type !== 'import_specifier') continue\n // A per-specifier `import { type Foo }` marks a type-only binding.\n let isType = false\n for (let t = 0; t < spec.childCount; t++) {\n if (spec.child(t)?.type === 'type') isType = true\n }\n if (isType) continue\n const nameNode = spec.childForFieldName('name')\n if (!nameNode) continue\n const importedName = nameNode.text\n const localName = spec.childForFieldName('alias')?.text ?? importedName\n out.set(localName, { specifier, importedName })\n }\n }\n }\n }\n return\n }\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) visit(child)\n }\n }\n visit(root)\n return out\n}\n\n// First reference line for each name in `wanted`, walking value-position\n// `identifier` uses and skipping the import declarations themselves. A call\n// (`fn()`), a JSX attribute (`action={fn}`), a `useActionState(fn)` argument, and\n// a `fn.bind(...)` receiver are all plain `identifier` nodes — property keys and\n// shorthand are their own node types, so they never match — which is what closes\n// the form-action gap: referenced, not only called.\nfunction firstReferenceLines(\n root: Parser.SyntaxNode,\n wanted: Set<string>,\n): Map<string, number> {\n const lines = new Map<string, number>()\n const visit = (node: Parser.SyntaxNode): void => {\n // Don't descend into the import that declares the binding — the import\n // specifier is not a usage reference.\n if (node.type === 'import_statement') return\n if (node.type === 'identifier' && wanted.has(node.text) && !lines.has(node.text)) {\n lines.set(node.text, node.startPosition.row + 1)\n }\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) visit(child)\n }\n }\n visit(root)\n return lines\n}\n\nexport async function addServerActions(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const parsers = new Map<string, Parser>()\n const parserForExt = (ext: string): Parser | null => {\n const grammar = GRAMMAR_BY_EXT[ext]\n if (!grammar) return null\n let parser = parsers.get(ext)\n if (!parser) {\n parser = new Parser()\n parser.setLanguage(grammar)\n parsers.set(ext, parser)\n }\n return parser\n }\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const deps = {\n ...(service.pkg.dependencies ?? {}),\n ...(service.pkg.devDependencies ?? {}),\n }\n // Gated on the `next` dependency, the same registry discipline addRoutes\n // uses — a service that isn't a Next.js app declares no Server Actions.\n if (deps['next'] === undefined) continue\n\n const tsPaths: TsPathConfig | null = await loadTsPathConfig(service.dir)\n const files = await loadSourceFiles(service.dir)\n\n // ── Pass 1: mint an action node per exported `\"use server\"` function. ──\n for (const file of files) {\n if (isTestPath(file.path)) continue\n const parser = parserForExt(path.extname(file.path))\n if (!parser) continue\n const relPath = toPosix(path.relative(service.dir, file.path))\n\n let root: Parser.SyntaxNode\n try {\n root = parseSource(parser, file.content).rootNode\n } catch (err) {\n recordExtractionError('server action extraction', file.path, err)\n continue\n }\n\n const moduleDirective = hasUseServerDirective(root)\n const exported = collectExportedAsyncFns(root)\n const actions = exported.filter(\n (fn) => moduleDirective || (fn.bodyNode ? hasUseServerDirective(fn.bodyNode) : false),\n )\n if (actions.length === 0) continue\n\n // The file owns its actions; ensure its FileNode (and the owning\n // `service ──CONTAINS──▶ file` edge) exists before an action hangs off it.\n const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relPath,\n )\n nodesAdded += fn\n edgesAdded += fe\n\n for (const action of actions) {\n const aid = serverActionId(service.pkg.name, relPath, action.exportName)\n if (!graph.hasNode(aid)) {\n const node: ServerActionNode = {\n id: aid,\n type: NodeType.ServerActionNode,\n name: action.exportName,\n service: service.pkg.name,\n module: relPath,\n exportName: action.exportName,\n path: relPath,\n line: action.line,\n discoveredVia: 'static',\n }\n graph.addNode(aid, node)\n nodesAdded++\n }\n // `file ──CONTAINS──▶ action` — structural ownership, the same tier and\n // shape as `file ──CONTAINS──▶ symbol` (file-awareness.md §2), evidence\n // pinned to the action's declaration file:line.\n const containsId = extractedEdgeId(fileNodeId, aid, EdgeType.CONTAINS)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: fileNodeId,\n target: aid,\n type: EdgeType.CONTAINS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: relPath,\n line: action.line,\n snippet: snippet(file.content, action.line),\n },\n }\n graph.addEdgeWithKey(containsId, fileNodeId, aid, edge)\n edgesAdded++\n }\n }\n }\n\n // ── Pass 2: client-stitch `file ──CALLS──▶ action` on any reference to an ──\n // imported action binding, resolved through the import graph.\n for (const file of files) {\n if (isTestPath(file.path)) continue\n const parser = parserForExt(path.extname(file.path))\n if (!parser) continue\n const relPath = toPosix(path.relative(service.dir, file.path))\n const fileDir = path.dirname(file.path)\n\n let root: Parser.SyntaxNode\n try {\n root = parseSource(parser, file.content).rootNode\n } catch (err) {\n recordExtractionError('server action stitch', file.path, err)\n continue\n }\n\n // Resolve each named import to a known ServerActionNode; a local binding\n // that resolves to one is an action reference in this file.\n const bindings = collectNamedImports(root)\n const actionBindings = new Map<string, string>() // local name → action id\n for (const [local, binding] of bindings) {\n const target = await resolveJsImport(binding.specifier, fileDir, service.dir, tsPaths)\n if (!target) continue\n const aid = serverActionId(service.pkg.name, target, binding.importedName)\n if (graph.hasNode(aid)) actionBindings.set(local, aid)\n }\n if (actionBindings.size === 0) continue\n\n const refLines = firstReferenceLines(root, new Set(actionBindings.keys()))\n\n let clientFileId: string | null = null\n for (const [local, aid] of actionBindings) {\n const line = refLines.get(local)\n if (line === undefined) continue // imported but never referenced — no edge, honestly.\n if (clientFileId === null) {\n const ensured = ensureFileNode(graph, service.pkg.name, service.node.id, relPath)\n clientFileId = ensured.fileNodeId\n nodesAdded += ensured.nodesAdded\n edgesAdded += ensured.edgesAdded\n }\n // `file ──CALLS──▶ action` — a client reference to the action, resolved\n // through the import graph to exactly one known node. Reuses CALLS; no new\n // edge type (ADR-168). Deduped by the deterministic edge id, so multiple\n // references (a call and a JSX `action={fn}`) land one edge.\n const callsId = extractedEdgeId(clientFileId, aid, EdgeType.CALLS)\n if (graph.hasEdge(callsId)) continue\n const edge: GraphEdge = {\n id: callsId,\n source: clientFileId,\n target: aid,\n type: EdgeType.CALLS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: relPath,\n line,\n snippet: snippet(file.content, line),\n },\n }\n graph.addEdgeWithKey(callsId, clientFileId, aid, edge)\n edgesAdded++\n }\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport type {\n CompatibleDriver,\n ConfigNode,\n DatabaseNode,\n GraphEdge,\n GraphNode,\n ServiceNode,\n} from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n configId,\n databaseId,\n confidenceForExtracted,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport {\n checkCompatibility,\n checkDeprecatedApi,\n checkNodeEngineConstraint,\n checkPackageConflict,\n compatPairs,\n deprecatedApis,\n nodeEngineConstraints,\n packageConflicts,\n} from '../../compat.js'\nimport {\n cleanVersion,\n isConfigFile,\n makeEdgeId,\n type DiscoveredService,\n} from '../shared.js'\nimport { ensureFileNode, toPosix } from '../calls/shared.js'\nimport { dbConfigYamlParser } from './db-config-yaml.js'\nimport { dotenvParser } from './dotenv.js'\nimport { prismaParser } from './prisma.js'\nimport { drizzleParser } from './drizzle.js'\nimport { knexParser } from './knex.js'\nimport { ormconfigParser } from './ormconfig.js'\nimport { typeormParser } from './typeorm.js'\nimport { sequelizeParser } from './sequelize.js'\nimport { dockerComposeParser } from './docker-compose.js'\nimport type { DbConfig } from './shared.js'\n\nexport type { DbConfig } from './shared.js'\n\nexport interface DbParser {\n name: string\n parse(serviceDir: string): Promise<DbConfig[]>\n}\n\n// Registry — order is for tie-breaking only (first wins on identical host).\n// db-config.yaml stays first so the canonical demo behaviour matches today's\n// extraction byte-for-byte.\nexport const DB_PARSERS: DbParser[] = [\n dbConfigYamlParser,\n dotenvParser,\n prismaParser,\n drizzleParser,\n knexParser,\n ormconfigParser,\n typeormParser,\n sequelizeParser,\n dockerComposeParser,\n]\n\nfunction compatibleDriversFor(engine: string): CompatibleDriver[] {\n return compatPairs()\n .filter((p) => p.engine === engine)\n .map((p) => ({ name: p.driver, minVersion: p.minDriverVersion }))\n}\n\nfunction toDatabaseNode(config: DbConfig): DatabaseNode {\n return {\n id: databaseId(config.host),\n type: NodeType.DatabaseNode,\n name: config.database || config.host,\n engine: config.engine,\n engineVersion: config.engineVersion,\n compatibleDrivers: compatibleDriversFor(config.engine),\n host: config.host,\n port: config.port,\n }\n}\n\nexport function attachIncompatibilities(\n service: DiscoveredService,\n configs: DbConfig[],\n): void {\n const deps = { ...(service.pkg.dependencies ?? {}), ...(service.pkg.devDependencies ?? {}) }\n const incompatibilities: NonNullable<ServiceNode['incompatibilities']> = []\n const seen = new Set<string>()\n\n // 1. driver-engine — original behaviour. Per (db config, configured driver\n // pair) check that the declared driver version meets the engine threshold.\n for (const config of configs) {\n for (const pair of compatPairs()) {\n if (pair.engine !== config.engine) continue\n const declaredVersion = cleanVersion(deps[pair.driver])\n if (!declaredVersion) continue\n const result = checkCompatibility(\n pair.driver,\n declaredVersion,\n config.engine,\n config.engineVersion,\n )\n if (!result.compatible && result.reason) {\n const key = `driver-engine|${pair.driver}@${declaredVersion}|${config.engine}@${config.engineVersion}`\n if (seen.has(key)) continue\n seen.add(key)\n incompatibilities.push({\n kind: 'driver-engine',\n driver: pair.driver,\n driverVersion: declaredVersion,\n engine: config.engine,\n engineVersion: config.engineVersion,\n reason: result.reason,\n })\n }\n }\n }\n\n // 2. node-engine — service's `engines.node` vs each declared dep that has a\n // matrix-recorded minimum.\n const serviceNodeEngine = service.node.nodeEngine ?? service.pkg.engines?.node\n for (const constraint of nodeEngineConstraints()) {\n const declared = cleanVersion(deps[constraint.package])\n if (!declared) continue\n const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine)\n if (!result.compatible && result.reason) {\n const key = `node-engine|${constraint.package}@${declared}|${serviceNodeEngine ?? ''}`\n if (seen.has(key)) continue\n seen.add(key)\n incompatibilities.push({\n kind: 'node-engine',\n package: constraint.package,\n packageVersion: declared,\n requiredNodeVersion: result.requiredNodeVersion ?? constraint.minNodeVersion,\n ...(serviceNodeEngine ? { declaredNodeEngine: serviceNodeEngine } : {}),\n reason: result.reason,\n })\n }\n }\n\n // 3. package-conflict — pair like react-query 5+ requiring react 18+.\n for (const conflict of packageConflicts()) {\n const declared = cleanVersion(deps[conflict.package])\n if (!declared) continue\n const requiredVersion = cleanVersion(deps[conflict.requires.name])\n const result = checkPackageConflict(conflict, declared, requiredVersion)\n if (!result.compatible && result.reason) {\n const key = `package-conflict|${conflict.package}@${declared}|${conflict.requires.name}@${requiredVersion ?? 'missing'}`\n if (seen.has(key)) continue\n seen.add(key)\n incompatibilities.push({\n kind: 'package-conflict',\n package: conflict.package,\n packageVersion: declared,\n requires: conflict.requires,\n ...(requiredVersion ? { foundVersion: requiredVersion } : {}),\n reason: result.reason,\n })\n }\n }\n\n // 4. deprecated-api — flag presence of a known-deprecated package.\n for (const rule of deprecatedApis()) {\n const declared = cleanVersion(deps[rule.package])\n if (declared === undefined) continue\n const result = checkDeprecatedApi(rule, declared)\n if (!result.compatible && result.reason) {\n const key = `deprecated-api|${rule.package}@${declared}`\n if (seen.has(key)) continue\n seen.add(key)\n incompatibilities.push({\n kind: 'deprecated-api',\n package: rule.package,\n packageVersion: declared,\n reason: result.reason,\n })\n }\n }\n\n if (incompatibilities.length > 0) service.node.incompatibilities = incompatibilities\n}\n\n// Phase 2 — for each service, run every parser and merge their DbConfigs by\n// host. Each unique host produces one DatabaseNode + CONNECTS_TO edge from the\n// service. The parser registry decides priority on tie; the demo's\n// db-config.yaml stays first so its `engineVersion: 15` continues to win.\nexport async function addDatabasesAndCompat(\n graph: NeatGraph,\n services: DiscoveredService[],\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const merged = new Map<string, DbConfig>()\n for (const parser of DB_PARSERS) {\n let configs: DbConfig[]\n try {\n configs = await parser.parse(service.dir)\n } catch (err) {\n console.warn(\n `[neat] ${parser.name} parser failed on ${service.node.name}: ${(err as Error).message}`,\n )\n continue\n }\n for (const config of configs) {\n if (!config.host) continue\n if (!merged.has(config.host)) merged.set(config.host, config)\n }\n }\n\n const allConfigs = [...merged.values()]\n for (const config of allConfigs) {\n const dbNode = toDatabaseNode(config)\n if (!graph.hasNode(dbNode.id)) {\n graph.addNode(dbNode.id, { ...dbNode, discoveredVia: 'static' })\n nodesAdded++\n } else {\n // OTel ingest may have auto-created a minimal node at this id. Merge\n // per ADR-033: static fields override OTel-derived fields, discoveredVia\n // flips to 'merged' when both layers contributed.\n const existing = graph.getNodeAttributes(dbNode.id) as DatabaseNode\n const mergedDiscoveredVia: 'static' | 'otel' | 'merged' =\n existing.discoveredVia === 'otel' ? 'merged' : 'static'\n graph.replaceNodeAttributes(dbNode.id, {\n ...existing,\n ...dbNode,\n discoveredVia: mergedDiscoveredVia,\n })\n }\n // file-awareness §1 — the connection is declared in a config file; that\n // file is the relationship origin. ensureFileNode creates the FileNode +\n // CONTAINS edge so the CONNECTS_TO lands file-grained, not service-level.\n const relConfigFile = toPosix(path.relative(service.dir, config.sourceFile))\n const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relConfigFile,\n )\n nodesAdded += fn\n edgesAdded += fe\n const evidenceFile = toPosix(path.relative(scanPath, config.sourceFile))\n const edge: GraphEdge = {\n id: makeEdgeId(fileNodeId, dbNode.id, EdgeType.CONNECTS_TO),\n source: fileNodeId,\n target: dbNode.id,\n type: EdgeType.CONNECTS_TO,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile },\n }\n if (!graph.hasEdge(edge.id)) {\n graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n\n // Service-level declared DB target. When a service declares exactly one DB\n // connection in config — a `postgres://…` string in .env, a db-config.yaml,\n // a prisma/drizzle/knex datasource — record where it's pointed on the\n // ServiceNode as `dbConnectionTarget` (host[:port]) and link the service to\n // the config that declared it with an EXTRACTED CONFIGURED_BY edge. This is\n // the service-grained declared intent the host-mismatch divergence compares\n // against the service-grained OBSERVED CONNECTS_TO (file-awareness §7 —\n // compare at the shared grain; a DB OTel span carries no call site, so the\n // comparison is service-level). Without this, `declaredHostFor` reads an\n // unpopulated field and host-mismatch never fires.\n //\n // We hold to a *single* declared target: with two or more distinct hosts\n // the single-target model is ambiguous and would flag every observed host\n // but one as a false mismatch, so we decline rather than guess.\n if (allConfigs.length === 1) {\n const primary = allConfigs[0]!\n service.node.dbConnectionTarget = primary.port\n ? `${primary.host}:${primary.port}`\n : primary.host\n\n // Link to the config file that declared the connection. Mirror configs.ts's\n // ConfigNode id (Phase 3 runs after this and is idempotent on the node);\n // the CONFIGURED_BY edge is service-grained here, matching the grain of\n // the declared target it backs.\n const relPath = path.relative(scanPath, primary.sourceFile)\n const cfgId = configId(relPath)\n if (!graph.hasNode(cfgId)) {\n const cfgNode: ConfigNode = {\n id: cfgId,\n type: NodeType.ConfigNode,\n name: path.basename(primary.sourceFile),\n path: relPath,\n fileType: isConfigFile(path.basename(primary.sourceFile)).fileType || 'config',\n }\n graph.addNode(cfgId, cfgNode)\n nodesAdded++\n }\n const cfgEdge: GraphEdge = {\n id: makeEdgeId(service.node.id, cfgId, EdgeType.CONFIGURED_BY),\n source: service.node.id,\n target: cfgId,\n type: EdgeType.CONFIGURED_BY,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: toPosix(relPath) },\n }\n if (!graph.hasEdge(cfgEdge.id)) {\n graph.addEdgeWithKey(cfgEdge.id, cfgEdge.source, cfgEdge.target, cfgEdge)\n edgesAdded++\n }\n }\n\n // Run all kinds of incompat checks even for services with no db connection\n // — node-engine / package-conflict / deprecated-api don't depend on db.\n attachIncompatibilities(service, allConfigs)\n if (graph.hasNode(service.node.id)) {\n // Merge with whatever's on the node already (aliases from γ #75 land\n // before this phase), so the writeback doesn't drop fields populated by\n // earlier passes.\n const current = graph.getNodeAttributes(service.node.id) as ServiceNode\n const updated: ServiceNode = {\n ...current,\n ...(service.node as ServiceNode),\n ...(current.aliases ? { aliases: current.aliases } : {}),\n }\n // attachIncompatibilities only sets the field when there's something to\n // flag. On a re-extract (`neat watch`, `POST /graph/scan`), a stale\n // entry on `current` would otherwise survive the spread and leave the\n // graph reporting a problem the new manifest no longer has.\n if (!service.node.incompatibilities || service.node.incompatibilities.length === 0) {\n delete (updated as { incompatibilities?: unknown }).incompatibilities\n }\n // Same stale-survivor guard for the declared DB target: if this pass found\n // no single declared connection, a value left on `current` from a prior\n // extract would otherwise survive the spread.\n if (!service.node.dbConnectionTarget) {\n delete (updated as { dbConnectionTarget?: unknown }).dbConnectionTarget\n }\n graph.replaceNodeAttributes(service.node.id, updated as unknown as GraphNode)\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport { exists, readYaml } from '../shared.js'\nimport type { DbConfig } from './shared.js'\n\ninterface DbConfigYaml {\n host: string\n port?: number\n database: string\n engine: string\n engineVersion?: string | number\n}\n\n// The original db-config.yaml format, kept as a parser so the demo continues\n// to work. Engine + version are explicit here, so this is the one source that\n// can produce a real engineVersion without inference.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const yamlPath = path.join(serviceDir, 'db-config.yaml')\n if (!(await exists(yamlPath))) return []\n const raw = await readYaml<DbConfigYaml>(yamlPath)\n return [\n {\n host: raw.host,\n port: raw.port,\n database: raw.database,\n engine: raw.engine,\n engineVersion: raw.engineVersion !== undefined ? String(raw.engineVersion) : 'unknown',\n sourceFile: yamlPath,\n },\n ]\n}\n\nexport const dbConfigYamlParser = { name: 'db-config.yaml', parse }\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { isConfigFile } from '../shared.js'\nimport { parseConnectionString, type DbConfig } from './shared.js'\n\n// Both the `_URL` and `_URI` spellings for every engine — apps split roughly\n// evenly between them and the two are interchangeable. `MONGODB_URL` in\n// particular is the single most common Mongo env-var name (the Mongoose docs\n// and most boilerplates use it), and its earlier absence meant an app's own\n// database connection went unextracted, so the runtime span had nothing to fuse\n// onto and minted a standalone `database:<host>` node instead (#832).\nconst CONNECTION_KEYS = new Set([\n 'DATABASE_URL',\n 'DATABASE_URI',\n 'DB_URL',\n 'DB_URI',\n 'POSTGRES_URL',\n 'POSTGRES_URI',\n 'POSTGRESQL_URL',\n 'POSTGRESQL_URI',\n 'MYSQL_URL',\n 'MYSQL_URI',\n 'MONGODB_URL',\n 'MONGODB_URI',\n 'MONGO_URL',\n 'MONGO_URI',\n 'REDIS_URL',\n 'REDIS_URI',\n])\n\n// Per ADR-016, .env contents do not land in any snapshot. We read them here\n// only to derive a transient DbConfig — the value never reaches a ConfigNode.\nfunction parseDotenvLine(line: string): { key: string; value: string } | null {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith('#')) return null\n const eq = trimmed.indexOf('=')\n if (eq < 0) return null\n const key = trimmed.slice(0, eq).trim()\n let value = trimmed.slice(eq + 1).trim()\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1)\n }\n return { key, value }\n}\n\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const entries = await fs.readdir(serviceDir, { withFileTypes: true }).catch(() => [])\n const configs: DbConfig[] = []\n const seen = new Set<string>()\n\n for (const entry of entries) {\n if (!entry.isFile()) continue\n const match = isConfigFile(entry.name)\n if (!match.match || match.fileType !== 'env') continue\n\n const filePath = path.join(serviceDir, entry.name)\n const content = await fs.readFile(filePath, 'utf8')\n for (const line of content.split('\\n')) {\n const parsed = parseDotenvLine(line)\n if (!parsed) continue\n if (!CONNECTION_KEYS.has(parsed.key.toUpperCase())) continue\n const config = parseConnectionString(parsed.value)\n if (!config) continue\n const key = `${config.engine}://${config.host}:${config.port ?? ''}/${config.database}`\n if (seen.has(key)) continue\n seen.add(key)\n configs.push({ ...config, sourceFile: filePath })\n }\n }\n return configs\n}\n\nexport const dotenvParser = { name: '.env', parse }\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\n\nexport interface DbConfig {\n host: string\n port?: number\n database: string\n engine: string\n engineVersion: string // \"unknown\" when not statically determinable\n // Absolute path to the file the parser read this config from. Required —\n // ghost-edge cleanup keys CONNECTS_TO retirement on `evidence.file` per\n // ADR-032 / #140. Parsers that synthesize a DbConfig from a partial\n // source still set this to the file the partial came from.\n sourceFile: string\n}\n\n// Map a connection-string scheme to the engine name our compat matrix uses.\n// Schemes like \"postgres+asyncpg\" are normalised by stripping the dialect\n// suffix; anything we don't recognise returns null so the parser can decline.\nexport function schemeToEngine(scheme: string): string | null {\n const s = scheme.toLowerCase().split('+')[0]\n switch (s) {\n case 'postgres':\n case 'postgresql':\n return 'postgresql'\n case 'mysql':\n case 'mariadb':\n return 'mysql'\n case 'mongodb':\n case 'mongodb+srv':\n return 'mongodb'\n case 'redis':\n case 'rediss':\n return 'redis'\n case 'sqlite':\n return 'sqlite'\n default:\n return null\n }\n}\n\n// Returns the inner DbConfig fields without sourceFile — every caller spreads\n// the parser's own file path on top. Type makes that contract explicit.\nexport type ParsedConnectionConfig = Omit<DbConfig, 'sourceFile'>\n\nexport function parseConnectionString(url: string): ParsedConnectionConfig | null {\n const m = url.match(\n /^(?<scheme>[a-z][a-z+]*):\\/\\/(?:[^@/]+(?::[^@]*)?@)?(?<host>[^:/?]+)(?::(?<port>\\d+))?(?:\\/(?<db>[^?#]*))?/i,\n )\n if (!m || !m.groups) return null\n const engine = schemeToEngine(m.groups.scheme!)\n if (!engine) return null\n return {\n host: m.groups.host!,\n port: m.groups.port ? Number(m.groups.port) : undefined,\n database: m.groups.db ?? '',\n engine,\n engineVersion: 'unknown',\n }\n}\n\nexport async function readIfExists(filePath: string): Promise<string | null> {\n try {\n return await fs.readFile(filePath, 'utf8')\n } catch {\n return null\n }\n}\n\n// Resolve an env variable an ORM datasource references indirectly — Prisma's\n// `url = env(\"DATABASE_URL\")`, Drizzle/Knex reading `process.env.DATABASE_URL`\n// — from the service's `.env` files, so the parser can recover the real host\n// instead of falling back to a placeholder (ADR-141). Precedence follows the\n// dotenv convention: `.env.local` overrides `.env`, then any other `.env.*`.\n// Per ADR-016 the value is transient — it only derives the DatabaseNode host and\n// never lands in a ConfigNode or the snapshot.\nexport async function resolveEnvVar(serviceDir: string, name: string): Promise<string | null> {\n const entries = await fs.readdir(serviceDir, { withFileTypes: true }).catch(() => [])\n const envNames = entries\n .filter((e) => e.isFile() && (e.name === '.env' || e.name.startsWith('.env.')))\n .map((e) => e.name)\n const rank = (n: string): number => (n === '.env.local' ? 0 : n === '.env' ? 1 : 2)\n envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b))\n for (const fileName of envNames) {\n const content = await readIfExists(path.join(serviceDir, fileName))\n if (!content) continue\n for (const line of content.split('\\n')) {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith('#')) continue\n const eq = trimmed.indexOf('=')\n if (eq < 0) continue\n if (trimmed.slice(0, eq).trim() !== name) continue\n let value = trimmed.slice(eq + 1).trim()\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1)\n }\n return value || null\n }\n }\n return null\n}\n\nexport async function findFirst(\n serviceDir: string,\n candidates: string[],\n): Promise<string | null> {\n for (const rel of candidates) {\n const abs = path.join(serviceDir, rel)\n const content = await readIfExists(abs)\n if (content !== null) return abs\n }\n return null\n}\n\n// Engine name from a docker-compose `image:` value like \"postgres:15-alpine\"\n// or \"mysql/mysql-server:8.0\". Returns the engine + version when both are\n// resolvable, or null if the image isn't one we recognise.\nexport function engineFromImage(\n image: string,\n): { engine: string; engineVersion: string } | null {\n const lower = image.toLowerCase()\n const colon = lower.lastIndexOf(':')\n const repo = colon >= 0 ? lower.slice(0, colon) : lower\n const tag = colon >= 0 ? lower.slice(colon + 1) : 'latest'\n const last = repo.split('/').pop() ?? repo\n let engine: string | null = null\n if (last.startsWith('postgres')) engine = 'postgresql'\n else if (last.startsWith('mysql') || last.startsWith('mariadb')) engine = 'mysql'\n else if (last.startsWith('mongo')) engine = 'mongodb'\n else if (last.startsWith('redis')) engine = 'redis'\n else if (last.startsWith('sqlite')) engine = 'sqlite'\n if (!engine) return null\n // Strip everything after the major version digit run; \"15-alpine\" -> \"15\".\n const versionMatch = tag.match(/^(\\d+(?:\\.\\d+){0,2})/)\n return {\n engine,\n engineVersion: versionMatch ? versionMatch[1]! : 'unknown',\n }\n}\n","import path from 'node:path'\nimport {\n readIfExists,\n parseConnectionString,\n resolveEnvVar,\n schemeToEngine,\n type DbConfig,\n} from './shared.js'\n\n// Prisma's schema file declares datasources of the form:\n//\n// datasource db {\n// provider = \"postgresql\"\n// url = env(\"DATABASE_URL\")\n// }\n//\n// We match the provider directly. The url is almost always an env() indirection,\n// so we resolve the referenced variable from the service's .env to recover the\n// real host — that keys the DatabaseNode on the same host the dotenv parser\n// mints, so the two fuse to one declared node instead of a placeholder twin\n// (ADR-141). Only when the url is a literal, or the env var is absent, do we\n// fall back (literal parse, then a deterministic per-service placeholder).\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const schemaPath = path.join(serviceDir, 'prisma', 'schema.prisma')\n const content = await readIfExists(schemaPath)\n if (!content) return []\n\n const block = content.match(/datasource\\s+\\w+\\s*\\{([^}]*)\\}/s)\n if (!block) return []\n const body = block[1] ?? ''\n\n const providerMatch = body.match(/provider\\s*=\\s*\"([^\"]+)\"/)\n if (!providerMatch) return []\n const engine = schemeToEngine(providerMatch[1]!)\n if (!engine) return []\n\n const urlMatch = body.match(/url\\s*=\\s*\"([^\"]+)\"/)\n if (urlMatch) {\n const config = parseConnectionString(urlMatch[1]!)\n if (config) return [{ ...config, sourceFile: schemaPath }]\n }\n\n const envMatch = body.match(/url\\s*=\\s*env\\(\\s*\"([^\"]+)\"\\s*\\)/)\n if (envMatch) {\n const resolved = await resolveEnvVar(serviceDir, envMatch[1]!)\n if (resolved) {\n const config = parseConnectionString(resolved)\n if (config) return [{ ...config, sourceFile: schemaPath }]\n }\n }\n\n return [\n {\n host: `${engine}-prisma`,\n database: '',\n engine,\n engineVersion: 'unknown',\n sourceFile: schemaPath,\n },\n ]\n}\n\nexport const prismaParser = { name: 'prisma', parse }\n","import {\n findFirst,\n readIfExists,\n parseConnectionString,\n resolveEnvVar,\n schemeToEngine,\n type DbConfig,\n} from './shared.js'\n\nconst DIALECT_TO_ENGINE: Record<string, string> = {\n postgresql: 'postgresql',\n postgres: 'postgresql',\n pg: 'postgresql',\n mysql: 'mysql',\n mysql2: 'mysql',\n sqlite: 'sqlite',\n 'better-sqlite': 'sqlite',\n}\n\n// Drizzle's drizzle.config.{ts,js,mjs} declares a `dialect` plus credentials.\n// We can't safely eval the file, but the relevant bits are simple key/value\n// expressions — regex-extracted to stay sandbox-clean and dep-free.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const filePath = await findFirst(serviceDir, [\n 'drizzle.config.ts',\n 'drizzle.config.js',\n 'drizzle.config.mjs',\n ])\n if (!filePath) return []\n const content = await readIfExists(filePath)\n if (!content) return []\n\n const dialectMatch = content.match(/dialect\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)\n if (!dialectMatch) return []\n const engine =\n DIALECT_TO_ENGINE[dialectMatch[1]!.toLowerCase()] ?? schemeToEngine(dialectMatch[1]!)\n if (!engine) return []\n\n const urlMatch = content.match(\n /(?:url|connectionString)\\s*:\\s*['\"`]([a-z][a-z+]*:\\/\\/[^'\"`]+)['\"`]/i,\n )\n if (urlMatch) {\n const config = parseConnectionString(urlMatch[1]!)\n if (config) return [{ ...config, sourceFile: filePath }]\n }\n // `url: process.env.DATABASE_URL` (or `connectionString:`, bracket form) — the\n // common env-driven shape. Resolve it from the service's .env so the node keys\n // on the real host and dedups with the dotenv parser instead of a placeholder\n // (ADR-141, #807).\n const urlEnvMatch = content.match(\n /(?:url|connectionString)\\s*:\\s*process\\.env(?:\\.([A-Za-z_$][\\w$]*)|\\[\\s*['\"]([^'\"]+)['\"]\\s*\\])/,\n )\n if (urlEnvMatch) {\n const varName = urlEnvMatch[1] ?? urlEnvMatch[2]\n const resolved = varName ? await resolveEnvVar(serviceDir, varName) : null\n const config = resolved ? parseConnectionString(resolved) : null\n if (config) return [{ ...config, sourceFile: filePath }]\n }\n const hostMatch = content.match(/host\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)\n if (hostMatch) {\n const portMatch = content.match(/port\\s*:\\s*(\\d+)/)\n const dbMatch = content.match(/database\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)\n return [\n {\n host: hostMatch[1]!,\n port: portMatch ? Number(portMatch[1]) : undefined,\n database: dbMatch?.[1] ?? '',\n engine,\n engineVersion: 'unknown',\n sourceFile: filePath,\n },\n ]\n }\n return [\n { host: `${engine}-drizzle`, database: '', engine, engineVersion: 'unknown', sourceFile: filePath },\n ]\n}\n\nexport const drizzleParser = { name: 'drizzle', parse }\n","import {\n findFirst,\n readIfExists,\n parseConnectionString,\n resolveEnvVar,\n type DbConfig,\n} from './shared.js'\n\nconst CLIENT_TO_ENGINE: Record<string, string> = {\n pg: 'postgresql',\n postgres: 'postgresql',\n postgresql: 'postgresql',\n mysql: 'mysql',\n mysql2: 'mysql',\n sqlite3: 'sqlite',\n 'better-sqlite3': 'sqlite',\n}\n\n// knexfile.{js,ts} declares a client (one of pg/mysql/sqlite/...) plus a\n// connection string or host/port object. We pick whichever shape is in the\n// file and ignore environment-driven values we can't resolve statically.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const filePath = await findFirst(serviceDir, [\n 'knexfile.js',\n 'knexfile.ts',\n 'knexfile.cjs',\n 'knexfile.mjs',\n ])\n if (!filePath) return []\n const content = await readIfExists(filePath)\n if (!content) return []\n\n const clientMatch = content.match(/client\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)\n if (!clientMatch) return []\n const engine = CLIENT_TO_ENGINE[clientMatch[1]!.toLowerCase()]\n if (!engine) return []\n\n const urlMatch = content.match(\n /connection\\s*:\\s*['\"`]([a-z][a-z+]*:\\/\\/[^'\"`]+)['\"`]/i,\n )\n if (urlMatch) {\n const config = parseConnectionString(urlMatch[1]!)\n if (config) return [{ ...config, sourceFile: filePath }]\n }\n\n // `connection: process.env.DATABASE_URL` (bracket form too) — the whole\n // connection is a URL from env. Resolve it from the service's .env so the node\n // keys on the real host and dedups instead of a placeholder (ADR-141, #807).\n // `connection: { host: process.env.DB_HOST }` deliberately does not match — an\n // object of env fields keeps the placeholder rather than a fabricated URL.\n const urlEnvMatch = content.match(\n /connection\\s*:\\s*process\\.env(?:\\.([A-Za-z_$][\\w$]*)|\\[\\s*['\"]([^'\"]+)['\"]\\s*\\])/,\n )\n if (urlEnvMatch) {\n const varName = urlEnvMatch[1] ?? urlEnvMatch[2]\n const resolved = varName ? await resolveEnvVar(serviceDir, varName) : null\n const config = resolved ? parseConnectionString(resolved) : null\n if (config) return [{ ...config, sourceFile: filePath }]\n }\n\n const host = content.match(/host\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)?.[1]\n if (host) {\n const port = content.match(/port\\s*:\\s*(\\d+)/)?.[1]\n const database = content.match(/database\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)?.[1] ?? ''\n return [\n {\n host,\n port: port ? Number(port) : undefined,\n database,\n engine,\n engineVersion: 'unknown',\n sourceFile: filePath,\n },\n ]\n }\n\n return [{ host: `${engine}-knex`, database: '', engine, engineVersion: 'unknown', sourceFile: filePath }]\n}\n\nexport const knexParser = { name: 'knex', parse }\n","import path from 'node:path'\nimport { exists, readJson, readYaml } from '../shared.js'\nimport { schemeToEngine, type DbConfig } from './shared.js'\n\ninterface OrmConfigEntry {\n type?: string\n host?: string\n port?: number\n database?: string\n}\n\n// ormconfig.{json,yaml,yml} — TypeORM's legacy config file. Single object or\n// an array of named connections; we walk both.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n for (const candidate of ['ormconfig.json', 'ormconfig.yaml', 'ormconfig.yml']) {\n const abs = path.join(serviceDir, candidate)\n if (!(await exists(abs))) continue\n const raw = candidate.endsWith('.json')\n ? await readJson<OrmConfigEntry | OrmConfigEntry[]>(abs)\n : await readYaml<OrmConfigEntry | OrmConfigEntry[]>(abs)\n const entries = Array.isArray(raw) ? raw : [raw]\n\n const out: DbConfig[] = []\n for (const entry of entries) {\n if (!entry?.type || !entry.host) continue\n const engine = schemeToEngine(entry.type)\n if (!engine) continue\n out.push({\n host: entry.host,\n port: entry.port,\n database: entry.database ?? '',\n engine,\n engineVersion: 'unknown',\n sourceFile: abs,\n })\n }\n if (out.length > 0) return out\n }\n return []\n}\n\nexport const ormconfigParser = { name: 'ormconfig', parse }\n","import { findFirst, readIfExists, schemeToEngine, type DbConfig } from './shared.js'\n\n// TypeORM's modern shape: `new DataSource({ type: 'postgres', host, port, ... })`\n// in a data-source.ts (or .js). We regex for the type/host/port/database keys\n// in the first DataSource literal we find.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const filePath = await findFirst(serviceDir, [\n 'data-source.ts',\n 'data-source.js',\n 'src/data-source.ts',\n 'src/data-source.js',\n ])\n if (!filePath) return []\n const content = await readIfExists(filePath)\n if (!content) return []\n\n const block = content.match(/new\\s+DataSource\\s*\\(\\s*\\{([\\s\\S]*?)\\}\\s*\\)/)\n const body = block ? block[1]! : content\n\n const typeMatch = body.match(/type\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)\n const host = body.match(/host\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)?.[1]\n if (!typeMatch || !host) return []\n\n const engine = schemeToEngine(typeMatch[1]!)\n if (!engine) return []\n\n const port = body.match(/port\\s*:\\s*(\\d+)/)?.[1]\n const database = body.match(/database\\s*:\\s*['\"`]([^'\"`]+)['\"`]/)?.[1] ?? ''\n\n return [\n {\n host,\n port: port ? Number(port) : undefined,\n database,\n engine,\n engineVersion: 'unknown',\n sourceFile: filePath,\n },\n ]\n}\n\nexport const typeormParser = { name: 'typeorm', parse }\n","import path from 'node:path'\nimport { exists, readJson } from '../shared.js'\nimport { schemeToEngine, type DbConfig } from './shared.js'\n\ninterface SequelizeConfigEntry {\n dialect?: string\n host?: string\n port?: number\n database?: string\n}\n\ntype SequelizeConfig = Record<string, SequelizeConfigEntry>\n\n// Sequelize stores per-environment configs under config/config.json. We read\n// every named environment so a service that declares production + staging\n// surfaces both DB targets.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n const configPath = path.join(serviceDir, 'config', 'config.json')\n if (!(await exists(configPath))) return []\n const raw = await readJson<SequelizeConfig>(configPath)\n\n const out: DbConfig[] = []\n const seen = new Set<string>()\n for (const entry of Object.values(raw)) {\n if (!entry?.dialect || !entry.host) continue\n const engine = schemeToEngine(entry.dialect)\n if (!engine) continue\n const key = `${engine}://${entry.host}:${entry.port ?? ''}/${entry.database ?? ''}`\n if (seen.has(key)) continue\n seen.add(key)\n out.push({\n host: entry.host,\n port: entry.port,\n database: entry.database ?? '',\n engine,\n engineVersion: 'unknown',\n sourceFile: configPath,\n })\n }\n return out\n}\n\nexport const sequelizeParser = { name: 'sequelize', parse }\n","import path from 'node:path'\nimport { exists, readYaml } from '../shared.js'\nimport { engineFromImage, type DbConfig } from './shared.js'\n\ninterface ComposeService {\n image?: string\n ports?: (string | number)[]\n environment?: Record<string, string> | string[]\n}\n\ninterface ComposeFile {\n services?: Record<string, ComposeService>\n}\n\nfunction portFromService(svc: ComposeService): number | undefined {\n for (const raw of svc.ports ?? []) {\n const str = String(raw)\n // \"5432:5432\", \"5432\", or \"host:5432\" → take the trailing port.\n const last = str.split(':').pop()\n const n = Number(last)\n if (Number.isFinite(n) && n > 0) return n\n }\n return undefined\n}\n\nfunction databaseFromEnv(svc: ComposeService): string {\n const env = svc.environment\n const get = (key: string): string | undefined => {\n if (!env) return undefined\n if (Array.isArray(env)) {\n for (const line of env) {\n const [k, v] = line.split('=')\n if (k === key) return v\n }\n return undefined\n }\n return env[key]\n }\n return get('POSTGRES_DB') ?? get('MYSQL_DATABASE') ?? get('MONGO_INITDB_DATABASE') ?? ''\n}\n\n// Service-local docker-compose.yml — every service whose image we recognise\n// becomes a candidate DB. The compose service name doubles as the host since\n// that's how peer services on the same compose network reach it.\nexport async function parse(serviceDir: string): Promise<DbConfig[]> {\n for (const name of ['docker-compose.yml', 'docker-compose.yaml']) {\n const abs = path.join(serviceDir, name)\n if (!(await exists(abs))) continue\n const raw = await readYaml<ComposeFile>(abs)\n if (!raw?.services) return []\n\n const out: DbConfig[] = []\n for (const [serviceName, svc] of Object.entries(raw.services)) {\n if (!svc.image) continue\n const meta = engineFromImage(svc.image)\n if (!meta) continue\n out.push({\n host: serviceName,\n port: portFromService(svc),\n database: databaseFromEnv(svc),\n engine: meta.engine,\n engineVersion: meta.engineVersion,\n sourceFile: abs,\n })\n }\n return out\n }\n return []\n}\n\nexport const dockerComposeParser = { name: 'docker-compose', parse }\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { ConfigNode, GraphEdge } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n configId,\n confidenceForExtracted,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport {\n IGNORED_DIRS,\n isConfigFile,\n isPythonVenvDir,\n makeEdgeId,\n type DiscoveredService,\n} from './shared.js'\nimport { ensureFileNode, toPosix } from './calls/shared.js'\n\n// Walk a service directory and collect every config file path\n// (yaml/yml + .env-shaped). We deliberately stop at file paths here so nothing\n// in this module reads file contents — .env files routinely carry secrets\n// (ADR-016).\nexport async function walkConfigFiles(dir: string): Promise<string[]> {\n const out: string[] = []\n async function walk(current: string): Promise<void> {\n const entries = await fs.readdir(current, { withFileTypes: true })\n for (const entry of entries) {\n const full = path.join(current, entry.name)\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name)) continue\n if (await isPythonVenvDir(full)) continue\n await walk(full)\n } else if (entry.isFile() && isConfigFile(entry.name).match) {\n out.push(full)\n }\n }\n }\n await walk(dir)\n return out\n}\n\n// Phase 3 — turn each config file into a ConfigNode with a CONFIGURED_BY edge\n// from its owning service.\nexport async function addConfigNodes(\n graph: NeatGraph,\n services: DiscoveredService[],\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n for (const service of services) {\n const configFiles = await walkConfigFiles(service.dir)\n for (const file of configFiles) {\n const relPath = path.relative(scanPath, file)\n const node: ConfigNode = {\n id: configId(relPath),\n type: NodeType.ConfigNode,\n name: path.basename(file),\n path: relPath,\n fileType: isConfigFile(path.basename(file)).fileType,\n }\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n // file-awareness §1 — the config file IS the relationship origin.\n // ensureFileNode creates the FileNode + CONTAINS edge so CONFIGURED_BY\n // lands file-grained rather than service-level.\n const relToService = toPosix(path.relative(service.dir, file))\n const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relToService,\n )\n nodesAdded += fn\n edgesAdded += fe\n const edge: GraphEdge = {\n id: makeEdgeId(fileNodeId, node.id, EdgeType.CONFIGURED_BY),\n source: fileNodeId,\n target: node.id,\n type: EdgeType.CONFIGURED_BY,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: relPath.split(path.sep).join('/') },\n }\n if (!graph.hasEdge(edge.id)) {\n graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n }\n return { nodesAdded, edgesAdded }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { GraphEdge, GrpcMethodNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n grpcMethodId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport { IGNORED_DIRS, isPythonVenvDir, isTestPath, type DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { snippet, toPosix } from './calls/shared.js'\n\n// gRPC `.proto` service/method extraction (ADR-123). gRPC used to engage only at\n// service grain — the client-stub detector in calls/grpc.ts maps a\n// `new OrderServiceClient()` to one `infra:grpc-service:*` node, and nothing read\n// the method surface at all. This producer reads the `.proto` service contract\n// as DATA (a bounded line-scan, no tree-sitter grammar — polyglot files are read\n// as data, CLAUDE.md) and materialises each `rpc` as a `GrpcMethodNode`, owned by\n// the service the proto lives in through a `service ──CONTAINS──▶ method` edge.\n//\n// This is the static half of two-sided gRPC observation: the node is keyed on the\n// fully-qualified `<package>.<Service>` name — the exact `rpc.service` an OBSERVED\n// execution span carries — so a declared method and its observed counterpart fuse\n// onto one node into a method-grain divergence (docs/contracts/otel-ingest.md\n// §gRPC methods). Scope is the service/method definitions only; message/field\n// grain, `import` resolution across proto files, and error-detail enrichment are\n// out of scope for this slice.\n\nconst PROTO_EXTENSION = '.proto'\n\n// A `.proto` service/method definition parsed out of one file.\nexport interface ExtractedGrpcMethod {\n rpcService: string // fully-qualified `<package>.<Service>` (or bare `<Service>` when the file declares no package)\n rpcMethod: string // bare method name, e.g. `GetOrder`\n line: number // 1-indexed line the `rpc` is declared on\n}\n\n// ── `.proto` parsing (data, not a grammar) ──────────────────────────────────\n\n// The file-level `package foo.bar;` declaration, if present. gRPC fully-qualifies\n// `rpc.service` as `<package>.<Service>`, so the package prefixes every service\n// name in the file. proto2/proto3 both use this single-line form.\nfunction packageOf(content: string): string | null {\n const m = content.match(/^\\s*package\\s+([A-Za-z_][A-Za-z0-9_.]*)\\s*;/m)\n return m ? m[1]! : null\n}\n\n// The 1-indexed line an absolute character offset falls on.\nfunction lineAt(content: string, offset: number): number {\n return content.slice(0, offset).split('\\n').length\n}\n\n// Scan a `.proto` file's `service X { rpc M(Req) returns (Res); }` blocks and\n// emit one method per `rpc`. Brace-balanced so a service body is read to its\n// close; the `rpc` scan is confined to that body. Streaming qualifiers\n// (`rpc M(stream Req) returns (stream Res)`) don't change the method identity, so\n// they're accepted and ignored. Comment-only or option lines that merely mention\n// `rpc`/`service` don't match — both anchors require the keyword followed by an\n// identifier and the structural token (`{` for a service, `(` for an rpc).\nexport function grpcMethodsFromProto(content: string, fqPackage: string | null): ExtractedGrpcMethod[] {\n const out: ExtractedGrpcMethod[] = []\n const serviceRe = /\\bservice\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{/g\n let sm: RegExpExecArray | null\n while ((sm = serviceRe.exec(content)) !== null) {\n const serviceName = sm[1]!\n const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName\n // Walk from the opening brace to its matching close, tracking depth so a\n // nested block (an inline `option`/message) doesn't end the service early.\n const bodyStart = serviceRe.lastIndex // index just past the `{`\n let depth = 1\n let i = bodyStart\n for (; i < content.length && depth > 0; i++) {\n const ch = content[i]\n if (ch === '{') depth++\n else if (ch === '}') depth--\n }\n const body = content.slice(bodyStart, i - 1)\n const rpcRe = /\\brpc\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(/g\n let rm: RegExpExecArray | null\n while ((rm = rpcRe.exec(body)) !== null) {\n const rpcMethod = rm[1]!\n const line = lineAt(content, bodyStart + rm.index)\n out.push({ rpcService, rpcMethod, line })\n }\n // Continue the outer scan past this service body.\n serviceRe.lastIndex = i\n }\n return out\n}\n\n// ── file discovery ──────────────────────────────────────────────────────────\n\n// Walk a service directory for `.proto` files, honouring the shared ignore set\n// (node_modules, .git, …) and Python venvs the same way walkSourceFiles does.\n// `.proto` isn't a SERVICE_FILE_EXTENSION, so the source-file walker skips it —\n// this is a dedicated pass over the proto contract surface.\nasync function walkProtoFiles(dir: string): Promise<string[]> {\n const out: string[] = []\n async function walk(current: string): Promise<void> {\n const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n const full = path.join(current, entry.name)\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name)) continue\n if (await isPythonVenvDir(full)) continue\n await walk(full)\n } else if (entry.isFile() && path.extname(entry.name) === PROTO_EXTENSION) {\n out.push(full)\n }\n }\n }\n await walk(dir)\n return out\n}\n\n// ── producer ─────────────────────────────────────────────────────────────────\n\nexport async function addGrpcMethods(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const protoPaths = await walkProtoFiles(service.dir)\n for (const protoPath of protoPaths) {\n // ADR-065 #1 — test-scope exclusion. A `.proto` under a test tree isn't the\n // service's declared contract surface.\n if (isTestPath(protoPath)) continue\n const relFile = toPosix(path.relative(service.dir, protoPath))\n\n let content: string\n try {\n content = await fs.readFile(protoPath, 'utf8')\n } catch (err) {\n recordExtractionError('proto extraction', protoPath, err)\n continue\n }\n\n let methods: ExtractedGrpcMethod[]\n try {\n methods = grpcMethodsFromProto(content, packageOf(content))\n } catch (err) {\n recordExtractionError('proto extraction', protoPath, err)\n continue\n }\n if (methods.length === 0) continue\n\n for (const method of methods) {\n const mid = grpcMethodId(method.rpcService, method.rpcMethod)\n if (!graph.hasNode(mid)) {\n const node: GrpcMethodNode = {\n id: mid,\n type: NodeType.GrpcMethodNode,\n name: `${method.rpcService}/${method.rpcMethod}`,\n rpcService: method.rpcService,\n rpcMethod: method.rpcMethod,\n path: relFile,\n line: method.line,\n discoveredVia: 'static',\n }\n graph.addNode(mid, node)\n nodesAdded++\n }\n // `service ──CONTAINS──▶ method` — the service owns the methods its\n // `.proto` declares, the same structural verb it has over its routes\n // (ADR-119) and files (file-awareness.md §2). Evidence pinned to the\n // `rpc` line. The node id is the wire-canonical FQN, so an OBSERVED\n // execution span lands on this same node — declared and observed fuse.\n const containsId = extractedEdgeId(service.node.id, mid, EdgeType.CONTAINS)\n if (!graph.hasEdge(containsId)) {\n const edge: GraphEdge = {\n id: containsId,\n source: service.node.id,\n target: mid,\n type: EdgeType.CONTAINS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: relFile,\n line: method.line,\n snippet: snippet(content, method.line),\n },\n }\n graph.addEdgeWithKey(containsId, service.node.id, mid, edge)\n edgesAdded++\n }\n }\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import type { GraphEdge, InfraNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n passesExtractedFloor,\n} from '@neat.is/types'\nimport { noteExtractedDropped, recordExtractionError } from '../errors.js'\nimport type { NeatGraph } from '../../graph.js'\nimport {\n isTestPath,\n makeEdgeId,\n maskCommentsInSource,\n type DiscoveredService,\n} from '../shared.js'\nimport { addHttpCallEdges } from './http.js'\nimport { addRouteCallEdges } from './route-match.js'\nimport { ensureFileNode, loadSourceFiles, toPosix, type ExternalEndpoint, type SourceFile } from './shared.js'\nimport { kafkaEndpointsFromFile } from './kafka.js'\nimport { redisEndpointsFromFile } from './redis.js'\nimport { awsEndpointsFromFile } from './aws.js'\nimport { grpcEndpointsFromFile } from './grpc.js'\nimport { supabaseEndpointsFromFile } from './supabase.js'\nimport { firestoreEndpointsFromFile } from './firestore.js'\nimport { mongooseEndpointsFromFile, mongooseCrossFileEndpoints } from './mongoose.js'\nimport { sqlalchemyEndpointsFromFile, pythonOrmCrossFileEndpoints } from './sqlalchemy.js'\nimport { djangoOrmEndpointsFromFile } from './django-orm.js'\nimport { drizzleEndpointsFromFile } from './drizzle.js'\nimport { prismaColumnEndpoints } from './prisma.js'\nimport { railsSchemaEndpointsFromFile, railsModelEndpointsFromFile } from './activerecord.js'\nimport { laravelMigrationEndpointsFromFile, laravelModelEndpointsFromFile } from './eloquent.js'\nimport { foldColumns, foldSdkWrites } from '../../columns.js'\nimport { goSqlEndpointsFromFile } from './go.js'\nimport { gormEndpointsFromFile } from './gorm.js'\n\nexport interface CallExtractResult {\n nodesAdded: number\n edgesAdded: number\n}\n\nfunction edgeTypeFromEndpoint(ep: ExternalEndpoint): (typeof EdgeType)[keyof typeof EdgeType] {\n switch (ep.edgeType) {\n case 'PUBLISHES_TO':\n return EdgeType.PUBLISHES_TO\n case 'CONSUMES_FROM':\n return EdgeType.CONSUMES_FROM\n default:\n return EdgeType.CALLS\n }\n}\n\nfunction isAwsKind(kind: string): boolean {\n return (\n kind.startsWith('aws-') ||\n kind.startsWith('s3') ||\n kind.startsWith('dynamodb')\n )\n}\n\nasync function addExternalEndpointEdges(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<CallExtractResult> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const files = await loadSourceFiles(service.dir)\n const endpoints: ExternalEndpoint[] = []\n const maskedFiles: SourceFile[] = []\n for (const file of files) {\n // ADR-065 #1 — test-scope exclusion. Tests stay registered as\n // service-internal (via the file walk earlier); only outbound\n // endpoint inference from them is filtered.\n if (isTestPath(file.path)) continue\n // ADR-065 #2 — comment-body exclusion. The regex-based extractors\n // (redis / kafka / aws / grpc) scan raw file.content; URLs inside\n // JSDoc / line / block comments leaked through to the graph in the\n // v0.3.0 medusa run. Mask comments while preserving line/column for\n // evidence line-mapping.\n const masked = maskCommentsInSource(file.content)\n const maskedFile = { path: file.path, content: masked }\n maskedFiles.push(maskedFile)\n endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...firestoreEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...sqlalchemyEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...djangoOrmEndpointsFromFile(maskedFile, service.dir))\n endpoints.push(...drizzleEndpointsFromFile(maskedFile, service.dir))\n try {\n endpoints.push(...goSqlEndpointsFromFile(maskedFile, service.dir))\n } catch (err) {\n recordExtractionError('go SQL call extraction', file.path, err)\n }\n // Go/GORM data axis (ADR-180). Model structs → `sql-table` nodes + literal\n // columns. Parsed via tree-sitter-go from the raw file (not the JS-comment-\n // masked copy), so Go `//` comments are excluded structurally as `comment`\n // nodes. Wrapped because tree-sitter-go is a native module — a per-file\n // failure must not abort the phase (ADR-055).\n try {\n endpoints.push(...gormEndpointsFromFile(file, service.dir))\n } catch (err) {\n recordExtractionError('gorm data-axis extraction', file.path, err)\n }\n // Rails ActiveRecord data axis (ADR-174). `db/schema.rb` tables + columns\n // and the model→table link. Parsed via tree-sitter-ruby from the raw file\n // (not the JS-comment-masked copy), so Ruby `#` comments are excluded\n // structurally as `comment` nodes. Wrapped because tree-sitter-ruby is a\n // native module — a per-file failure must not abort the phase (ADR-055).\n try {\n endpoints.push(...railsSchemaEndpointsFromFile(file, service.dir))\n endpoints.push(...railsModelEndpointsFromFile(file, service.dir))\n } catch (err) {\n recordExtractionError('rails activerecord extraction', file.path, err)\n }\n // Laravel data axis (ADR-178). `database/migrations/*.php` Schema::create\n // blueprints (tables + literal columns) and the `app/Models/*.php`\n // Eloquent class→table link. Parsed via tree-sitter-php (`php_only`) from\n // the raw file — PHP `//` / `#` comments are excluded structurally as\n // `comment` nodes. Wrapped because tree-sitter-php is a native module — a\n // per-file failure must not abort the phase (ADR-055).\n try {\n endpoints.push(...laravelMigrationEndpointsFromFile(file, service.dir))\n endpoints.push(...laravelModelEndpointsFromFile(file, service.dir))\n } catch (err) {\n recordExtractionError('laravel eloquent extraction', file.path, err)\n }\n }\n // Cross-file mongoose resolution (ADR-149) — a whole-program pass over the\n // service's files, attributing a query in one file to a model defined in\n // another via the import graph.\n endpoints.push(...(await mongooseCrossFileEndpoints(maskedFiles, service.dir)))\n // Cross-file SQLAlchemy model→table query attribution (ADR-149 analog): a\n // query file gets the table edge for a model imported from another file.\n endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir))\n // Prisma declared columns (ADR-157 §3): `schema.prisma` is read as text —\n // it isn't a source file the walker loads — so this runs once per service,\n // reading each model's scalar fields as EXTRACTED columns at DB-name fidelity.\n endpoints.push(...(await prismaColumnEndpoints(service.dir)))\n if (endpoints.length === 0) continue\n\n const seenEdges = new Set<string>()\n for (const ep of endpoints) {\n if (!graph.hasNode(ep.infraId)) {\n const node: InfraNode = {\n id: ep.infraId,\n type: NodeType.InfraNode,\n name: ep.name,\n // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,\n // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the\n // bucket / table kinds from aws.ts.\n provider: isAwsKind(ep.kind) ? 'aws' : 'self',\n kind: ep.kind,\n }\n graph.addNode(node.id, node)\n nodesAdded++\n }\n\n // Column grain (ADR-157 §3): a schema-column producer (Drizzle) carries the\n // declared columns at database-name fidelity. Fold them onto the table node\n // with EXTRACTED provenance — whether the node was just minted here or\n // already exists from an OBSERVED span, so the declared and observed columns\n // fuse on one node and column-drift (§4) can read both sides. The\n // definition's file:line rides the EXTRACTED edge minted below. Mutation is\n // allowed here — extract/* is a lifecycle authority (lifecycle.md §3).\n if (ep.columns && ep.columns.length > 0) {\n const node = graph.getNodeAttributes(ep.infraId) as InfraNode\n if (node.type === NodeType.InfraNode) {\n graph.replaceNodeAttributes(ep.infraId, {\n ...node,\n columns: foldColumns(\n node.columns,\n ep.columns,\n Provenance.EXTRACTED,\n confidenceForExtracted(ep.confidenceKind),\n ),\n })\n }\n }\n\n // Firestore write-SDK tags (ADR-167). A `firestore-collection` endpoint\n // carries, per written field, which SDK wrote it (client vs admin) — the\n // seam the field-guard policy (ADR-169) joins on. Fold it onto the columns\n // the block above just landed, via the parallel `foldSdkWrites` — a separate\n // firestore-gated pass so `foldColumns` and its fold block stay untouched.\n // Gated on `ep.sdkWrites`, which only calls/firestore.ts populates.\n if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {\n const node = graph.getNodeAttributes(ep.infraId) as InfraNode\n if (node.type === NodeType.InfraNode) {\n graph.replaceNodeAttributes(ep.infraId, {\n ...node,\n columns: foldSdkWrites(node.columns, ep.sdkWrites),\n })\n }\n }\n\n const edgeType = edgeTypeFromEndpoint(ep)\n const confidence = confidenceForExtracted(ep.confidenceKind)\n // File-first (file-awareness.md §1): the endpoint relationship originates\n // from the file the call site lives in, with the owning service\n // ──CONTAINS──▶ file edge alongside it (§2). File-node existence is\n // independent of edge-target precision (ADR-089 amendment) — a matched\n // call site is a parsed fact, so the FileNode + CONTAINS materialize\n // regardless of how confident we are about the resolved target.\n const relFile = toPosix(ep.evidence.file)\n const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relFile,\n )\n nodesAdded += n\n edgesAdded += e\n // Precision floor (ADR-066 §3). Only the file→target edge is gated:\n // sub-threshold candidates are recorded as drops (banner accounting) and\n // never added to the graph; the file and its call site still surface.\n if (!passesExtractedFloor(confidence)) {\n noteExtractedDropped({\n source: fileNodeId,\n target: ep.infraId,\n type: edgeType,\n confidence,\n confidenceKind: ep.confidenceKind,\n evidence: ep.evidence,\n })\n continue\n }\n const edgeId = makeEdgeId(fileNodeId, ep.infraId, edgeType)\n if (seenEdges.has(edgeId)) continue\n seenEdges.add(edgeId)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: fileNodeId,\n target: ep.infraId,\n type: edgeType,\n provenance: Provenance.EXTRACTED,\n confidence,\n evidence: ep.evidence,\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n }\n return { nodesAdded, edgesAdded }\n}\n\nexport async function addCallEdges(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<CallExtractResult> {\n const http = await addHttpCallEdges(graph, services)\n const ext = await addExternalEndpointEdges(graph, services)\n // Cross-service contract matching (ADR-119). Runs after the RouteNodes are in\n // the graph (addRoutes, a prior phase) so client call sites can be matched\n // against the full route table, minting route-grained CALLS edges.\n const routes = await addRouteCallEdges(graph, services)\n return {\n nodesAdded: http.nodesAdded + ext.nodesAdded + routes.nodesAdded,\n edgesAdded: http.edgesAdded + ext.edgesAdded + routes.edgesAdded,\n }\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport Python from 'tree-sitter-python'\nimport type { GraphEdge } from '@neat.is/types'\nimport {\n EdgeType,\n Provenance,\n confidenceForExtracted,\n passesExtractedFloor,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport {\n isTestPath,\n makeEdgeId,\n urlMatchesHost,\n type DiscoveredService,\n} from '../shared.js'\nimport { recordExtractionError, noteExtractedDropped } from '../errors.js'\nimport {\n buildServiceHostIndex,\n ensureFileNode,\n loadSourceFiles,\n snippet,\n toPosix,\n} from './shared.js'\n\n// JS uses `string_fragment` for the textual interior of a template/string;\n// Python uses `string_content` inside a `string` node. Either way we want the\n// raw textual content (no quotes), so we accept both.\nconst STRING_LITERAL_NODE_TYPES = new Set(['string_fragment', 'string_content'])\n\n// ADR-065 #3 — JSX external-link exclusion. Tags whose URL-attr strings are\n// user-clickable hyperlinks, not service-to-service calls.\nconst JSX_EXTERNAL_LINK_TAGS = new Set(['a', 'Link', 'NavLink', 'ExternalLink', 'Anchor'])\n\n// Walk upward from a string-literal node to detect whether it sits inside a\n// JSX attribute on an external-link element. Returns true if the literal\n// should be filtered.\nfunction isInsideJsxExternalLink(node: Parser.SyntaxNode): boolean {\n let cursor: Parser.SyntaxNode | null = node.parent\n // Step out of the string wrapper if needed (parent is `string` /\n // `template_string`).\n while (cursor) {\n if (cursor.type === 'jsx_attribute') {\n // The element that owns this attribute. jsx_attribute lives inside\n // jsx_opening_element / jsx_self_closing_element.\n let owner: Parser.SyntaxNode | null = cursor.parent\n while (owner && owner.type !== 'jsx_opening_element' && owner.type !== 'jsx_self_closing_element') {\n owner = owner.parent\n }\n if (!owner) return false\n // First named child of an opening/self-closing element is the tag name\n // (`identifier` or `member_expression`).\n const tagNode = owner.namedChild(0)\n const tagName = tagNode?.text ?? ''\n // For `<Foo.Bar>` we just want the rightmost ident; pick after the\n // last dot.\n const right = tagName.includes('.') ? tagName.split('.').pop()! : tagName\n return JSX_EXTERNAL_LINK_TAGS.has(right)\n }\n cursor = cursor.parent\n }\n return false\n}\n\n// Collect (literal text, ast-node) pairs so the JSX-context check has the\n// node available. Comment tokens have no string_fragment / string_content\n// children in tree-sitter — JSDoc text lives inside `comment` nodes — so\n// comment-body exclusion comes for free with this AST walk (ADR-065 #2).\nfunction collectStringLiterals(\n node: Parser.SyntaxNode,\n out: { text: string; node: Parser.SyntaxNode }[],\n): void {\n if (STRING_LITERAL_NODE_TYPES.has(node.type)) out.push({ text: node.text, node })\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) collectStringLiterals(child, out)\n }\n}\n\n// A matched outbound call: the host the URL literal names and the 1-indexed\n// line it sits on. File-first extraction (file-awareness.md §1) originates a\n// CALLS edge from the file the call site lives in, so the position travels with\n// the host rather than being recovered after the fact.\nexport interface HttpCallSite {\n host: string\n line: number\n}\n\n// tree-sitter's node binding copies a string handed to `parser.parse` into a\n// fixed scratch buffer of ~32K code units and throws a bare \"Invalid argument\"\n// once the source is larger — it never names the size as the cause. NEAT's own\n// `cli.ts`, `ingest.ts`, and `installers/javascript.ts` all clear 40K, so the\n// http-call extractor was quietly skipping the three most interesting files in\n// the repo while dogfooding NEAT-on-NEAT. The callback form sidesteps the\n// buffer entirely: tree-sitter pulls the text in chunks we keep well under the\n// limit, so a file of any length parses. Chunking is by `.length` (UTF-16 code\n// units), which is exactly what the buffer counts.\nconst PARSE_CHUNK = 16384\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nexport function callsFromSource(\n source: string,\n parser: Parser,\n knownHosts: Set<string>,\n): HttpCallSite[] {\n const tree = parseSource(parser, source)\n const literals: { text: string; node: Parser.SyntaxNode }[] = []\n collectStringLiterals(tree.rootNode, literals)\n const out: HttpCallSite[] = []\n for (const lit of literals) {\n // ADR-065 #3 — JSX external-link exclusion. URL strings on <a>, <Link>,\n // <NavLink>, <ExternalLink>, <Anchor> are user-clickable hyperlinks, not\n // service calls.\n if (isInsideJsxExternalLink(lit.node)) continue\n for (const host of knownHosts) {\n // ADR-065 #5 — exact hostname match (not substring containment).\n // `medusa.cloud` no longer matches `@medusajs/medusa`.\n if (urlMatchesHost(lit.text, host)) {\n out.push({ host, line: lit.node.startPosition.row + 1 })\n }\n }\n }\n return out\n}\n\nfunction makeJsParser(): Parser {\n const p = new Parser()\n p.setLanguage(JavaScript)\n return p\n}\n\nfunction makePyParser(): Parser {\n const p = new Parser()\n p.setLanguage(Python)\n return p\n}\n\n// HTTP CALLS via URL hostname match. Parser is picked per file extension:\n// .py uses tree-sitter-python; everything else uses tree-sitter-javascript.\n//\n// File-first (file-awareness.md §1): each matched call site originates a\n// `file:<svc>:<relPath> ──CALLS──▶ target` edge plus the owning service's\n// CONTAINS edge, rather than collapsing every call in a service to one\n// service-level edge.\n//\n// File-node existence is independent of edge-target precision (file-awareness.md\n// §1, ADR-089 amendment). A matched call site is a parsed fact — the file and\n// its `service ──CONTAINS──▶ file` edge are certain regardless of how confident\n// we are about *what* it calls. So the FileNode + CONTAINS materialize for every\n// matched site; only the file→target CALLS edge is subject to the precision\n// floor. A scheme-qualified URL literal to a registered service grades at the\n// floor (url-literal-service-target, 0.7) so the declared HTTP dependency enters\n// the graph; if the floor is raised past it for diagnostics the file and its\n// call site still surface, without claiming the resolved target.\nexport async function addHttpCallEdges(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const jsParser = makeJsParser()\n const pyParser = makePyParser()\n\n // Host → owning ServiceNode id (ADR-065 #5), shared with the route-matching\n // producer so both resolve a URL's host to a service the same way.\n const { knownHosts, hostToNodeId } = buildServiceHostIndex(services)\n\n let nodesAdded = 0\n let edgesAdded = 0\n for (const service of services) {\n const files = await loadSourceFiles(service.dir)\n // File grain: one file→target CALLS per (file, target) pair, even when a\n // file names the same host on several lines (function-level is deferred).\n const seen = new Set<string>()\n for (const file of files) {\n // ADR-065 #1 — test-scope exclusion.\n if (isTestPath(file.path)) continue\n const parser = path.extname(file.path) === '.py' ? pyParser : jsParser\n let sites: HttpCallSite[]\n try {\n sites = callsFromSource(file.content, parser, knownHosts)\n } catch (err) {\n recordExtractionError('http call extraction', file.path, err)\n continue\n }\n if (sites.length === 0) continue\n const relFile = toPosix(path.relative(service.dir, file.path))\n for (const site of sites) {\n const targetId = hostToNodeId.get(site.host)\n if (!targetId || targetId === service.node.id) continue\n const dedupKey = `${relFile}|${targetId}`\n if (seen.has(dedupKey)) continue\n seen.add(dedupKey)\n // A scheme-qualified URL literal that resolves to a registered service\n // is a declared HTTP dependency (static-extraction contract §5). It\n // grades at the precision floor rather than below it, so the EXTRACTED\n // CALLS edge enters the graph and missing-observed can flag a declared-\n // but-never-driven upstream (issue #592). Still below structural /\n // verified-call-site: no call expression wraps the literal.\n const confidence = confidenceForExtracted('url-literal-service-target')\n const ev = {\n file: relFile,\n line: site.line,\n snippet: snippet(file.content, site.line),\n }\n // The matched call site is a parsed fact: materialize the FileNode and\n // its `service ──CONTAINS──▶ file` edge regardless of target precision\n // (file-awareness.md §1, ADR-089 amendment). The file surfaces even\n // when the resolved target sits below the floor.\n const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relFile,\n )\n nodesAdded += n\n edgesAdded += e\n // The file→target CALLS edge alone is subject to the precision floor.\n // A sub-floor target is recorded as a drop (banner accounting) and not\n // claimed as a resolved edge — the file and its call site still stand.\n if (!passesExtractedFloor(confidence)) {\n noteExtractedDropped({\n source: fileNodeId,\n target: targetId,\n type: EdgeType.CALLS,\n confidence,\n confidenceKind: 'url-literal-service-target',\n evidence: ev,\n })\n continue\n }\n const edgeId = makeEdgeId(fileNodeId, targetId, EdgeType.CALLS)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: fileNodeId,\n target: targetId,\n type: EdgeType.CALLS,\n provenance: Provenance.EXTRACTED,\n confidence,\n evidence: ev,\n }\n graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge)\n edgesAdded++\n }\n }\n }\n }\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport type { GraphEdge, RouteNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n passesExtractedFloor,\n serviceId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { isTestPath, makeEdgeId, urlMatchesHost, type DiscoveredService } from '../shared.js'\nimport { recordExtractionError, noteExtractedDropped } from '../errors.js'\nimport { normalizePathTemplate } from '../routes.js'\nimport {\n buildServiceHostIndex,\n ensureFileNode,\n loadSourceFiles,\n snippet,\n toPosix,\n} from './shared.js'\n\n// Cross-service contract matching (ADR-119). This is the bridge between the two\n// static islands: a client call site names a URL (host + method + path); a\n// server RouteNode (extracted by routes.ts) declares (method, path-template).\n// When a client call's (host→service, method, normalised path) resolves to a\n// server route, this producer mints a route-grained EXTRACTED CALLS edge from\n// the client's FileNode to the server's RouteNode. It reuses the host→service\n// resolution the HTTP producers share (buildServiceHostIndex / urlMatchesHost),\n// adding path-template matching for the route half.\n//\n// The edge pairs with the OBSERVED server-span edge landing on the same\n// RouteNode (#576), giving get_divergences a file-precise, two-sided comparison\n// at route grain instead of only at service grain.\n//\n// Mainstream clients only: `fetch`, `axios` (default instance + method calls),\n// and node `http`/`https` `.request`/`.get`. The host and path must sit in the\n// same URL literal (or template literal) for a match — split base-URL + path\n// across variables is out of scope for this slice.\n\nconst PARSE_CHUNK = 16384\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nfunction makeJsParser(): Parser {\n const p = new Parser()\n p.setLanguage(JavaScript)\n return p\n}\n\nconst JS_CLIENT_EXTENSIONS = new Set(['.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx'])\nconst AXIOS_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'request'])\n\nexport interface ClientCallSite {\n host: string // the known host the URL literal named (basename or pkg name)\n method?: string // upper-cased; undefined when not statically determinable\n pathTemplate: string // the URL path, with `:param` for interpolations\n line: number\n snippet: string\n}\n\n// ── AST helpers ─────────────────────────────────────────────────────────────\n\nfunction walk(node: Parser.SyntaxNode, visit: (n: Parser.SyntaxNode) => void): void {\n visit(node)\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) walk(child, visit)\n }\n}\n\n// Reconstruct the URL text a string / template-string argument names. A template\n// substitution (`${id}`) becomes the literal `:param` so the reconstructed URL\n// is a valid string with a param-shaped path segment: `/users/${id}` →\n// `/users/:param`. Returns null for anything that isn't a string-ish literal.\nfunction reconstructUrl(node: Parser.SyntaxNode): string | null {\n if (node.type === 'string') {\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child?.type === 'string_fragment') return child.text\n }\n return ''\n }\n if (node.type === 'template_string') {\n let out = ''\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (!child) continue\n if (child.type === 'string_fragment') out += child.text\n else if (child.type === 'template_substitution') out += ':param'\n }\n // A template with no fragments/subs (empty) — fall back to stripped text.\n if (out.length === 0) {\n const raw = node.text\n return raw.length >= 2 ? raw.slice(1, -1) : ''\n }\n return out\n }\n return null\n}\n\n// Read the `method` string off an options / config object (`{ method: 'POST' }`).\nfunction methodFromOptions(objNode: Parser.SyntaxNode): string | undefined {\n for (let i = 0; i < objNode.namedChildCount; i++) {\n const pair = objNode.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n const kText = k ? (k.type === 'string' ? stringText(k) : k.text) : null\n if (kText !== 'method') continue\n const v = pair.childForFieldName('value')\n if (v && (v.type === 'string' || v.type === 'template_string')) {\n const s = stringText(v)\n return s ? s.toUpperCase() : undefined\n }\n }\n return undefined\n}\n\nfunction stringText(node: Parser.SyntaxNode): string | null {\n if (node.type === 'string') {\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child?.type === 'string_fragment') return child.text\n }\n return ''\n }\n return null\n}\n\n// The URL string a config object names (`axios({ url: '…', method })`).\nfunction urlNodeFromConfig(objNode: Parser.SyntaxNode): Parser.SyntaxNode | null {\n for (let i = 0; i < objNode.namedChildCount; i++) {\n const pair = objNode.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n const kText = k ? (k.type === 'string' ? stringText(k) : k.text) : null\n if (kText === 'url') return pair.childForFieldName('value')\n }\n return null\n}\n\n// Parse the path out of a reconstructed URL string. Returns null when the string\n// isn't URL-shaped (no scheme + host). `:param` in the path survives parsing.\nfunction pathOf(urlStr: string): string | null {\n try {\n const candidate = urlStr.startsWith('//') ? `http:${urlStr}` : urlStr\n const parsed = new URL(candidate)\n return parsed.pathname || '/'\n } catch {\n return null\n }\n}\n\n// Resolve which known host a reconstructed URL names (ADR-065 #5 — scheme +\n// exact hostname). Returns the host token or null.\nfunction matchHost(urlStr: string, knownHosts: Set<string>): string | null {\n for (const host of knownHosts) {\n if (urlMatchesHost(urlStr, host)) return host\n }\n return null\n}\n\n// ── client call-site recognition ────────────────────────────────────────────\n\n// Extract every recognised HTTP client call site whose URL literal names a\n// known host. Each site carries the method (when statically determinable) and\n// the path-template, ready to be matched against the server route table.\nexport function clientCallSitesFromSource(\n source: string,\n parser: Parser,\n knownHosts: Set<string>,\n): ClientCallSite[] {\n const tree = parseSource(parser, source)\n const out: ClientCallSite[] = []\n\n const push = (\n urlNode: Parser.SyntaxNode,\n method: string | undefined,\n callNode: Parser.SyntaxNode,\n ): void => {\n const urlStr = reconstructUrl(urlNode)\n if (!urlStr) return\n const host = matchHost(urlStr, knownHosts)\n if (!host) return\n const p = pathOf(urlStr)\n if (p === null) return\n const line = callNode.startPosition.row + 1\n out.push({\n host,\n method,\n pathTemplate: p,\n line,\n snippet: snippet(source, line),\n })\n }\n\n walk(tree.rootNode, (node) => {\n if (node.type !== 'call_expression') return\n const fn = node.childForFieldName('function')\n if (!fn) return\n const args = node.childForFieldName('arguments')\n const first = args?.namedChild(0)\n if (!first) return\n\n // fetch(url, opts?) — global or member (globalThis.fetch).\n const fnName =\n fn.type === 'identifier'\n ? fn.text\n : fn.type === 'member_expression'\n ? (fn.childForFieldName('property')?.text ?? '')\n : ''\n\n if (fn.type === 'identifier' && fnName === 'fetch') {\n const opts = args?.namedChild(1)\n const method = opts && opts.type === 'object' ? (methodFromOptions(opts) ?? 'GET') : 'GET'\n push(first, method, node)\n return\n }\n\n // axios(url | config) — default instance called directly.\n if (fn.type === 'identifier' && fnName === 'axios') {\n if (first.type === 'object') {\n const urlNode = urlNodeFromConfig(first)\n if (urlNode) push(urlNode, methodFromOptions(first) ?? 'GET', node)\n } else {\n const opts = args?.namedChild(1)\n const method = opts && opts.type === 'object' ? (methodFromOptions(opts) ?? 'GET') : 'GET'\n push(first, method, node)\n }\n return\n }\n\n if (fn.type === 'member_expression') {\n const obj = fn.childForFieldName('object')\n const objName = obj?.text ?? ''\n\n // axios.get('/x') / axios.post('/x', body) / axios.request({ url, method }).\n if (objName === 'axios' && AXIOS_METHODS.has(fnName)) {\n if (fnName === 'request' && first.type === 'object') {\n const urlNode = urlNodeFromConfig(first)\n if (urlNode) push(urlNode, methodFromOptions(first) ?? 'GET', node)\n } else {\n push(first, fnName.toUpperCase(), node)\n }\n return\n }\n\n // node http/https .request(url, …) / .get(url, …).\n if ((objName === 'http' || objName === 'https') && (fnName === 'request' || fnName === 'get')) {\n const opts = args?.namedChild(1)\n const method =\n opts && opts.type === 'object'\n ? (methodFromOptions(opts) ?? (fnName === 'get' ? 'GET' : 'GET'))\n : 'GET'\n push(first, method, node)\n return\n }\n }\n })\n\n return out\n}\n\n// ── route index + matching ──────────────────────────────────────────────────\n\ninterface RouteEntry {\n method: string // upper, or 'ALL'\n normalizedPath: string\n routeNodeId: string\n}\n\n// Group every RouteNode in the graph by its owning ServiceNode id, keyed for\n// (method, normalised-path) lookup. Built once per pass so client matching is a\n// map read, not a graph scan per call site.\nfunction buildRouteIndex(graph: NeatGraph): Map<string, RouteEntry[]> {\n const index = new Map<string, RouteEntry[]>()\n graph.forEachNode((_id, attrs) => {\n const node = attrs as unknown as { type?: string }\n if (node.type !== NodeType.RouteNode) return\n const route = attrs as unknown as RouteNode\n const owner = serviceId(route.service)\n const entry: RouteEntry = {\n method: route.method.toUpperCase(),\n normalizedPath: normalizePathTemplate(route.pathTemplate),\n routeNodeId: route.id,\n }\n const list = index.get(owner)\n if (list) list.push(entry)\n else index.set(owner, [entry])\n })\n return index\n}\n\n// A client call matches a route when the normalised paths agree and the methods\n// are compatible: exact, or the route is method-agnostic (`ALL`), or the client\n// method couldn't be read statically.\nfunction findRoute(\n entries: RouteEntry[],\n method: string | undefined,\n normalizedPath: string,\n): RouteEntry | undefined {\n return entries.find(\n (e) =>\n e.normalizedPath === normalizedPath &&\n (e.method === 'ALL' || method === undefined || e.method === method),\n )\n}\n\nexport async function addRouteCallEdges(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n const jsParser = makeJsParser()\n const { knownHosts, hostToNodeId } = buildServiceHostIndex(services)\n const routeIndex = buildRouteIndex(graph)\n if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 }\n\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const files = await loadSourceFiles(service.dir)\n // One edge per (client file, route) pair even if a file calls the route on\n // several lines (function grain is deferred, matching http.ts).\n const seen = new Set<string>()\n for (const file of files) {\n // ADR-065 #1 — test-scope exclusion.\n if (isTestPath(file.path)) continue\n if (!JS_CLIENT_EXTENSIONS.has(path.extname(file.path))) continue\n\n let sites: ClientCallSite[]\n try {\n sites = clientCallSitesFromSource(file.content, jsParser, knownHosts)\n } catch (err) {\n recordExtractionError('route-match call extraction', file.path, err)\n continue\n }\n if (sites.length === 0) continue\n\n const relFile = toPosix(path.relative(service.dir, file.path))\n for (const site of sites) {\n const serverServiceId = hostToNodeId.get(site.host)\n // Skip an unresolved host or a self-call (intra-service — no\n // cross-service contract to match, mirroring http.ts).\n if (!serverServiceId || serverServiceId === service.node.id) continue\n const entries = routeIndex.get(serverServiceId)\n if (!entries) continue\n const normalizedPath = normalizePathTemplate(site.pathTemplate)\n const match = findRoute(entries, site.method, normalizedPath)\n if (!match) continue\n\n const dedupKey = `${relFile}|${match.routeNodeId}`\n if (seen.has(dedupKey)) continue\n seen.add(dedupKey)\n\n // The matched call site is a parsed fact — the client FileNode and its\n // service ──CONTAINS──▶ file edge materialise regardless (file-awareness\n // §1). Only the file→route edge is gated by the precision floor.\n const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relFile,\n )\n nodesAdded += n\n edgesAdded += e\n\n // A matched client↔route contract grades at verified-call-site (0.85):\n // both endpoints are recognised — a framework-aware client shape and a\n // parsed route definition — so it clears the floor and enters the graph\n // (ADR-119).\n const confidence = confidenceForExtracted('verified-call-site')\n const ev = {\n file: relFile,\n line: site.line,\n snippet: site.snippet,\n method: site.method ?? match.method,\n pathTemplate: site.pathTemplate,\n }\n if (!passesExtractedFloor(confidence)) {\n noteExtractedDropped({\n source: fileNodeId,\n target: match.routeNodeId,\n type: EdgeType.CALLS,\n confidence,\n confidenceKind: 'verified-call-site',\n evidence: ev,\n })\n continue\n }\n const edgeId = makeEdgeId(fileNodeId, match.routeNodeId, EdgeType.CALLS)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: fileNodeId,\n target: match.routeNodeId,\n type: EdgeType.CALLS,\n provenance: Provenance.EXTRACTED,\n confidence,\n evidence: ev,\n }\n graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge)\n edgesAdded++\n }\n }\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { lineOf, snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// Match `producer.send({ topic: \"orders\" ... })` and `producer.send({\n// topic: 'orders' })` plus the two-arg form `producer.send(\"orders\", ...)`.\n// Kafka client libraries vary; these two forms cover kafkajs + node-rdkafka\n// well enough for static extraction. Same shape covers consumer.subscribe.\nconst PRODUCER_TOPIC_RE =\n /(?:producer|kafkaProducer)[\\s\\S]{0,40}?\\.send\\s*\\(\\s*\\{[\\s\\S]{0,200}?topic\\s*:\\s*['\"`]([^'\"`]+)['\"`]/g\nconst CONSUMER_TOPIC_RE =\n /(?:consumer|kafkaConsumer)[\\s\\S]{0,40}?\\.(?:subscribe|run)\\s*\\(\\s*\\{[\\s\\S]{0,200}?topic[s]?\\s*:\\s*(?:\\[\\s*)?['\"`]([^'\"`]+)['\"`]/g\n\nfunction findAll(re: RegExp, text: string): { topic: string; index: number }[] {\n re.lastIndex = 0\n const out: { topic: string; index: number }[] = []\n let m: RegExpExecArray | null\n while ((m = re.exec(text)) !== null) {\n out.push({ topic: m[1]!, index: m.index })\n }\n return out\n}\n\nexport function kafkaEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n const make = (topic: string, edgeType: 'PUBLISHES_TO' | 'CONSUMES_FROM'): void => {\n const key = `${edgeType}|${topic}`\n if (seen.has(key)) return\n seen.add(key)\n const line = lineOf(file.content, topic)\n out.push({\n infraId: infraId('kafka-topic', topic),\n name: topic,\n kind: 'kafka-topic',\n edgeType,\n // `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —\n // framework-aware (kafkajs / node-rdkafka shape). Verified-call-site\n // tier (ADR-066).\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n\n for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, 'PUBLISHES_TO')\n for (const { topic } of findAll(CONSUMER_TOPIC_RE, file.content)) make(topic, 'CONSUMES_FROM')\n return out\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { lineOf, snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// Redis URLs in source — `redis://host[:port]` or `rediss://...`. We only\n// catch literal strings; env-driven URLs go through the database parsers\n// (.env, ormconfig, etc.) and don't need a CALLS edge.\nconst REDIS_URL_RE = /redis(?:s)?:\\/\\/(?:[^@'\"`\\s]+@)?([^:/'\"`\\s]+)(?::(\\d+))?/g\n\nexport function redisEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n REDIS_URL_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = REDIS_URL_RE.exec(file.content)) !== null) {\n const host = m[1]!\n if (seen.has(host)) continue\n seen.add(host)\n const line = lineOf(file.content, host)\n out.push({\n infraId: infraId('redis', host),\n name: host,\n kind: 'redis',\n edgeType: 'CALLS',\n // `redis://host` URL literal — the scheme is structural support, but no\n // call expression is verified to wire it through. URL-with-structural-\n // support tier (ADR-066).\n confidenceKind: 'url-with-structural-support',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n return out\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { lineOf, snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// AWS SDK v3 calls. We catch S3 (`Bucket: \"x\"` near a `S3Client`-using\n// PutObjectCommand / GetObjectCommand / DeleteObjectCommand) and DynamoDB\n// (`TableName: \"x\"` near GetCommand / PutCommand / DynamoDBClient). The\n// pattern is intentionally permissive: a literal Bucket/TableName near an\n// SDK constant is good enough evidence; misses are fine because non-static\n// resources can't be catalogued anyway.\nconst S3_BUCKET_RE = /Bucket\\s*:\\s*['\"`]([^'\"`]+)['\"`]/g\nconst DYNAMO_TABLE_RE = /TableName\\s*:\\s*['\"`]([^'\"`]+)['\"`]/g\n\nfunction hasMarker(text: string, markers: string[]): boolean {\n return markers.some((m) => text.includes(m))\n}\n\nfunction findAll(re: RegExp, text: string): { name: string; index: number }[] {\n re.lastIndex = 0\n const out: { name: string; index: number }[] = []\n let m: RegExpExecArray | null\n while ((m = re.exec(text)) !== null) {\n out.push({ name: m[1]!, index: m.index })\n }\n return out\n}\n\nexport function awsEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n const make = (kind: string, name: string): void => {\n const key = `${kind}|${name}`\n if (seen.has(key)) return\n seen.add(key)\n const line = lineOf(file.content, name)\n out.push({\n infraId: infraId(kind, name),\n name,\n kind,\n edgeType: 'CALLS',\n // SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName\n // literal — framework-aware recognizer, verified-call-site tier\n // (ADR-066).\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n\n if (hasMarker(file.content, ['S3Client', 'PutObjectCommand', 'GetObjectCommand', 'DeleteObjectCommand'])) {\n for (const { name } of findAll(S3_BUCKET_RE, file.content)) make('s3-bucket', name)\n }\n if (\n hasMarker(file.content, [\n 'DynamoDBClient',\n 'DynamoDBDocumentClient',\n 'GetCommand',\n 'PutCommand',\n 'QueryCommand',\n 'UpdateCommand',\n 'DeleteCommand',\n ])\n ) {\n for (const { name } of findAll(DYNAMO_TABLE_RE, file.content)) make('dynamodb-table', name)\n }\n return out\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { lineOf, snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// Client-construction in JS/TS:\n//\n// const client = new orders_proto.OrderService(...)\n// const client = new OrdersClient('orders.internal:50051', ...)\n// const client = new S3Client({ region: 'us-east-1' })\n//\n// The same `new <Name>Client(...)` shape is used by gRPC client stubs, AWS\n// SDK v3 service clients, and a long tail of other SDKs. v0.3.0 mapped every\n// `*Client(...)` to `infra:grpc-service:*`, which was true for the demo and\n// false for every AWS service medusa imported.\n//\n// Per ADR-065 / #238, classification is import-aware:\n// 1. file imports `@aws-sdk/client-<suffix>` and `<Name>` lowercases to the\n// same alphanumeric tail (`S3Client` ↔ `client-s3`,\n// `CognitoIdentityProviderClient` ↔ `client-cognito-identity-provider`)\n// → kind `aws-<suffix>` (e.g. `infra:aws-s3:S3`).\n// 2. file imports `@grpc/grpc-js` or any `*_grpc_pb` generated stub\n// → kind `grpc-service` (the legitimate gRPC path; demo unchanged).\n// 3. otherwise → kind `service` (the safe default — accurate but\n// uninformative, the v0.3.0 grpc lie is removed).\nconst GRPC_CLIENT_RE = /new\\s+([A-Z][A-Za-z0-9_]*)Client\\s*\\(\\s*['\"`]?([^,'\"`)]+)?/g\nconst AWS_SDK_IMPORT_RE =\n /(?:from\\s+['\"`]|require\\(\\s*['\"`])@aws-sdk\\/client-([a-z0-9-]+)['\"`]/g\nconst GRPC_IMPORT_RE =\n /(?:from\\s+['\"`]|require\\(\\s*['\"`])@grpc\\/grpc-js['\"`]|_grpc_pb['\"`]/\n\nfunction isLikelyAddress(value: string | undefined): boolean {\n if (!value) return false\n // Reject code expressions: object literals, anything with whitespace.\n // process.env.X contains dots and would otherwise pass the dot check.\n if (/\\s/.test(value) || value.startsWith('{')) return false\n return /:\\d{2,5}$/.test(value) || value.includes('.')\n}\n\nfunction normaliseForMatch(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]/g, '')\n}\n\ninterface ImportContext {\n awsSdkSuffixes: Map<string, string> // normalised → raw suffix (e.g. 's3' → 's3')\n hasGrpcImport: boolean\n}\n\nfunction readImports(content: string): ImportContext {\n const awsSdkSuffixes = new Map<string, string>()\n AWS_SDK_IMPORT_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = AWS_SDK_IMPORT_RE.exec(content)) !== null) {\n const raw = m[1]!\n awsSdkSuffixes.set(normaliseForMatch(raw), raw)\n }\n return {\n awsSdkSuffixes,\n hasGrpcImport: GRPC_IMPORT_RE.test(content),\n }\n}\n\nfunction classifyClient(\n symbol: string,\n ctx: ImportContext,\n): { kind: string } | null {\n const key = normaliseForMatch(symbol)\n const awsRaw = ctx.awsSdkSuffixes.get(key)\n if (awsRaw) return { kind: `aws-${awsRaw}` }\n if (ctx.hasGrpcImport) return { kind: 'grpc-service' }\n // ADR-065 #5-adjacent — a bare `new <Name>Client()` with no AWS or gRPC\n // import context is ambiguous; could be an in-process client object\n // (`new QueryClient()` from @tanstack/react-query, `new PrismaClient()`,\n // a domain Client class, etc.). Refuse to emit an edge rather than guess\n // a `service` kind that produces false positives. v0.3.3 medusa run had\n // a QueryClient false positive before this guard.\n return null\n}\n\nexport function grpcEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n const ctx = readImports(file.content)\n GRPC_CLIENT_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = GRPC_CLIENT_RE.exec(file.content)) !== null) {\n const symbol = m[1]!\n const addr = m[2]?.trim()\n const name = isLikelyAddress(addr) ? addr! : symbol\n if (seen.has(name)) continue\n const classified = classifyClient(symbol, ctx)\n if (!classified) continue\n seen.add(name)\n const { kind } = classified\n const line = lineOf(file.content, m[0])\n out.push({\n infraId: infraId(kind, name),\n name,\n kind,\n edgeType: 'CALLS',\n // `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import\n // context — import-aware classification per #238. Verified-call-site\n // tier (ADR-066).\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n return out\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { lineOf, snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// Supabase clients in JS/TS:\n//\n// import { createClient } from '@supabase/supabase-js'\n// const supabase = createClient(process.env.SUPABASE_URL, key)\n// const db = createClient('https://abc.supabase.co', key)\n// supabase.from('orders').select()\n// db.auth.getUser()\n//\n// import { createServerClient } from '@supabase/ssr'\n// const supabase = createServerClient(url, key, { cookies })\n//\n// Supabase apps talk to *.supabase.co exclusively through these client\n// constructors. The HTTP-call extractor never sees the host: it lives behind\n// `createClient`, and in the common case the URL is `process.env.SUPABASE_URL`\n// (or `EXPO_PUBLIC_SUPABASE_URL`), not a literal. So a production-observed\n// `service → *.supabase.co` CALLS edge arrives with no declared twin and shows\n// up as a false `missing-extracted` divergence. This extractor closes that gap.\n//\n// Classification is import-aware, the same discipline grpc.ts / aws.ts use\n// (#238): a bare `createClient(...)` is too common a name to claim without the\n// `@supabase/*` import in scope. The constructor names map to the package that\n// owns them:\n// - `createClient` ← @supabase/supabase-js\n// - `createServerClient` / `createBrowserClient` ← @supabase/ssr\nconst SUPABASE_JS_IMPORT_RE =\n /(?:from\\s+['\"`]|require\\(\\s*['\"`])@supabase\\/supabase-js['\"`]/\nconst SUPABASE_SSR_IMPORT_RE =\n /(?:from\\s+['\"`]|require\\(\\s*['\"`])@supabase\\/ssr['\"`]/\n\n// First argument of a Supabase client constructor. Group 1 is the constructor\n// name, group 2 (when present) is a string-literal URL. A non-literal first arg\n// (`process.env.SUPABASE_URL`, a config getter) leaves group 2 undefined — we\n// still match the construction, we just can't resolve the literal host.\nconst SUPABASE_CLIENT_RE =\n /\\b(createClient|createServerClient|createBrowserClient)\\s*\\(\\s*(?:['\"`]([^'\"`]*)['\"`])?/g\n\n// A *.supabase.co host pulled out of a literal URL argument. We only treat the\n// constructor's URL as a resolvable host when the literal actually names a\n// supabase.co domain — anything else (a self-hosted Supabase behind a custom\n// domain, a placeholder) stays the unresolved env target rather than a guessed\n// host. Reading the literal is the only honest host source (file-awareness §6).\nfunction hostFromLiteral(literal: string | undefined): string | null {\n if (!literal) return null\n const m = /^https?:\\/\\/([^/:'\"`\\s]+)/.exec(literal.trim())\n if (!m) return null\n const host = m[1]!\n if (!/\\.supabase\\.(co|in)$/i.test(host)) return null\n return host\n}\n\ninterface ImportContext {\n hasSupabaseJs: boolean\n hasSupabaseSsr: boolean\n}\n\nfunction readImports(content: string): ImportContext {\n return {\n hasSupabaseJs: SUPABASE_JS_IMPORT_RE.test(content),\n hasSupabaseSsr: SUPABASE_SSR_IMPORT_RE.test(content),\n }\n}\n\n// A constructor name is only a Supabase client when the package that owns it is\n// imported in the same file. `createClient` needs @supabase/supabase-js;\n// `createServerClient` / `createBrowserClient` need @supabase/ssr. Without the\n// import in scope we refuse to emit — a bare `createClient` could be anything.\nfunction constructorMatchesImport(name: string, ctx: ImportContext): boolean {\n if (name === 'createClient') return ctx.hasSupabaseJs\n return ctx.hasSupabaseSsr\n}\n\n// Variables assigned from a Supabase client constructor in this file, e.g.\n// `const supabase = createClient(...)`. We scope `.from()` / `.rpc()` matching to\n// these so `supabase.from('orders')` (a table query) is recognized while\n// `Array.from(...)` or an unrelated `.from` never mints a phantom table node.\nconst SUPABASE_CLIENT_ASSIGN_RE =\n /(?:const|let|var)\\s+(\\w+)\\s*=\\s*(?:await\\s+)?(createClient|createServerClient|createBrowserClient)\\s*\\(/g\n\nfunction supabaseClientVars(content: string, ctx: ImportContext): Set<string> {\n const vars = new Set<string>()\n SUPABASE_CLIENT_ASSIGN_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = SUPABASE_CLIENT_ASSIGN_RE.exec(content)) !== null) {\n if (constructorMatchesImport(m[2]!, ctx)) vars.add(m[1]!)\n }\n return vars\n}\n\nexport function supabaseEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const ctx = readImports(file.content)\n if (!ctx.hasSupabaseJs && !ctx.hasSupabaseSsr) return []\n\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n SUPABASE_CLIENT_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = SUPABASE_CLIENT_RE.exec(file.content)) !== null) {\n const ctor = m[1]!\n if (!constructorMatchesImport(ctor, ctx)) continue\n\n // When the first argument is a literal *.supabase.co URL we resolve the\n // real host. Otherwise the URL is env-driven (`process.env.SUPABASE_URL`,\n // a config getter) and unknowable statically — the edge still has to land,\n // so it resolves to a stable `supabase:env` target. That target is honest:\n // we read the @supabase import and the `createClient` call, but not a host,\n // so we don't fabricate one (file-awareness §6).\n const host = hostFromLiteral(m[2])\n const name = host ?? 'env'\n if (seen.has(name)) continue\n seen.add(name)\n\n const line = lineOf(file.content, m[0])\n out.push({\n infraId: infraId('supabase', name),\n name,\n kind: 'supabase',\n edgeType: 'CALLS',\n // `createClient(...)` from @supabase/supabase-js (or createServerClient /\n // createBrowserClient from @supabase/ssr) with the import in scope — a\n // framework-aware recognizer matched the SDK shape. Verified-call-site\n // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n\n // `<client>.from('<table>')` / `<client>.rpc('<fn>')` — table/RPC-grained call\n // sites (#803). These give a production-observed supabase-table access a\n // file-grained static twin to fuse onto (the Supabase connector otherwise\n // observes the table but not the calling file). Scoped to the client vars found\n // above so `Array.from` and unrelated `.from` never mint a phantom table.\n const clientVars = supabaseClientVars(file.content, ctx)\n for (const clientVar of clientVars) {\n const accessRe = new RegExp(\n `\\\\b${clientVar}\\\\s*\\\\.\\\\s*(from|rpc)\\\\s*\\\\(\\\\s*['\"\\`]([\\\\w.-]+)['\"\\`]`,\n 'g',\n )\n let am: RegExpExecArray | null\n while ((am = accessRe.exec(file.content)) !== null) {\n const kind = am[1] === 'rpc' ? 'supabase-rpc' : 'supabase-table'\n const resource = am[2]!\n const key = `${kind}/${resource}`\n if (seen.has(key)) continue\n seen.add(key)\n const line = lineOf(file.content, am[0])\n out.push({\n infraId: infraId(kind, resource),\n name: resource,\n kind,\n edgeType: 'CALLS',\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n }\n\n return out\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport { infraId } from '@neat.is/types'\nimport { GRAMMAR_BY_EXT, parseSource } from '../symbols.js'\nimport { snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// Firestore call sites (ADR-167). The collection-grained analog of\n// calls/supabase.ts (`.from('table')`) and calls/mongoose.ts (`.collection(...)`):\n// it names the collection a file reads or writes so a Firebase/Next.js app's\n// datastore surface — collections, and the fields the code writes — becomes\n// first-class in the graph at the same grain as `sql-table` + `ColumnAttr`.\n//\n// // modular (firebase/firestore)\n// import { getFirestore, collection, doc, addDoc, setDoc, query, where } from 'firebase/firestore'\n// const db = getFirestore(app)\n// collection(db, 'orders') // → collection 'orders'\n// doc(db, 'orders', id) // → collection 'orders'\n// collection(doc(db, 'users', id), 'posts') // → 'users/{}/posts'\n// addDoc(collection(db, 'orders'), { total, status }) // → written fields\n// query(collection(db, 'orders'), where('status', '==', s)) // → read field\n//\n// // namespaced / admin (firebase-admin/firestore)\n// import admin from 'firebase-admin'\n// const db = admin.firestore()\n// db.collection('orders').doc(id).set({ total }) // → collection + written field\n// db.collection('orders').where('status', '==', s) // → read field\n//\n// Firestore has no least-privilege telemetry path (ADR-128 makes its runtime an\n// explicit connector non-goal), so its value is EXTRACTED-only. The load-bearing\n// distinction for the field-guard policy (ADR-169) is whether a write comes from\n// the client SDK (`firebase/firestore`, governed by security rules) or the admin\n// SDK (`firebase-admin/firestore`, which bypasses rules entirely) — recorded per\n// written field as `sdkWrites`.\n//\n// Classification is import-aware, the same discipline calls/supabase.ts uses: the\n// producer is gated on a firebase import, and `collection(` / `.collection(` are\n// only claimed when scoped to a recognized Firestore client var (or an inline\n// client factory) — a bare `collection(...)` could be Mongoose's native driver.\n// A computed or interpolated path segment is left unclaimed rather than guessed.\n\nconst FIRESTORE_CLIENT_IMPORT_RE =\n /(?:from\\s+['\"`]|require\\(\\s*['\"`])firebase\\/firestore['\"`]/\nconst FIRESTORE_ADMIN_IMPORT_RE =\n /(?:from\\s+['\"`]|require\\(\\s*['\"`])firebase-admin(?:\\/firestore)?['\"`]/\n\ntype SdkWrite = 'client' | 'admin'\n\nfunction parserForExt(ext: string): Parser {\n const p = new Parser()\n p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript)\n return p\n}\n\nfunction namedChildren(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c) out.push(c)\n }\n return out\n}\n\n// The literal text inside a `string` node (quotes stripped). Null for a template /\n// interpolated string — never guessed at (identical to drizzle.ts).\nfunction stringLiteralText(node: Parser.SyntaxNode | null): string | null {\n if (!node || node.type !== 'string') return null\n for (const child of namedChildren(node)) {\n if (child.type === 'string_fragment') return child.text\n }\n return '' // an empty '' literal has no string_fragment child\n}\n\nfunction callArgs(call: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const args = call.childForFieldName('arguments')\n return args ? namedChildren(args) : []\n}\n\n// A `getFirestore(...)` / `initializeFirestore(...)` call, or an `X.firestore()`\n// namespaced/admin client — the factory shapes a Firestore client comes from.\nfunction isFirestoreClientFactory(node: Parser.SyntaxNode | null): boolean {\n if (node?.type !== 'call_expression') return false\n const fn = node.childForFieldName('function')\n if (fn?.type === 'identifier') return fn.text === 'getFirestore' || fn.text === 'initializeFirestore'\n if (fn?.type === 'member_expression') return fn.childForFieldName('property')?.text === 'firestore'\n return false\n}\n\n// Variables assigned from a Firestore client factory in this file, e.g.\n// `const db = getFirestore(app)` / `const db = admin.firestore()`. We scope the\n// `collection(db, …)` and `db.collection(…)` matching to these so an unrelated\n// `.collection` (Mongoose's native driver, an array helper) never mints a phantom\n// collection — the same scoping calls/supabase.ts applies to `.from()`.\nfunction firestoreClientVars(root: Parser.SyntaxNode): Set<string> {\n const vars = new Set<string>()\n const walk = (node: Parser.SyntaxNode): void => {\n if (node.type === 'variable_declarator') {\n const name = node.childForFieldName('name')\n let value = node.childForFieldName('value')\n if (value?.type === 'await_expression') value = namedChildren(value)[0] ?? null\n if (name?.type === 'identifier' && isFirestoreClientFactory(value)) {\n vars.add(name.text)\n }\n }\n for (const c of namedChildren(node)) walk(c)\n }\n walk(root)\n return vars\n}\n\nfunction isClientExpr(node: Parser.SyntaxNode | null, clientVars: Set<string>): boolean {\n if (!node) return false\n if (node.type === 'identifier') return clientVars.has(node.text)\n return isFirestoreClientFactory(node)\n}\n\n// Resolve a modular `collection(...)` / `doc(...)` call to its ordered Firestore\n// path segments, where a collection name is its literal string and a document id\n// collapses to '{}' (the path-template placeholder — the collection template is\n// the same whatever the doc id). Returns null when the base is not a recognized\n// client or any collection-position segment is computed/interpolated: left\n// unclaimed, never guessed. Segments alternate collection(0)/doc(1)/collection(2)…\nfunction resolveModular(node: Parser.SyntaxNode, clientVars: Set<string>): string[] | null {\n const fn = node.childForFieldName('function')\n if (fn?.type !== 'identifier' || (fn.text !== 'collection' && fn.text !== 'doc')) return null\n const args = callArgs(node)\n const first = args[0]\n if (!first) return null\n\n let segs: string[]\n if (first.type === 'call_expression') {\n const base = resolveModular(first, clientVars)\n if (base === null) return null\n segs = [...base]\n } else if (isClientExpr(first, clientVars)) {\n segs = []\n } else {\n return null // first arg is not a firestore client — not our call\n }\n\n for (let i = 1; i < args.length; i++) {\n const isCollectionPos = segs.length % 2 === 0\n if (isCollectionPos) {\n const lit = stringLiteralText(args[i]!)\n if (lit === null) return null // computed collection name → unclaimed\n segs.push(lit)\n } else {\n segs.push('{}') // a document id — literal or computed alike → placeholder\n }\n }\n return segs\n}\n\n// Resolve a namespaced `<client>.collection('x')` / `.doc(id)` chain to its\n// ordered path segments, the same segment model as the modular resolver.\nfunction resolveNamespaced(node: Parser.SyntaxNode, clientVars: Set<string>): string[] | null {\n if (node.type !== 'call_expression') return null\n const fn = node.childForFieldName('function')\n if (fn?.type !== 'member_expression') return null\n const prop = fn.childForFieldName('property')?.text\n if (prop !== 'collection' && prop !== 'doc') return null\n const obj = fn.childForFieldName('object')\n\n let base: string[]\n if (obj?.type === 'call_expression') {\n const inner = resolveNamespaced(obj, clientVars)\n if (inner === null) return null\n base = inner\n } else if (isClientExpr(obj, clientVars)) {\n base = []\n } else {\n return null\n }\n\n if (prop === 'doc') return [...base, '{}']\n const lit = stringLiteralText(callArgs(node)[0] ?? null)\n if (lit === null) return null // computed collection name → unclaimed\n return [...base, lit]\n}\n\n// The collection path a resolved segment list names: a `collection(...)` call\n// ends on a collection (odd length), a `doc(...)` call ends on a document — drop\n// its trailing id to get the collection it belongs to. Joined with '/'.\nfunction collectionPathOf(node: Parser.SyntaxNode, segs: string[] | null): string | null {\n if (segs === null || segs.length === 0) return null\n const fn = node.childForFieldName('function')\n const prop =\n fn?.type === 'identifier'\n ? fn.text\n : fn?.type === 'member_expression'\n ? fn.childForFieldName('property')?.text\n : null\n const collSegs = prop === 'doc' ? segs.slice(0, -1) : segs\n if (collSegs.length === 0) return null\n return collSegs.join('/')\n}\n\n// The collection path any collection/doc reference (modular or namespaced) names.\nfunction collectionPathFromCall(node: Parser.SyntaxNode, clientVars: Set<string>): string | null {\n const modular = resolveModular(node, clientVars)\n if (modular !== null) return collectionPathOf(node, modular)\n const namespaced = resolveNamespaced(node, clientVars)\n if (namespaced !== null) return collectionPathOf(node, namespaced)\n return null\n}\n\n// Descend a namespaced query/write receiver chain (`.where().orderBy()`, or a\n// bare collection/doc ref) to the collection it targets.\nfunction receiverColl(node: Parser.SyntaxNode | null, clientVars: Set<string>): string | null {\n let cur: Parser.SyntaxNode | null = node\n while (cur?.type === 'call_expression') {\n const fn = cur.childForFieldName('function')\n if (fn?.type !== 'member_expression') return null\n const prop = fn.childForFieldName('property')?.text\n if (prop === 'collection' || prop === 'doc') return collectionPathFromCall(cur, clientVars)\n cur = fn.childForFieldName('object') // skip query methods (where/orderBy/limit/…)\n }\n return null\n}\n\n// The collection a modular `query(collRef, …)` reads — arg0 is the collection ref\n// or a nested `query(...)`.\nfunction modularQueryColl(node: Parser.SyntaxNode, clientVars: Set<string>): string | null {\n const fn = node.childForFieldName('function')\n if (fn?.type !== 'identifier') return null\n if (fn.text === 'query') {\n const arg0 = callArgs(node)[0]\n return arg0?.type === 'call_expression' ? modularQueryColl(arg0, clientVars) : null\n }\n if (fn.text === 'collection' || fn.text === 'doc') return collectionPathFromCall(node, clientVars)\n return null\n}\n\n// A property name, the way drizzle.ts reads it: a bare `status:` or quoted\n// `'status':` key, or a `{ status }` shorthand. A computed / spread key is skipped\n// (returns null / not a pair) rather than guessed at.\nfunction keyName(key: Parser.SyntaxNode | null): string | null {\n if (!key) return null\n if (key.type === 'property_identifier') return key.text\n if (key.type === 'string') return stringLiteralText(key)\n return null\n}\n\nfunction objectKeys(obj: Parser.SyntaxNode | undefined): string[] {\n if (obj?.type !== 'object') return []\n const out: string[] = []\n for (const child of namedChildren(obj)) {\n if (child.type === 'pair') {\n const k = keyName(child.childForFieldName('key'))\n if (k) out.push(k)\n } else if (child.type === 'shorthand_property_identifier') {\n out.push(child.text)\n }\n // spread_element / computed key → left unclaimed\n }\n return out\n}\n\nconst MODULAR_WRITES = new Set(['addDoc', 'setDoc', 'updateDoc'])\nconst NAMESPACED_WRITES = new Set(['add', 'set', 'update'])\nconst READ_CLAUSES = new Set(['where', 'orderBy'])\n\nexport function firestoreEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(file.content)\n const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content)\n if (!hasClient && !hasAdmin) return []\n\n // The writing SDK is the file's imported Firestore SDK: a file uses one SDK\n // (`getFirestore` resolves to client under firebase/firestore, admin under\n // firebase-admin/firestore; `admin.firestore()` is admin). When a file imports\n // both — rare — the per-write SDK is genuinely ambiguous, so we leave the tag\n // off (the field still lands as a column); ADR-169 reads `sdkWrites ?? []`, so\n // an untagged write degrades to silence, never a false positive.\n const fileSdk: SdkWrite | null =\n hasClient && !hasAdmin ? 'client' : hasAdmin && !hasClient ? 'admin' : null\n\n const tree = parseSource(parserForExt(path.extname(file.path)), file.content)\n const clientVars = firestoreClientVars(tree.rootNode)\n\n const collLine = new Map<string, number>() // path → first evidence line\n const writes = new Map<string, Map<string, Set<SdkWrite>>>() // path → field → sdks\n const reads = new Map<string, Set<string>>() // path → read field names\n\n const noteColl = (p: string, line: number): void => {\n if (!collLine.has(p)) collLine.set(p, line)\n }\n const noteWrite = (p: string, fields: string[], line: number): void => {\n if (fields.length === 0) return\n noteColl(p, line)\n let byField = writes.get(p)\n if (!byField) {\n byField = new Map()\n writes.set(p, byField)\n }\n for (const f of fields) {\n let sdks = byField.get(f)\n if (!sdks) {\n sdks = new Set()\n byField.set(f, sdks)\n }\n if (fileSdk) sdks.add(fileSdk)\n }\n }\n const noteRead = (p: string, field: string, line: number): void => {\n noteColl(p, line)\n let s = reads.get(p)\n if (!s) {\n s = new Set()\n reads.set(p, s)\n }\n s.add(field)\n }\n\n const walk = (node: Parser.SyntaxNode): void => {\n if (node.type === 'call_expression') {\n const fn = node.childForFieldName('function')\n const line = node.startPosition.row + 1\n\n // A collection / doc reference — the collection node itself.\n const collPath = collectionPathFromCall(node, clientVars)\n if (collPath) noteColl(collPath, line)\n\n if (fn?.type === 'identifier') {\n // Modular write: setDoc(ref, {…}) / updateDoc(ref, {…}) / addDoc(ref, {…}).\n if (MODULAR_WRITES.has(fn.text)) {\n const args = callArgs(node)\n const ref = args[0]\n const p = ref?.type === 'call_expression' ? collectionPathFromCall(ref, clientVars) : null\n if (p) noteWrite(p, objectKeys(args[1]), line)\n }\n // Modular read: query(collRef, where('field', …), orderBy('field')).\n if (fn.text === 'query') {\n const p = modularQueryColl(node, clientVars)\n if (p) {\n for (const arg of callArgs(node)) {\n if (arg.type !== 'call_expression') continue\n const cfn = arg.childForFieldName('function')\n if (cfn?.type === 'identifier' && READ_CLAUSES.has(cfn.text)) {\n const field = stringLiteralText(callArgs(arg)[0] ?? null)\n if (field) noteRead(p, field, arg.startPosition.row + 1)\n }\n }\n }\n }\n } else if (fn?.type === 'member_expression') {\n const prop = fn.childForFieldName('property')?.text\n const receiver = fn.childForFieldName('object')\n // Namespaced write: <ref>.set({…}) / .update({…}) / .add({…}).\n if (prop && NAMESPACED_WRITES.has(prop)) {\n const p = receiverColl(receiver, clientVars)\n if (p) noteWrite(p, objectKeys(callArgs(node)[0]), line)\n }\n // Namespaced read: <ref>.where('field', …) / .orderBy('field').\n if (prop && READ_CLAUSES.has(prop)) {\n const p = receiverColl(receiver, clientVars)\n const field = stringLiteralText(callArgs(node)[0] ?? null)\n if (p && field) noteRead(p, field, line)\n }\n }\n }\n for (const c of namedChildren(node)) walk(c)\n }\n walk(tree.rootNode)\n\n const out: ExternalEndpoint[] = []\n for (const [collPath, line] of collLine) {\n const byField = writes.get(collPath)\n const readFields = reads.get(collPath)\n // Union of written + read field names → columns (the existing columns fold in\n // calls/index.ts lands these with EXTRACTED provenance).\n const columnSet = new Set<string>()\n if (byField) for (const f of byField.keys()) columnSet.add(f)\n if (readFields) for (const f of readFields) columnSet.add(f)\n\n // Per-written-field SDK tags → the seam ADR-169 joins on. Only fields with a\n // known writing SDK carry a tag; an ambiguous (both-imports) write is a column\n // with no tag.\n let sdkWrites: Record<string, SdkWrite[]> | undefined\n if (byField) {\n for (const [field, sdks] of byField) {\n if (sdks.size === 0) continue\n if (!sdkWrites) sdkWrites = {}\n sdkWrites[field] = [...sdks].sort()\n }\n }\n\n out.push({\n infraId: infraId('firestore-collection', collPath),\n name: collPath,\n kind: 'firestore-collection',\n edgeType: 'CALLS',\n // A firebase import is in scope and the call is scoped to a recognized\n // Firestore client — a framework-aware recognizer matched the SDK shape.\n // Verified-call-site tier (ADR-066), the grade calls/supabase.ts emits at.\n confidenceKind: 'verified-call-site',\n ...(columnSet.size > 0 ? { columns: [...columnSet] } : {}),\n ...(sdkWrites ? { sdkWrites } : {}),\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n return out\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { resolveJsImport } from '../imports.js'\nimport { lineOf, snippet, toPosix, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// MongoDB collection call sites (ADR-147 in-file, ADR-149 cross-file). The\n// collection-grained analog of calls/supabase.ts: it names the collection a file\n// reads or writes so a production-observed mongodb operation (ADR-148 mints the\n// OBSERVED side from the driver's OTel spans) has a file-grained static twin to\n// fuse onto.\n//\n// import mongoose from 'mongoose'\n// const Order = mongoose.model('Order', orderSchema) // → collection 'orders'\n// const Log = mongoose.model('Log', s, 'audit_logs') // → collection 'audit_logs'\n//\n// import { MongoClient } from 'mongodb'\n// db.collection('orders').insertOne(doc) // → collection 'orders'\n//\n// A Mongoose query names a *model* (`Order`), not a collection; the collection\n// is derived. The derivation reuses Mongoose's own pluralizer VERBATIM (below)\n// because the real collection is named by it — `Goose` → `gooses`, not `geese`\n// — so our string matches the one the OBSERVED span carries. Fidelity is the\n// fusion key, not a nicety (ADR-147). The native-driver path is exact: the\n// collection is the string literal.\n//\n// The model is usually registered in `models/Order.js` and queried across\n// `routes/`, `services/` — so `mongooseCrossFileEndpoints` (ADR-149) resolves a\n// query binding to its defining file through the import graph imports.ts already\n// resolves, and names the collection at the *query* site too.\n\nconst MONGOOSE_IMPORT_RE = /(?:from\\s+['\"`]|require\\(\\s*['\"`])mongoose['\"`]/\nconst MONGODB_IMPORT_RE = /(?:from\\s+['\"`]|require\\(\\s*['\"`])mongodb['\"`]/\n\n// `mongoose.pluralize(null)` / `pluralize(false)` disables pluralization\n// globally — `toCollectionName` then returns the model name unchanged (case\n// preserved).\nconst PLURALIZE_DISABLED_RE = /\\bpluralize\\s*\\(\\s*(?:null|false)\\s*\\)/\n\nconst UNCOUNTABLES = new Set([\n 'advice', 'energy', 'excretion', 'digestion', 'cooperation', 'health', 'justice', 'labour',\n 'machinery', 'equipment', 'information', 'pollution', 'sewage', 'paper', 'money', 'species',\n 'series', 'rain', 'rice', 'fish', 'sheep', 'moose', 'deer', 'news', 'expertise', 'status', 'media',\n])\n\n// Ported verbatim from mongoose-legacy-pluralize@2.0.0 (byte-identical to\n// mongoose 9.x lib/helpers/pluralize.js). Ordered; the first matching rule wins.\n// Do not \"correct\" the quirks — an English-accurate pluralizer would produce\n// names Mongoose never generates and fuse onto nothing.\nconst PLURALIZE_RULES: ReadonlyArray<readonly [RegExp, string]> = [\n [/human$/gi, 'humans'],\n [/(m)an$/gi, '$1en'],\n [/(pe)rson$/gi, '$1ople'],\n [/(child)$/gi, '$1ren'],\n [/^(ox)$/gi, '$1en'],\n [/(ax|test)is$/gi, '$1es'],\n [/(octop|vir)us$/gi, '$1i'],\n [/(alias|status)$/gi, '$1es'],\n [/(bu)s$/gi, '$1ses'],\n [/(buffal|tomat|potat)o$/gi, '$1oes'],\n [/([ti])um$/gi, '$1a'],\n [/sis$/gi, 'ses'],\n [/(?:([^f])fe|([lr])f)$/gi, '$1$2ves'],\n [/(hive)$/gi, '$1s'],\n [/([^aeiouy]|qu)y$/gi, '$1ies'],\n [/(x|ch|ss|sh)$/gi, '$1es'],\n [/(matr|vert|ind)ix|ex$/gi, '$1ices'],\n [/([m|l])ouse$/gi, '$1ice'],\n [/(kn|w|l)ife$/gi, '$1ives'],\n [/(quiz)$/gi, '$1zes'],\n [/s$/gi, 's'],\n [/([^a-z])$/, '$1'],\n [/$/gi, 's'],\n]\n\nexport function pluralizeCollection(name: string): string {\n const str = name.toLowerCase()\n if (UNCOUNTABLES.has(str)) return str\n for (const [re, repl] of PLURALIZE_RULES) {\n if (str.match(re)) return str.replace(re, repl)\n }\n return str\n}\n\n// The Mongoose Model query/write methods (statics + instance) plus the pre-v7\n// names still common in older code. A `<binding>.<one-of-these>(` call is a\n// collection access. Used cross-file to attribute a query to its collection.\nconst MODEL_METHODS = [\n 'find', 'findOne', 'findById', 'findByIdAndUpdate', 'findByIdAndDelete', 'findOneAndUpdate',\n 'findOneAndDelete', 'findOneAndReplace', 'countDocuments', 'estimatedDocumentCount', 'distinct',\n 'aggregate', 'exists', 'where', 'populate', 'watch', 'hydrate', 'create', 'insertMany', 'insertOne',\n 'updateOne', 'updateMany', 'replaceOne', 'deleteOne', 'deleteMany', 'bulkWrite', 'bulkSave', 'save',\n // pre-v7, removed in current mongoose but present in older codebases:\n 'count', 'update', 'remove', 'findOneAndRemove', 'findByIdAndRemove',\n]\nconst MODEL_METHODS_ALT = MODEL_METHODS.join('|')\n\nfunction schemaCollectionVars(content: string): Map<string, string> {\n const out = new Map<string, string>()\n const re = /(?:const|let|var)\\s+(\\w+)\\s*=\\s*new\\s+(?:mongoose\\s*\\.\\s*)?Schema\\s*\\(/g\n let m: RegExpExecArray | null\n while ((m = re.exec(content)) !== null) {\n const windowText = content.slice(m.index, m.index + 2000)\n const opt = /\\bcollection\\s*:\\s*['\"`]([\\w.-]+)['\"`]/.exec(windowText)\n if (opt) out.set(m[1]!, opt[1]!)\n }\n return out\n}\n\ninterface Resolved {\n name: string\n kind: 'mongodb-collection' | 'mongodb-model'\n}\n\nfunction resolveModel(\n modelName: string,\n rest: string,\n schemaColl: Map<string, string>,\n pluralizeOn: boolean,\n): Resolved {\n const trimmed = rest.trim()\n const literalThird = /,\\s*['\"`]([\\w.-]+)['\"`]\\s*$/.exec(trimmed)\n if (literalThird) return { name: literalThird[1]!, kind: 'mongodb-collection' }\n\n const firstIdent = /^([A-Za-z_$][\\w$]*)/.exec(trimmed)?.[1]\n if (firstIdent && schemaColl.has(firstIdent)) {\n return { name: schemaColl.get(firstIdent)!, kind: 'mongodb-collection' }\n }\n if (/,\\s*[A-Za-z_$][\\w$]*\\s*$/.test(trimmed)) {\n return { name: modelName, kind: 'mongodb-model' }\n }\n return { name: pluralizeOn ? pluralizeCollection(modelName) : modelName, kind: 'mongodb-collection' }\n}\n\ninterface ModelDef {\n modelName: string\n varName: string | null // the `const X =` the registration was assigned to, if any\n resolved: Resolved\n matchText: string\n}\n\n// Every `[const X = ][obj.]model('Name'[, schema[, 'coll']])` registration in a\n// file, with its resolved collection. Shared by the in-file emitter and the\n// cross-file registry.\nfunction collectModelDefs(content: string, pluralizeOn: boolean): ModelDef[] {\n const schemaColl = schemaCollectionVars(content)\n const out: ModelDef[] = []\n const re =\n /(?:(?:const|let|var)\\s+(\\w+)\\s*=\\s*)?(?:await\\s+)?(?:\\b\\w+\\s*\\.\\s*)?\\bmodel\\s*\\(\\s*['\"`]([\\w$]+)['\"`]\\s*(?:,\\s*([\\s\\S]{0,300}?))?\\)/g\n let m: RegExpExecArray | null\n while ((m = re.exec(content)) !== null) {\n out.push({\n modelName: m[2]!,\n varName: m[1] ?? null,\n resolved: resolveModel(m[2]!, m[3] ?? '', schemaColl, pluralizeOn),\n matchText: m[0],\n })\n }\n return out\n}\n\nfunction endpoint(\n r: Resolved,\n file: SourceFile,\n serviceDir: string,\n matchText: string,\n): ExternalEndpoint {\n const line = lineOf(file.content, matchText)\n return {\n infraId: infraId(r.kind, r.name),\n name: r.name,\n kind: r.kind,\n edgeType: 'CALLS',\n confidenceKind: 'verified-call-site',\n evidence: { file: path.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) },\n }\n}\n\n// ── in-file (ADR-147) ──────────────────────────────────────────────────────\n\nexport function mongooseEndpointsFromFile(file: SourceFile, serviceDir: string): ExternalEndpoint[] {\n const hasMongoose = MONGOOSE_IMPORT_RE.test(file.content)\n const hasMongodb = MONGODB_IMPORT_RE.test(file.content)\n if (!hasMongoose && !hasMongodb) return []\n\n const content = file.content\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n const push = (r: Resolved, matchText: string): void => {\n const key = `${r.kind}/${r.name}`\n if (seen.has(key)) return\n seen.add(key)\n out.push(endpoint(r, file, serviceDir, matchText))\n }\n\n if (hasMongoose) {\n const pluralizeOn = !PLURALIZE_DISABLED_RE.test(content)\n for (const def of collectModelDefs(content, pluralizeOn)) push(def.resolved, def.matchText)\n }\n\n // Native driver — `<db>.collection('orders')`. The literal is the collection.\n const collRe = /\\.\\s*collection\\s*\\(\\s*['\"`]([\\w.$-]+)['\"`]\\s*\\)/g\n let c: RegExpExecArray | null\n while ((c = collRe.exec(content)) !== null) {\n push({ name: c[1]!, kind: 'mongodb-collection' }, c[0])\n }\n\n return out\n}\n\n// ── cross-file (ADR-149) ───────────────────────────────────────────────────\n\n// A file's model exports: the collection reachable via each named export, and\n// via the default / whole-module export (`module.exports = Order`).\ninterface FileExports {\n byName: Map<string, string>\n default?: string\n}\n\n// A binding imported into a query file. `named` maps a local name to a specific\n// export; `default`/`whole`/`namespace` bind the module object itself.\ninterface ImportBinding {\n local: string\n kind: 'named' | 'default' | 'namespace' | 'whole'\n exportName?: string\n specifier: string\n}\n\nfunction parseDestructure(inner: string, specifier: string, out: ImportBinding[]): void {\n for (const raw of inner.split(',')) {\n const part = raw.trim()\n if (!part) continue\n const asM = /^(\\w+)\\s+as\\s+(\\w+)$/.exec(part) // ESM: A as C\n const colonM = /^(\\w+)\\s*:\\s*(\\w+)$/.exec(part) // CJS: A: C\n if (asM) out.push({ local: asM[2]!, kind: 'named', exportName: asM[1]!, specifier })\n else if (colonM) out.push({ local: colonM[2]!, kind: 'named', exportName: colonM[1]!, specifier })\n else {\n const id = /^(\\w+)$/.exec(part)\n if (id) out.push({ local: id[1]!, kind: 'named', exportName: id[1]!, specifier })\n }\n }\n}\n\nfunction parseImportBindings(content: string): ImportBinding[] {\n const out: ImportBinding[] = []\n\n const esm = /import\\s+([^;'\"`\\n]+?)\\s+from\\s*['\"`]([^'\"`]+)['\"`]/g\n let m: RegExpExecArray | null\n while ((m = esm.exec(content)) !== null) {\n const clause = m[1]!.trim()\n const spec = m[2]!\n const ns = /^\\*\\s+as\\s+(\\w+)$/.exec(clause)\n if (ns) {\n out.push({ local: ns[1]!, kind: 'namespace', specifier: spec })\n continue\n }\n const named = /\\{([^}]*)\\}/.exec(clause)\n if (named) parseDestructure(named[1]!, spec, out)\n const def = /^(\\w+)\\s*(?:,|$)/.exec(clause) // leading default binding, before any `{`\n if (def && !clause.startsWith('{')) out.push({ local: def[1]!, kind: 'default', specifier: spec })\n }\n\n const cjs = /(?:const|let|var)\\s+(\\{[^}]*\\}|\\w+)\\s*=\\s*require\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n while ((m = cjs.exec(content)) !== null) {\n const lhs = m[1]!\n const spec = m[2]!\n if (lhs.startsWith('{')) parseDestructure(lhs.slice(1, -1), spec, out)\n else out.push({ local: lhs, kind: 'whole', specifier: spec }) // require() default IS module.exports\n }\n\n return out\n}\n\n// A defining file's exports, keyed for both named and default/whole-module\n// import. `byName` treats every model var as a possible named export — valid\n// source can only import a name it actually exported, so a non-exported var is\n// never asked for. The default is the single-model file's model, or the one\n// named in `module.exports = X` / `export default X`.\nfunction fileExportsOf(content: string, pluralizeOn: boolean): FileExports | null {\n const defs = collectModelDefs(content, pluralizeOn).filter((d) => d.resolved.kind === 'mongodb-collection')\n if (defs.length === 0) return null\n\n const byVar = new Map<string, string>()\n for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name)\n\n const byName = new Map(byVar)\n let def: string | undefined\n\n const named = /(?:module\\.exports|export\\s+default)\\s*=\\s*(\\w+)\\b/.exec(content)\n if (named && byVar.has(named[1]!)) def = byVar.get(named[1]!)\n if (!def) {\n const inline =\n /(?:module\\.exports|export\\s+default)\\s*=\\s*(?:await\\s+)?(?:\\w+\\s*\\.\\s*)?model\\s*\\(\\s*['\"`]([\\w$]+)['\"`]/.exec(content)\n if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]!) : inline[1]!\n }\n // Single-model file: a default/whole-module import binds to its one model.\n if (!def && byVar.size === 1) def = [...byVar.values()][0]!\n\n return { byName, ...(def ? { default: def } : {}) }\n}\n\n/**\n * Cross-file model→collection resolution (ADR-149). Names the collection at the\n * *query* site — `routes/orders.js` calling `Order.find()` on a model defined in\n * `models/Order.js` — by resolving the imported binding to its defining file\n * through the same import resolution `imports.ts` uses, then attributing the\n * query methods on that binding. Emits onto the same `mongodb-collection` node\n * the in-file and observed paths use, so all three fuse.\n *\n * `files` should be the same masked (comment-stripped) SourceFiles the per-file\n * pass sees. Returns endpoints whose evidence points at the query site.\n */\nexport async function mongooseCrossFileEndpoints(\n files: SourceFile[],\n serviceDir: string,\n): Promise<ExternalEndpoint[]> {\n const mongooseFiles = files.filter((f) => MONGOOSE_IMPORT_RE.test(f.content))\n if (mongooseFiles.length === 0) return []\n\n // A single `pluralize(null)` anywhere flips derivation for the whole service.\n const pluralizeOn = !files.some((f) => PLURALIZE_DISABLED_RE.test(f.content))\n\n // Registry: service-relative posix path → its model exports.\n const registry = new Map<string, FileExports>()\n for (const f of mongooseFiles) {\n const fx = fileExportsOf(f.content, pluralizeOn)\n if (fx) registry.set(toPosix(path.relative(serviceDir, f.path)), fx)\n }\n if (registry.size === 0) return []\n\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n\n for (const f of files) {\n const bindings = parseImportBindings(f.content)\n if (bindings.length === 0) continue\n\n // Bindings that ARE a model (named/default import) → `local.method()`.\n const directColl = new Map<string, string>()\n // Whole-module / namespace bindings → `local.Export.method()`.\n const nsExports = new Map<string, FileExports>()\n\n for (const b of bindings) {\n const resolvedRel = await resolveJsImport(b.specifier, path.dirname(f.path), serviceDir, null)\n if (!resolvedRel) continue\n const fx = registry.get(resolvedRel)\n if (!fx) continue\n if (b.kind === 'named' && b.exportName) {\n const coll = fx.byName.get(b.exportName)\n if (coll) directColl.set(b.local, coll)\n } else if (b.kind === 'default') {\n if (fx.default) directColl.set(b.local, fx.default)\n } else {\n // whole (require) or namespace — could be the model itself or the module object.\n if (fx.default) directColl.set(b.local, fx.default)\n nsExports.set(b.local, fx)\n }\n }\n if (directColl.size === 0 && nsExports.size === 0) continue\n\n // A binding defined *in this file* is the in-file pass's job — don't double-count.\n const localDefs = new Set(\n collectModelDefs(f.content, pluralizeOn)\n .map((d) => d.varName)\n .filter((v): v is string => v !== null),\n )\n\n const emit = (collection: string, matchText: string): void => {\n const key = `${f.path}::${collection}`\n if (seen.has(key)) return\n seen.add(key)\n out.push(endpoint({ name: collection, kind: 'mongodb-collection' }, f, serviceDir, matchText))\n }\n\n for (const [local, collection] of directColl) {\n if (localDefs.has(local)) continue\n const qre = new RegExp(`\\\\b${local}\\\\s*\\\\.\\\\s*(?:${MODEL_METHODS_ALT})\\\\s*\\\\(`, 'g')\n let qm: RegExpExecArray | null\n while ((qm = qre.exec(f.content)) !== null) emit(collection, qm[0])\n }\n for (const [local, fx] of nsExports) {\n const qre = new RegExp(`\\\\b${local}\\\\s*\\\\.\\\\s*(\\\\w+)\\\\s*\\\\.\\\\s*(?:${MODEL_METHODS_ALT})\\\\s*\\\\(`, 'g')\n let qm: RegExpExecArray | null\n while ((qm = qre.exec(f.content)) !== null) {\n const coll = fx.byName.get(qm[1]!)\n if (coll) emit(coll, qm[0])\n }\n }\n }\n\n return out\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport Python from 'tree-sitter-python'\nimport { infraId } from '@neat.is/types'\nimport { snippet, type ExternalEndpoint, type SourceFile, type TableReference } from './shared.js'\n\n// SQLAlchemy / Flask-SQLAlchemy table call sites (ADR-151, ADR-152). The SQL\n// analog of calls/mongoose.ts: it names the table a model maps to, so a\n// production SQLAlchemy query — whose OBSERVED table NEAT recovers by parsing the\n// `db.statement` SQL (the instrumentation emits no table attribute, ADR-152) —\n// has a file-grained static twin to fuse onto at `infra:sql-table:<name>`.\n//\n// The table name is the fusion key, derived VERBATIM the way the ORM derives it\n// so the static string matches the table the running query hits:\n// - plain SQLAlchemy declarative: __tablename__ = 'orders' (explicit, required)\n// - Flask-SQLAlchemy (no __tablename__): camel_to_snake_case(ClassName)\n// class UserProfile(db.Model): ... → 'user_profile'\n// - native Core: Table('orders', metadata, ...) → the string literal\n\nconst SQLALCHEMY_IMPORT_RE = /(?:from|import)\\s+(?:flask_sqlalchemy|sqlalchemy)\\b/\n\nconst PARSE_CHUNK = 16384\n\nfunction makePyParser(): Parser {\n const p = new Parser()\n p.setLanguage(Python)\n return p\n}\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\n// Verbatim port of flask_sqlalchemy's `camel_to_snake_case` (model.py, v3.x):\n// re.sub(r\"((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))\", r\"_\\1\", name).lower().lstrip(\"_\")\n// Reproduced byte-for-byte so a derived table matches the one Flask-SQLAlchemy\n// actually creates — `UserProfile` → `user_profile`, `HTTPRequest` →\n// `http_request`, `OAuth2Token` → `o_auth2_token`. Do not \"improve\" it: an\n// English-nicer version would name a table the ORM never created and fuse onto\n// nothing (the same fidelity rule the Mongoose pluralizer follows).\nexport function flaskSqlalchemyTableName(className: string): string {\n return className\n .replace(/((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))/g, '_$1')\n .toLowerCase()\n .replace(/^_+/, '')\n}\n\nfunction namedChildren(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c) out.push(c)\n }\n return out\n}\n\nfunction pyStaticStringText(node: Parser.SyntaxNode): string | null {\n if (node.type !== 'string') return null\n for (const child of namedChildren(node)) {\n if (child.type === 'interpolation') return null // f-string — not a static literal\n if (child.type === 'string_content') return child.text\n }\n return ''\n}\n\n// The explicit `__tablename__ = 'x'` in a class body: its literal name, `'computed'`\n// when it's assigned a non-literal (never guessed), or null when absent.\nfunction explicitTablename(body: Parser.SyntaxNode): { name: string } | 'computed' | null {\n for (const stmt of namedChildren(body)) {\n if (stmt.type !== 'expression_statement') continue\n const assign = stmt.namedChild(0)\n if (assign?.type !== 'assignment') continue\n if (assign.childForFieldName('left')?.text !== '__tablename__') continue\n const right = assign.childForFieldName('right')\n if (right?.type === 'string') {\n const s = pyStaticStringText(right)\n return s ? { name: s } : 'computed'\n }\n return 'computed'\n }\n return null\n}\n\n// A Flask-SQLAlchemy model derives its table from the class name when it has no\n// `__tablename__` — recognised by a `db.Model` / `Model` base class.\nfunction extendsFlaskModel(cls: Parser.SyntaxNode): boolean {\n const supers = cls.childForFieldName('superclasses')\n if (!supers) return false\n for (const a of namedChildren(supers)) {\n const t = a.text\n if (t === 'db.Model' || t === 'Model' || t.endsWith('.Model')) return true\n }\n return false\n}\n\nfunction walk(node: Parser.SyntaxNode, visit: (n: Parser.SyntaxNode) => void): void {\n visit(node)\n for (const c of namedChildren(node)) walk(c, visit)\n}\n\n// ── declared columns off a SQLAlchemy model body (ADR-157 §3) ────────────────\n\n// The bare builder name a `Column(...)` / `mapped_column(...)` call resolves to,\n// tolerating a `db.` / `sa.` / `orm.` qualifier. Anything else (a `relationship`,\n// a plain value) is not a column.\nconst COLUMN_BUILDERS = new Set(['Column', 'mapped_column'])\nfunction isColumnBuilder(call: Parser.SyntaxNode): boolean {\n const fn = call.childForFieldName('function')\n const t = fn?.text\n if (!t) return false\n const base = t.includes('.') ? t.slice(t.lastIndexOf('.') + 1) : t\n return COLUMN_BUILDERS.has(base)\n}\n\n// The first positional argument of a call as a static string, or null when the\n// first positional argument is not a plain string literal (a type like `Integer`)\n// — so the caller falls back to the attribute name.\nfunction firstPositionalString(call: Parser.SyntaxNode): string | null {\n const args = call.childForFieldName('arguments')\n if (!args) return null\n for (const arg of namedChildren(args)) {\n if (arg.type === 'keyword_argument') continue\n return arg.type === 'string' ? pyStaticStringText(arg) : null\n }\n return null\n}\n\n// The DB columns a declarative model body declares. For each `x = Column(...)` /\n// `x: Mapped[..] = mapped_column(...)` assignment, the column name is the explicit\n// first-string argument where given (`Column('total_amount', Integer)` →\n// `total_amount`), else the attribute name itself — SQLAlchemy's own rule, where\n// the attribute name IS the column name (ADR-157 §3). Non-Column assignments\n// (`__tablename__`, `relationship(...)`) are skipped, and a computed / non-string\n// explicit name is left unclaimed rather than guessed.\nfunction columnsFromClassBody(body: Parser.SyntaxNode): string[] {\n const out: string[] = []\n const seen = new Set<string>()\n for (const stmt of namedChildren(body)) {\n if (stmt.type !== 'expression_statement') continue\n const assign = stmt.namedChild(0)\n if (assign?.type !== 'assignment') continue\n const left = assign.childForFieldName('left')\n if (left?.type !== 'identifier') continue\n const right = assign.childForFieldName('right')\n if (right?.type !== 'call' || !isColumnBuilder(right)) continue\n const name = firstPositionalString(right) ?? left.text\n if (name && !seen.has(name)) {\n seen.add(name)\n out.push(name)\n }\n }\n return out\n}\n\n// ── foreign-key table→table references (ADR-161) ─────────────────────────────\n//\n// SQLAlchemy names a FK by the DATABASE table directly: `ForeignKey('users.id')`\n// — the string is `<table>.<column>` (or `<schema>.<table>.<column>`), so the\n// parent table is the segment before the last dot, already at DB-name fidelity\n// (no code-model translation needed, unlike Drizzle/Prisma). The child is the\n// enclosing model's `__tablename__` / Flask-derived table, the same table the\n// column read (ADR-157 §3) lands on. A non-string `ForeignKey(User.id)` (a mapped\n// attribute) is left unclaimed — resolving the class→table there is a follow-on.\n\n// The parent DB table a `ForeignKey('users.id')` names, tolerating a `db.` / `sa.`\n// / `sqlalchemy.` qualifier on the call. Null for a non-string / bare argument.\nfunction foreignKeyParentTable(call: Parser.SyntaxNode): string | null {\n const fn = call.childForFieldName('function')\n const t = fn?.text\n if (!t) return null\n const base = t.includes('.') ? t.slice(t.lastIndexOf('.') + 1) : t\n if (base !== 'ForeignKey') return null\n const target = firstPositionalString(call)\n if (!target) return null\n const parts = target.split('.')\n if (parts.length < 2) return null // needs at least `<table>.<column>`\n return parts[parts.length - 2]! // `users.id` → users; `public.users.id` → users\n}\n\nexport function sqlalchemyForeignKeys(\n file: SourceFile,\n serviceDir: string,\n): TableReference[] {\n if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return []\n const tree = parseSource(makePyParser(), file.content)\n const out: TableReference[] = []\n const seen = new Set<string>()\n\n walk(tree.rootNode, (node) => {\n if (node.type !== 'class_definition') return\n const body = node.childForFieldName('body')\n const nameNode = node.childForFieldName('name')\n if (!body || !nameNode) return\n const explicit = explicitTablename(body)\n if (explicit === 'computed') return // named but not a literal — never guess\n let childTable: string | null = null\n if (explicit) childTable = explicit.name\n else if (extendsFlaskModel(node)) childTable = flaskSqlalchemyTableName(nameNode.text)\n if (!childTable) return\n\n walk(body, (n) => {\n if (n.type !== 'call') return\n const parentTable = foreignKeyParentTable(n)\n if (!parentTable) return\n const key = `${childTable}->${parentTable}`\n if (seen.has(key)) return\n seen.add(key)\n const line = n.startPosition.row + 1\n out.push({\n childTable: childTable!,\n parentTable,\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n })\n })\n\n return out\n}\n\nexport function sqlalchemyEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return []\n const tree = parseSource(makePyParser(), file.content)\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n const push = (name: string, line: number, columns?: string[]): void => {\n if (seen.has(name)) return\n seen.add(name)\n out.push({\n infraId: infraId('sql-table', name),\n name,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'verified-call-site',\n ...(columns && columns.length > 0 ? { columns } : {}),\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n\n walk(tree.rootNode, (node) => {\n if (node.type === 'class_definition') {\n const body = node.childForFieldName('body')\n const nameNode = node.childForFieldName('name')\n if (!body || !nameNode) return\n const line = node.startPosition.row + 1\n const explicit = explicitTablename(body)\n if (explicit === 'computed') return // named but not a literal — never guess\n // Declared columns off the model body — the DB name at attribute grain.\n const columns = columnsFromClassBody(body)\n if (explicit) {\n push(explicit.name, line, columns)\n return\n }\n // No __tablename__: Flask-SQLAlchemy derives the table from the class name.\n if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line, columns)\n return\n }\n // Native Core: `Table('orders', metadata, ...)` — the first positional string.\n if (node.type === 'call') {\n const fn = node.childForFieldName('function')\n const fnText = fn?.text\n if (fnText !== 'Table' && fnText !== 'sa.Table' && fnText !== 'sqlalchemy.Table') return\n const first = node.childForFieldName('arguments')?.namedChild(0)\n if (first?.type === 'string') {\n const s = pyStaticStringText(first)\n if (s) push(s, node.startPosition.row + 1)\n }\n }\n })\n\n return out\n}\n\n// ── cross-file model→table query attribution (ADR-149 analog) ────────────────\n\n// A model class name → its table, service-wide (SQLAlchemy + Flask-SQLAlchemy). A\n// name defined in two files with different tables is dropped — ambiguous, never\n// guessed (the ADR-147 discipline).\nfunction buildSqlalchemyModelRegistry(files: SourceFile[]): Map<string, string> {\n const table = new Map<string, string>()\n const ambiguous = new Set<string>()\n const parser = makePyParser()\n for (const file of files) {\n if (!SQLALCHEMY_IMPORT_RE.test(file.content)) continue\n const tree = parseSource(parser, file.content)\n walk(tree.rootNode, (node) => {\n if (node.type !== 'class_definition') return\n const nameNode = node.childForFieldName('name')\n const body = node.childForFieldName('body')\n if (!nameNode || !body) return\n const explicit = explicitTablename(body)\n if (explicit === 'computed') return\n let t: string | null = null\n if (explicit) t = explicit.name\n else if (extendsFlaskModel(node)) t = flaskSqlalchemyTableName(nameNode.text)\n if (!t) return\n const cls = nameNode.text\n if (table.has(cls) && table.get(cls) !== t) ambiguous.add(cls)\n else table.set(cls, t)\n })\n }\n for (const a of ambiguous) table.delete(a)\n return table\n}\n\n// The model class names a file queries: `session.query(X)` / `db.session.query(X)`\n// / `select(X)` / `session.get(X, …)` (the first argument), and `X.query`\n// (Flask-SQLAlchemy, the object). A non-model receiver (`session.query`) is\n// filtered downstream by the registry.\nfunction queryClassSites(root: Parser.SyntaxNode): { cls: string; line: number }[] {\n const out: { cls: string; line: number }[] = []\n walk(root, (node) => {\n if (node.type === 'call') {\n const fn = node.childForFieldName('function')\n const isQueryCall =\n (fn?.type === 'attribute' &&\n (fn.childForFieldName('attribute')?.text === 'query' ||\n fn.childForFieldName('attribute')?.text === 'get')) ||\n (fn?.type === 'identifier' && fn.text === 'select')\n if (isQueryCall) {\n const arg = node.childForFieldName('arguments')?.namedChild(0)\n if (arg?.type === 'identifier') out.push({ cls: arg.text, line: node.startPosition.row + 1 })\n }\n }\n if (node.type === 'attribute') {\n const obj = node.childForFieldName('object')\n if (node.childForFieldName('attribute')?.text === 'query' && obj?.type === 'identifier') {\n out.push({ cls: obj.text, line: node.startPosition.row + 1 })\n }\n }\n })\n return out\n}\n\n// Does a file import this name — so a query on it references a model defined\n// elsewhere? Bounds the attribution to a real cross-file reference (the in-file\n// case is already the definition-site edge).\nfunction importsModelName(content: string, name: string): boolean {\n return new RegExp(`\\\\bimport\\\\b[^\\\\n]*\\\\b${name}\\\\b`).test(content)\n}\n\n// Attribute a cross-file query to its table at the *query* site: a file that runs\n// `session.query(Order)` on an `Order` model defined and imported from another\n// file gets the `file → sql-table:<name>` edge, so the code that actually reads\n// the table is named — not only the model-definition file. A computed model, or\n// a query on a locally-defined model (already the definition-site edge), is not\n// re-emitted here.\nexport function pythonOrmCrossFileEndpoints(\n files: SourceFile[],\n serviceDir: string,\n): ExternalEndpoint[] {\n const registry = buildSqlalchemyModelRegistry(files)\n if (registry.size === 0) return []\n const parser = makePyParser()\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n for (const file of files) {\n if (!SQLALCHEMY_IMPORT_RE.test(file.content)) continue\n const tree = parseSource(parser, file.content)\n for (const { cls, line } of queryClassSites(tree.rootNode)) {\n const t = registry.get(cls)\n if (!t) continue\n if (!importsModelName(file.content, cls)) continue // cross-file (imported) only\n const key = `${file.path}::${t}`\n if (seen.has(key)) continue\n seen.add(key)\n out.push({\n infraId: infraId('sql-table', t),\n name: t,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n }\n return out\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport Python from 'tree-sitter-python'\nimport { infraId } from '@neat.is/types'\nimport { snippet, type ExternalEndpoint, type SourceFile } from './shared.js'\n\n// Django ORM table call sites (ADR-151). A Django model maps to a table named\n// `<app_label>_<lowercased-model>` unless `Meta.db_table` overrides it; the\n// app_label defaults to the model file's app package (its parent directory),\n// overridable by `Meta.app_label`. This is Django's naming reproduced verbatim —\n// the fusion key, the same discipline the Mongoose pluralizer follows.\n//\n// It emits the same `infra:sql-table:<name>` node the SQLAlchemy extractor and\n// the OBSERVED `db.statement` parse (ADR-152) use, so a declared Django model and\n// the runtime query that hits its table fuse onto one node.\n\nconst DJANGO_IMPORT_RE = /(?:from|import)\\s+django\\b/\nconst PARSE_CHUNK = 16384\n\nfunction makePyParser(): Parser {\n const p = new Parser()\n p.setLanguage(Python)\n return p\n}\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nfunction namedChildren(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c) out.push(c)\n }\n return out\n}\n\nfunction pyStaticStringText(node: Parser.SyntaxNode): string | null {\n if (node.type !== 'string') return null\n for (const c of namedChildren(node)) {\n if (c.type === 'interpolation') return null\n if (c.type === 'string_content') return c.text\n }\n return ''\n}\n\nfunction walk(node: Parser.SyntaxNode, visit: (n: Parser.SyntaxNode) => void): void {\n visit(node)\n for (const c of namedChildren(node)) walk(c, visit)\n}\n\nfunction extendsDjangoModel(cls: Parser.SyntaxNode): boolean {\n const supers = cls.childForFieldName('superclasses')\n if (!supers) return false\n for (const a of namedChildren(supers)) {\n const t = a.text\n if (t === 'models.Model' || t === 'Model' || t.endsWith('.Model')) return true\n }\n return false\n}\n\n// Read `Meta.db_table` / `Meta.app_label` off a model class body (both literal).\nfunction readMeta(body: Parser.SyntaxNode): { dbTable?: string; appLabel?: string } {\n const out: { dbTable?: string; appLabel?: string } = {}\n for (const stmt of namedChildren(body)) {\n if (stmt.type !== 'class_definition') continue\n if (stmt.childForFieldName('name')?.text !== 'Meta') continue\n const metaBody = stmt.childForFieldName('body')\n if (!metaBody) continue\n for (const ms of namedChildren(metaBody)) {\n if (ms.type !== 'expression_statement') continue\n const a = ms.namedChild(0)\n if (a?.type !== 'assignment') continue\n const l = a.childForFieldName('left')?.text\n const r = a.childForFieldName('right')\n if (r?.type !== 'string') continue\n if (l === 'db_table') out.dbTable = pyStaticStringText(r) ?? undefined\n if (l === 'app_label') out.appLabel = pyStaticStringText(r) ?? undefined\n }\n }\n return out\n}\n\nexport function djangoOrmEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n if (!DJANGO_IMPORT_RE.test(file.content)) return []\n const tree = parseSource(makePyParser(), file.content)\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n // The app_label defaults to the model file's app package — its parent directory.\n const defaultAppLabel = path.basename(path.dirname(file.path))\n\n walk(tree.rootNode, (node) => {\n if (node.type !== 'class_definition') return\n if (!extendsDjangoModel(node)) return\n const nameNode = node.childForFieldName('name')\n const body = node.childForFieldName('body')\n if (!nameNode || !body) return\n const meta = readMeta(body)\n const table = meta.dbTable ?? `${meta.appLabel ?? defaultAppLabel}_${nameNode.text.toLowerCase()}`\n if (seen.has(table)) return\n seen.add(table)\n const line = node.startPosition.row + 1\n out.push({\n infraId: infraId('sql-table', table),\n name: table,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'verified-call-site',\n evidence: { file: path.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) },\n })\n })\n\n return out\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport { infraId } from '@neat.is/types'\nimport { GRAMMAR_BY_EXT, parseSource } from '../symbols.js'\nimport { snippet, type ExternalEndpoint, type SourceFile, type TableReference } from './shared.js'\n\n// Drizzle schema table + column recognizer (ADR-152 table, ADR-157 §3 columns).\n// The declared side of column grain: it names the table a `pgTable('orders',\n// { … })` (mysqlTable / sqliteTable) declares AND the columns inside it, so a\n// production query — whose OBSERVED table + columns NEAT recovers by parsing\n// `db.statement` (ADR-152 / ADR-157 §2) — has a static twin to fuse onto at\n// `infra:sql-table:<name>`.\n//\n// import { pgTable, integer, text } from 'drizzle-orm/pg-core'\n// export const orders = pgTable('orders', {\n// id: serial('id').primaryKey(),\n// userId: integer('user_id'),\n// total: integer(),\n// status: text('status'),\n// })\n//\n// The table name is the first string literal. Each COLUMN name is derived the way\n// Drizzle names the DB column, because that string — not the JS field name — is\n// the fusion key the running query carries (ADR-157 §3, the load-bearing rule):\n// - the explicit column-builder name wins: integer('user_id') → user_id\n// - the object key is Drizzle's own fallback when the builder is given no\n// name: total: integer() → total\n// Reading the key alone would name `userId` where production writes `user_id` and\n// fuse onto nothing — the same fidelity rule the Mongoose pluralizer (ADR-147) and\n// the Flask table-name port (ADR-152) follow. A computed / interpolated name is\n// left unclaimed rather than guessed.\n\nconst DRIZZLE_IMPORT_RE = /drizzle-orm/\nconst TABLE_BUILDERS = new Set(['pgTable', 'mysqlTable', 'sqliteTable'])\n\nfunction parserForExt(ext: string): Parser {\n const p = new Parser()\n p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript)\n return p\n}\n\nfunction namedChildren(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c) out.push(c)\n }\n return out\n}\n\n// The literal text inside a `string` node (quotes stripped). Null for a template\n// / interpolated string — never guessed at.\nfunction stringLiteralText(node: Parser.SyntaxNode | null): string | null {\n if (!node || node.type !== 'string') return null\n for (const child of namedChildren(node)) {\n if (child.type === 'string_fragment') return child.text\n }\n return '' // an empty '' literal has no string_fragment child\n}\n\n// The first positional string argument of a call, or null when the first\n// argument is not a plain string literal (so the caller falls back to the key).\nfunction firstStringArg(call: Parser.SyntaxNode): string | null {\n const args = call.childForFieldName('arguments')\n if (!args) return null\n for (const arg of namedChildren(args)) {\n if (arg.type === 'string') return stringLiteralText(arg)\n // Stop at the first positional arg — a non-string first arg means no name.\n return null\n }\n return null\n}\n\n// The DB column name a Drizzle column value declares. Walks to the head builder\n// call at the base of a `.primaryKey().notNull()` chain and reads its first\n// string argument (the explicit column name). Null when the builder is given no\n// name, so the caller falls back to the object key — exactly as Drizzle does.\nfunction builderColumnName(value: Parser.SyntaxNode): string | null {\n let node: Parser.SyntaxNode | null = value\n while (node) {\n if (node.type === 'call_expression') {\n const fn = node.childForFieldName('function')\n if (fn?.type === 'identifier') return firstStringArg(node)\n if (fn?.type === 'member_expression') {\n node = fn.childForFieldName('object')\n continue\n }\n return null\n }\n if (node.type === 'member_expression') {\n node = node.childForFieldName('object')\n continue\n }\n return null\n }\n return null\n}\n\n// A pair key → its column-name string (a bare `total:` or a quoted `'total':`).\nfunction keyName(key: Parser.SyntaxNode | null): string | null {\n if (!key) return null\n if (key.type === 'property_identifier') return key.text\n if (key.type === 'string') return stringLiteralText(key)\n return null\n}\n\n// The columns declared in a pgTable's second-argument object. Reads each `pair`,\n// preferring the builder's explicit name over the object key, and skips spreads /\n// computed keys rather than guessing.\nfunction columnsFromObject(obj: Parser.SyntaxNode): string[] {\n const out: string[] = []\n const seen = new Set<string>()\n for (const child of namedChildren(obj)) {\n if (child.type !== 'pair') continue\n const value = child.childForFieldName('value')\n const key = keyName(child.childForFieldName('key'))\n const name = (value ? builderColumnName(value) : null) ?? key\n if (name && !seen.has(name)) {\n seen.add(name)\n out.push(name)\n }\n }\n return out\n}\n\nexport function drizzleEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n if (!DRIZZLE_IMPORT_RE.test(file.content)) return []\n const tree = parseSource(parserForExt(path.extname(file.path)), file.content)\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n\n const walk = (node: Parser.SyntaxNode): void => {\n if (node.type === 'call_expression') {\n const fn = node.childForFieldName('function')\n if (fn?.type === 'identifier' && TABLE_BUILDERS.has(fn.text)) {\n const args = node.childForFieldName('arguments')\n const argNodes = args ? namedChildren(args) : []\n const tableName = stringLiteralText(argNodes[0] ?? null)\n const obj = argNodes[1]\n if (tableName && obj?.type === 'object' && !seen.has(tableName)) {\n seen.add(tableName)\n const columns = columnsFromObject(obj)\n const line = node.startPosition.row + 1\n out.push({\n infraId: infraId('sql-table', tableName),\n name: tableName,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'structural',\n columns,\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n }\n }\n for (const c of namedChildren(node)) walk(c)\n }\n\n walk(tree.rootNode)\n return out\n}\n\n// ── foreign-key table→table references (ADR-161) ─────────────────────────────\n//\n// Drizzle declares a FK inline on the column: `.references(() => users.id)`. The\n// referenced `users` is the schema *variable*, so the DB parent-table name is the\n// variable's own `pgTable('...')` first-string argument, NOT the variable name —\n// `export const appUsers = pgTable('app_users', …)` referenced as\n// `.references(() => appUsers.id)` is a FK onto `app_users`. Resolving through the\n// in-file variable→table map keeps the parent at DB-name fidelity, the same\n// fusion key the column/table read (ADR-157 §3) and OTLP already land on. A\n// reference to a table not declared in this file (imported from another schema\n// module) resolves to nothing and is left unclaimed — cross-file FK resolution is\n// a follow-on, not a guess.\n\ninterface DrizzleTable {\n tableName: string\n object: Parser.SyntaxNode | null\n}\n\n// Climb from a table-builder call to the `const <var> = …` it is bound to, so the\n// variable a `.references()` names can be resolved back to the DB table. Returns\n// the variable name, or null when the call is not a simple named binding.\nfunction enclosingVarName(call: Parser.SyntaxNode): string | null {\n let node: Parser.SyntaxNode | null = call\n while (node?.parent) {\n const parent = node.parent\n if (parent.type === 'variable_declarator') {\n const name = parent.childForFieldName('name')\n return name?.type === 'identifier' ? name.text : null\n }\n // Climb through a wrapping call/member (`pgTable(...).$type<X>()`).\n if (parent.type === 'call_expression' || parent.type === 'member_expression') {\n node = parent\n continue\n }\n return null\n }\n return null\n}\n\n// Every table-builder declaration in the file: the DB table name (first string\n// argument) and its column object, plus a variable→table map for FK resolution.\nfunction collectDrizzleTables(root: Parser.SyntaxNode): {\n tables: DrizzleTable[]\n varToTable: Map<string, string>\n} {\n const tables: DrizzleTable[] = []\n const varToTable = new Map<string, string>()\n const walk = (node: Parser.SyntaxNode): void => {\n if (node.type === 'call_expression') {\n const fn = node.childForFieldName('function')\n if (fn?.type === 'identifier' && TABLE_BUILDERS.has(fn.text)) {\n const args = node.childForFieldName('arguments')\n const argNodes = args ? namedChildren(args) : []\n const tableName = stringLiteralText(argNodes[0] ?? null)\n const obj = argNodes[1]?.type === 'object' ? argNodes[1] : null\n if (tableName) {\n tables.push({ tableName, object: obj })\n const varName = enclosingVarName(node)\n if (varName) varToTable.set(varName, tableName)\n }\n }\n }\n for (const c of namedChildren(node)) walk(c)\n }\n walk(root)\n return { tables, varToTable }\n}\n\n// The referenced table *variable* of a `.references(() => X.col)` call, or null\n// when the target is not a plain `X.col` member on a variable — a computed or\n// dynamic reference (`.references(() => makeRef().id)`) is left unclaimed.\nfunction referencesTargetVar(call: Parser.SyntaxNode): string | null {\n const fn = call.childForFieldName('function')\n if (fn?.type !== 'member_expression') return null\n if (fn.childForFieldName('property')?.text !== 'references') return null\n const args = call.childForFieldName('arguments')\n const first = args ? (namedChildren(args)[0] ?? null) : null\n if (first?.type !== 'arrow_function') return null\n const body = first.childForFieldName('body')\n if (body?.type !== 'member_expression') return null\n const obj = body.childForFieldName('object')\n return obj?.type === 'identifier' ? obj.text : null\n}\n\nexport function drizzleForeignKeys(file: SourceFile, serviceDir: string): TableReference[] {\n if (!DRIZZLE_IMPORT_RE.test(file.content)) return []\n const tree = parseSource(parserForExt(path.extname(file.path)), file.content)\n const { tables, varToTable } = collectDrizzleTables(tree.rootNode)\n const out: TableReference[] = []\n const seen = new Set<string>()\n\n for (const table of tables) {\n if (!table.object) continue\n const walk = (node: Parser.SyntaxNode): void => {\n if (node.type === 'call_expression') {\n const targetVar = referencesTargetVar(node)\n const parentTable = targetVar ? varToTable.get(targetVar) : undefined\n if (parentTable) {\n const key = `${table.tableName}->${parentTable}`\n if (!seen.has(key)) {\n seen.add(key)\n const line = node.startPosition.row + 1\n out.push({\n childTable: table.tableName,\n parentTable,\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n }\n }\n for (const c of namedChildren(node)) walk(c)\n }\n walk(table.object)\n }\n\n return out\n}\n","import path from 'node:path'\nimport { infraId } from '@neat.is/types'\nimport { findFirst, readIfExists } from '../databases/shared.js'\nimport { snippet, type ExternalEndpoint, type SourceFile, type TableReference } from './shared.js'\n\n// Prisma schema table + column recognizer (ADR-157 §3 columns, the named\n// follow-on the ADR reserves after Drizzle). The declared side of column grain\n// for Prisma: it reads each `model` block in `schema.prisma` and emits the table\n// that model maps to AND the scalar columns it declares, so a production query —\n// whose OBSERVED table + columns NEAT recovers by parsing `db.statement`\n// (ADR-152 / ADR-157 §2) — has a static twin to fuse onto at\n// `infra:sql-table:<name>`.\n//\n// model Article {\n// id Int @id @default(autoincrement())\n// title String\n// body String @map(\"article_body\")\n// authorId Int\n// author User @relation(fields: [authorId], references: [id])\n// comments Comment[]\n// @@map(\"articles\")\n// }\n//\n// The fidelity rules are the load-bearing part (ADR-157 §3 — the fusion key the\n// running query carries is the DATABASE name, not the code name):\n// - TABLE name = the model name verbatim (Prisma's default is PascalCase, NOT\n// snake-cased), or the `@@map(\"name\")` value when the model declares one.\n// - COLUMN name = the field name verbatim, or the `@map(\"db_col\")` value when\n// the field declares one. Prisma does NOT snake_case by default, so `authorId`\n// is the column, not `author_id`.\n// A scalar field (`Int String Boolean DateTime Float Decimal BigInt Bytes Json`,\n// or an enum-typed field) is a column — foreign-key scalars like `authorId Int`\n// included. A relation field is NOT a column and is skipped: a field whose base\n// type is another declared model (`author User`, `comments Comment[]`) or that\n// carries `@relation(...)`. `schema.prisma` is not JS, so it is read as text with\n// a small block scanner (the same read-polyglot-as-data discipline `proto.ts`\n// follows). A field whose type is neither a scalar, a declared enum, nor a\n// declared model is left unclaimed rather than guessed.\n\nconst SCALAR_TYPES = new Set([\n 'Int',\n 'String',\n 'Boolean',\n 'DateTime',\n 'Float',\n 'Decimal',\n 'BigInt',\n 'Bytes',\n 'Json',\n])\n\n// Drop a trailing `//` line comment, respecting `\"...\"` strings so a `//` inside\n// a URL default (`@default(\"https://…\")`) is not mistaken for a comment.\nfunction stripLineComment(line: string): string {\n let inStr = false\n for (let i = 0; i < line.length; i++) {\n const ch = line[i]\n if (ch === '\"' && line[i - 1] !== '\\\\') inStr = !inStr\n else if (!inStr && ch === '/' && line[i + 1] === '/') return line.slice(0, i)\n }\n return line\n}\n\n// Net `{` minus `}` on a line, ignoring comments and string contents so a brace\n// inside a default value can't unbalance the block scan. Prisma blocks don't\n// nest, but counting keeps the scan robust to odd formatting.\nfunction netBraces(line: string): number {\n const s = stripLineComment(line).replace(/\"(\\\\.|[^\"\\\\])*\"/g, '')\n let n = 0\n for (const ch of s) {\n if (ch === '{') n++\n else if (ch === '}') n--\n }\n return n\n}\n\ninterface ModelBlock {\n kind: string\n tableName: string\n startLine: number\n columns: string[]\n seenCol: Set<string>\n}\n\n// Parse a `schema.prisma`'s model blocks into `sql-table` endpoints carrying the\n// declared columns at database-name fidelity. Pure over the file content so a\n// test can drive it without touching the filesystem, mirroring\n// `drizzleEndpointsFromFile`.\nexport function prismaColumnsFromSchema(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const content = file.content\n if (!/\\bmodel\\s+[A-Za-z_]\\w*\\s*\\{/.test(content)) return []\n\n const lines = content.split('\\n')\n\n // First pass: the declared model + enum names. A field whose base type is a\n // model name is a relation (skipped); an enum-typed field is a column. Names\n // are collected up front so a relation to a model declared later in the file\n // still resolves.\n const modelNames = new Set<string>()\n const enumNames = new Set<string>()\n for (const line of lines) {\n const m = line.match(/^\\s*model\\s+([A-Za-z_]\\w*)\\s*\\{/)\n if (m) modelNames.add(m[1]!)\n const e = line.match(/^\\s*enum\\s+([A-Za-z_]\\w*)\\s*\\{/)\n if (e) enumNames.add(e[1]!)\n }\n\n const out: ExternalEndpoint[] = []\n const seenTable = new Set<string>()\n\n const finalize = (b: ModelBlock): void => {\n if (seenTable.has(b.tableName)) return\n seenTable.add(b.tableName)\n out.push({\n infraId: infraId('sql-table', b.tableName),\n name: b.tableName,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'structural',\n columns: b.columns,\n evidence: {\n file: path.relative(serviceDir, file.path),\n line: b.startLine,\n snippet: snippet(content, b.startLine),\n },\n })\n }\n\n // Second pass: a line-based block scan. Only `model` blocks contribute\n // columns; `datasource` / `generator` / `enum` / `type` blocks are consumed so\n // their closing brace doesn't leak, but produce nothing here.\n let current: ModelBlock | null = null\n let depth = 0\n\n for (let i = 0; i < lines.length; i++) {\n const raw = lines[i]!\n const lineNo = i + 1\n\n if (current === null) {\n const header = raw.match(/^\\s*(model|enum|datasource|generator|type)\\s+([A-Za-z_]\\w*)\\b/)\n if (header && raw.includes('{')) {\n current = {\n kind: header[1]!,\n tableName: header[2]!,\n startLine: lineNo,\n columns: [],\n seenCol: new Set(),\n }\n depth = netBraces(raw)\n if (depth <= 0) {\n if (current.kind === 'model') finalize(current)\n current = null\n }\n }\n continue\n }\n\n depth += netBraces(raw)\n if (depth <= 0) {\n if (current.kind === 'model') finalize(current)\n current = null\n continue\n }\n if (current.kind !== 'model') continue\n\n const trimmed = stripLineComment(raw).trim()\n if (!trimmed) continue\n if (trimmed.startsWith('@@')) {\n const map = trimmed.match(/@@map\\(\\s*\"([^\"]+)\"\\s*\\)/)\n if (map) current.tableName = map[1]!\n continue\n }\n\n // `fieldName BaseType[...] attributes…` — the base type is the second token,\n // with a trailing `[]` array marker stripped by the alternation.\n const fm = trimmed.match(/^([A-Za-z_]\\w*)\\s+([A-Za-z_]\\w*)/)\n if (!fm) continue\n const fieldName = fm[1]!\n const baseType = fm[2]!\n\n // Relation fields are not columns: an `@relation(...)` field, or a field whose\n // base type is another declared model (covers `author User`, `Tag[]`, `Comment[]`).\n if (/@relation\\b/.test(trimmed) || modelNames.has(baseType)) continue\n // A column is a scalar-typed or enum-typed field. Anything else (a composite\n // type, `Unsupported(...)`, a cross-file model) is left unclaimed.\n if (!enumNames.has(baseType) && !SCALAR_TYPES.has(baseType)) continue\n\n const mapM = trimmed.match(/(?<!@)@map\\(\\s*\"([^\"]+)\"\\s*\\)/)\n const columnName = mapM ? mapM[1]! : fieldName\n if (!current.seenCol.has(columnName)) {\n current.seenCol.add(columnName)\n current.columns.push(columnName)\n }\n }\n\n // An unterminated final model (no trailing `}` at EOF) is still emitted.\n if (current && current.kind === 'model') finalize(current)\n\n return out\n}\n\n// Per-service pass: locate the service's `schema.prisma` and read its declared\n// columns. `schema.prisma` is not a source file the walker loads (its extension\n// is not in SERVICE_FILE_EXTENSIONS), so — unlike the JS producers that scan an\n// already-loaded file — this reads the schema directly, the same standard\n// location `databases/prisma.ts` reads for the datasource.\nexport async function prismaColumnEndpoints(serviceDir: string): Promise<ExternalEndpoint[]> {\n const schemaPath = await findFirst(serviceDir, [\n path.join('prisma', 'schema.prisma'),\n 'schema.prisma',\n ])\n if (!schemaPath) return []\n const content = await readIfExists(schemaPath)\n if (!content) return []\n return prismaColumnsFromSchema({ path: schemaPath, content }, serviceDir)\n}\n\n// ── foreign-key table→table references (ADR-161) ─────────────────────────────\n//\n// Prisma declares a FK on the relation field that carries the scalar columns:\n// model Post {\n// authorId Int\n// author User @relation(fields: [authorId], references: [id])\n// }\n// The `fields:` side is the child (it holds the FK column); the field's base type\n// is the parent model. The FK edge is `child-table ──REFERENCES──▶ parent-table`,\n// both at DB-name fidelity — the model's `@@map` name where given, the model name\n// verbatim otherwise (ADR-157 §3). The back-relation side (`posts Post[]`, no\n// `fields:`) declares no FK and mints nothing, so the edge is minted once, in the\n// correct direction. A relation whose base type is not a declared model, or a\n// relation without `fields:`, resolves to no parent and is left unclaimed.\n\n// Model name → the DB table it maps to (the `@@map` value, else the model name\n// verbatim). Built up front so a relation to a model declared later still resolves.\nfunction buildModelTableMap(lines: string[]): Map<string, string> {\n const map = new Map<string, string>()\n let current: { model: string; table: string } | null = null\n let depth = 0\n for (const raw of lines) {\n if (current === null) {\n const header = raw.match(/^\\s*model\\s+([A-Za-z_]\\w*)\\b/)\n if (header && raw.includes('{')) {\n current = { model: header[1]!, table: header[1]! }\n depth = netBraces(raw)\n if (depth <= 0) {\n map.set(current.model, current.table)\n current = null\n }\n }\n continue\n }\n depth += netBraces(raw)\n const trimmed = stripLineComment(raw).trim()\n if (trimmed.startsWith('@@')) {\n const m = trimmed.match(/@@map\\(\\s*\"([^\"]+)\"\\s*\\)/)\n if (m) current.table = m[1]!\n }\n if (depth <= 0) {\n map.set(current.model, current.table)\n current = null\n }\n }\n if (current) map.set(current.model, current.table)\n return map\n}\n\nexport function prismaForeignKeysFromSchema(\n file: SourceFile,\n serviceDir: string,\n): TableReference[] {\n const content = file.content\n if (!/\\bmodel\\s+[A-Za-z_]\\w*\\s*\\{/.test(content)) return []\n const lines = content.split('\\n')\n const modelToTable = buildModelTableMap(lines)\n\n const out: TableReference[] = []\n const seen = new Set<string>()\n let current: { table: string } | null = null\n let depth = 0\n\n for (let i = 0; i < lines.length; i++) {\n const raw = lines[i]!\n const lineNo = i + 1\n\n if (current === null) {\n const header = raw.match(/^\\s*model\\s+([A-Za-z_]\\w*)\\b/)\n if (header && raw.includes('{')) {\n current = { table: modelToTable.get(header[1]!) ?? header[1]! }\n depth = netBraces(raw)\n if (depth <= 0) current = null\n }\n continue\n }\n\n depth += netBraces(raw)\n const closing = depth <= 0\n const trimmed = stripLineComment(raw).trim()\n\n // A relation field holding the FK: `author User @relation(fields: [authorId],\n // references: [id])`. Only the `fields:` side mints an edge — the back-relation\n // (`posts Post[]`, no `fields:`) is skipped, so the direction is unambiguous.\n if (trimmed && !trimmed.startsWith('@@') && !trimmed.startsWith('}')) {\n const fm = trimmed.match(/^([A-Za-z_]\\w*)\\s+([A-Za-z_]\\w*)/)\n if (fm && /@relation\\b[^)]*\\bfields\\s*:/.test(trimmed)) {\n const parentTable = modelToTable.get(fm[2]!)\n if (parentTable) {\n const key = `${current.table}->${parentTable}`\n if (!seen.has(key)) {\n seen.add(key)\n out.push({\n childTable: current.table,\n parentTable,\n evidence: {\n file: path.relative(serviceDir, file.path),\n line: lineNo,\n snippet: snippet(content, lineNo),\n },\n })\n }\n }\n }\n }\n\n if (closing) current = null\n }\n\n return out\n}\n\n// Per-service pass: locate `schema.prisma` and read its declared foreign keys.\n// Mirrors `prismaColumnEndpoints` — the schema is not a walked source file, so it\n// is read directly from the standard location.\nexport async function prismaForeignKeys(serviceDir: string): Promise<TableReference[]> {\n const schemaPath = await findFirst(serviceDir, [\n path.join('prisma', 'schema.prisma'),\n 'schema.prisma',\n ])\n if (!schemaPath) return []\n const content = await readIfExists(schemaPath)\n if (!content) return []\n return prismaForeignKeysFromSchema({ path: schemaPath, content }, serviceDir)\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport Ruby from 'tree-sitter-ruby'\nimport { infraId } from '@neat.is/types'\nimport { snippet, type ExternalEndpoint, type SourceFile, type TableReference } from './shared.js'\n\n// Ruby/Rails ActiveRecord data-axis recognizer (ADR-174), the second rung of the\n// Ruby pilot after ADR-173's routes. Rails declares its schema twice — literally\n// in `db/schema.rb` and behaviourally in the ActiveRecord models — and both feed\n// the SAME `sql-table` nodes, `ColumnAttr` columns (ADR-157), and `REFERENCES`\n// edges (ADR-161) Prisma and Drizzle already produce, so the reasoning core learns\n// nothing Rails-specific (calls/prisma.ts is the closest analog).\n//\n// ActiveRecord::Schema[7.1].define do\n// create_table \"orders\", force: :cascade do |t|\n// t.string \"name\"\n// t.integer \"qty\"\n// t.references :user, foreign_key: true # -> user_id column + FK to users\n// t.timestamps # -> created_at, updated_at\n// end\n// add_foreign_key \"line_items\", \"orders\" # -> line_items REFERENCES orders\n// end\n//\n// The fidelity rule (ADR-157 §3): the table/column/FK names in schema.rb are\n// LITERAL — the exact strings the database uses and a query's `db.statement`\n// carries at runtime — so schema.rb is the anchor and needs no pluralization. The\n// models add what the schema can't name on its own: the class↔table link and the\n// association graph. There the ActiveSupport pluralizer is a fallback only, since\n// a wrong guess there is a missed edge, never a wrong table node.\n\nconst AR_SCHEMA_RE = /ActiveRecord::Schema/\n\nconst PARSE_CHUNK = 16384\n\nfunction makeRubyParser(): Parser {\n const p = new Parser()\n p.setLanguage(Ruby)\n return p\n}\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nfunction walk(node: Parser.SyntaxNode, visit: (n: Parser.SyntaxNode) => void): void {\n visit(node)\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c) walk(c, visit)\n }\n}\n\n// The interior text of a Ruby string literal, or a symbol's name without its\n// leading colon (`:orders` → 'orders'). Null for an interpolated string — a\n// schema name is always a static literal. Mirrors routes.ts's `rubyLiteral`.\nfunction rubyLiteral(node: Parser.SyntaxNode | null | undefined): string | null {\n if (!node) return null\n if (node.type === 'simple_symbol') return node.text.replace(/^:/, '')\n if (node.type === 'string') {\n let text = ''\n let sawContent = false\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (!child) continue\n if (child.type === 'string_content' || child.type === 'escape_sequence') {\n text += child.text\n sawContent = true\n } else {\n return null // interpolation or anything non-literal\n }\n }\n return sawContent ? text : '' // an empty string literal has no content child\n }\n return null\n}\n\nfunction rubyArgs(call: Parser.SyntaxNode): Parser.SyntaxNode | null {\n return call.childForFieldName('arguments')\n}\n\n// The positional (non-`pair`) arguments of a call, in order.\nfunction rubyPositional(args: Parser.SyntaxNode | null): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n if (!args) return out\n for (let i = 0; i < args.namedChildCount; i++) {\n const c = args.namedChild(i)\n if (c && c.type !== 'pair') out.push(c)\n }\n return out\n}\n\n// The value node of a keyword argument `key: …` — a `pair` whose key is a\n// `hash_key_symbol` with the given name. Handles both an inline argument-list\n// pair (`create_table \"x\", id: false`) and a `hash` literal's own pairs\n// (`foreign_key: { to_table: :y }`).\nfunction rubyKwarg(container: Parser.SyntaxNode | null, key: string): Parser.SyntaxNode | null {\n if (!container) return null\n for (let i = 0; i < container.namedChildCount; i++) {\n const pair = container.namedChild(i)\n if (!pair || pair.type !== 'pair') continue\n const k = pair.childForFieldName('key')\n if (k?.type === 'hash_key_symbol' && k.text === key) return pair.childForFieldName('value')\n }\n return null\n}\n\n// The `call` statements at the top level of a `do … end` block or a class\n// `body_statement`. tree-sitter-ruby wraps multiple statements in a\n// `body_statement`; fall back to the container's own direct children for the\n// single-statement shape. Mirrors routes.ts's `rubyBlockCalls`.\nfunction rubyBodyCalls(container: Parser.SyntaxNode | null): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n if (!container) return out\n const pushCalls = (n: Parser.SyntaxNode): void => {\n for (let i = 0; i < n.namedChildCount; i++) {\n const c = n.namedChild(i)\n if (c?.type === 'call') out.push(c)\n }\n }\n let sawBody = false\n for (let i = 0; i < container.namedChildCount; i++) {\n const c = container.namedChild(i)\n if (c?.type === 'body_statement') {\n pushCalls(c)\n sawBody = true\n }\n }\n if (!sawBody) pushCalls(container)\n return out\n}\n\n// ── inflection (model / association fallback only) ───────────────────────────\n//\n// schema.rb names are literal and never routed through here. These reproduce the\n// common-case ActiveSupport rules for the model→table fallback and the singular\n// association→table derivation. Irregulars (person/people, child/children) and a\n// project's custom inflections are a documented refinement (ADR-174) — since the\n// schema.rb literal is the anchor, a wrong guess here is a missed corroborating\n// edge, not a wrong node.\n\n// `Admin::User` → `User`.\nfunction demodulize(name: string): string {\n const i = name.lastIndexOf('::')\n return i >= 0 ? name.slice(i + 2) : name\n}\n\n// ActiveSupport underscore: CamelCase → snake_case, keeping acronym runs together\n// (`APIKey` → `api_key`, `LineItem` → `line_item`).\nfunction underscore(name: string): string {\n return name\n .replace(/([A-Z\\d]+)([A-Z][a-z])/g, '$1_$2')\n .replace(/([a-z\\d])([A-Z])/g, '$1_$2')\n .replace(/-/g, '_')\n .toLowerCase()\n}\n\n// Common-case English pluralizer — the rules ActiveSupport's default inflections\n// ship for the frequent endings.\nfunction pluralize(word: string): string {\n if (/[^aeiou]y$/i.test(word)) return word.replace(/y$/i, 'ies')\n if (/(s|x|z|ch|sh)$/i.test(word)) return word + 'es'\n return word + 's'\n}\n\n// class name → table name (ActiveSupport `tableize`, default `table_name`):\n// pluralize(underscore(demodulize(name))). `Order` → orders, `LineItem` →\n// line_items, `Admin::User` → users.\nfunction tableize(className: string): string {\n return pluralize(underscore(demodulize(className)))\n}\n\n// ── db/schema.rb: create_table → sql-table + columns ─────────────────────────\n\n// The ActiveRecord migration column-type methods that declare a column named by\n// their first string/symbol argument. `t.column \"name\", \"type\"` (the generic\n// form) is here too — its first positional is still the name. Non-column table\n// methods (`t.index`, `t.check_constraint`, `t.foreign_key`) are absent, so they\n// contribute nothing — the bounded-set discipline Drizzle's TABLE_BUILDERS and\n// SQLAlchemy's COLUMN_BUILDERS follow. `references`/`belongs_to`/`timestamps` are\n// handled specially below (they expand to `<name>_id` / created_at+updated_at).\nconst AR_COLUMN_TYPES = new Set([\n 'string', 'text', 'integer', 'bigint', 'float', 'decimal', 'numeric',\n 'datetime', 'timestamp', 'timestamptz', 'time', 'date', 'binary', 'boolean',\n 'json', 'jsonb', 'uuid', 'hstore', 'citext', 'inet', 'cidr', 'macaddr',\n 'money', 'xml', 'tsvector', 'ltree', 'interval', 'oid', 'bit', 'blob',\n 'int4range', 'int8range', 'numrange', 'tsrange', 'tstzrange', 'daterange',\n 'point', 'line', 'lseg', 'box', 'path', 'polygon', 'circle', 'enum',\n 'primary_key', 'virtual', 'column',\n])\n\nconst AR_REFERENCE_METHODS = new Set(['references', 'belongs_to'])\n\n// The receiver-and-method of a `t.<method> …` block call. Returns the method name\n// (`string`, `references`, `timestamps`, …) regardless of the block variable name.\nfunction blockCallMethod(call: Parser.SyntaxNode): string | null {\n return call.childForFieldName('method')?.text ?? null\n}\n\ninterface SchemaTable {\n name: string\n line: number\n columns: string[]\n}\n\n// Read a single `create_table \"orders\", … do |t| … end` call into its table name,\n// the implicit primary key, and the columns its block declares.\nfunction readCreateTable(call: Parser.SyntaxNode): SchemaTable | null {\n const args = rubyArgs(call)\n const name = rubyLiteral(rubyPositional(args)[0])\n if (!name) return null // computed / interpolated table name — never guessed\n const line = call.startPosition.row + 1\n\n const columns: string[] = []\n const seen = new Set<string>()\n const add = (col: string | null): void => {\n if (col && !seen.has(col)) {\n seen.add(col)\n columns.push(col)\n }\n }\n\n // Implicit primary key, unless `id: false`. A `primary_key: \"uuid\"` renames it.\n const idOpt = rubyKwarg(args, 'id')\n if (!idOpt || idOpt.type !== 'false') {\n add(rubyLiteral(rubyKwarg(args, 'primary_key')) ?? 'id')\n }\n\n for (const bc of rubyBodyCalls(call.childForFieldName('block'))) {\n const method = blockCallMethod(bc)\n if (!method) continue\n const bArgs = rubyArgs(bc)\n if (method === 'timestamps') {\n add('created_at')\n add('updated_at')\n } else if (AR_REFERENCE_METHODS.has(method)) {\n const refName = rubyLiteral(rubyPositional(bArgs)[0])\n if (!refName) continue\n add(`${refName}_id`)\n // A polymorphic reference also declares a `<name>_type` discriminator.\n const poly = rubyKwarg(bArgs, 'polymorphic')\n if (poly && poly.type === 'true') add(`${refName}_type`)\n } else if (AR_COLUMN_TYPES.has(method)) {\n add(rubyLiteral(rubyPositional(bArgs)[0]))\n }\n // Anything else (index, check_constraint, …) declares no column.\n }\n\n return { name, line, columns }\n}\n\n// db/schema.rb tables + columns. Gated on the `ActiveRecord::Schema` marker so it\n// is inert on every other `.rb` file (the import-gate discipline Drizzle and\n// SQLAlchemy follow). Emits one `sql-table` endpoint per `create_table`, carrying\n// the literal column names — the orchestrator (calls/index.ts) mints the node and\n// folds the columns onto it as EXTRACTED `ColumnAttr`s (ADR-157 §3).\nexport function railsSchemaEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n if (!AR_SCHEMA_RE.test(file.content)) return []\n const tree = parseSource(makeRubyParser(), file.content)\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n\n walk(tree.rootNode, (node) => {\n if (node.type !== 'call') return\n if (blockCallMethod(node) !== 'create_table') return\n const table = readCreateTable(node)\n if (!table || seen.has(table.name)) return\n seen.add(table.name)\n out.push({\n infraId: infraId('sql-table', table.name),\n name: table.name,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'structural',\n ...(table.columns.length > 0 ? { columns: table.columns } : {}),\n evidence: {\n file: path.relative(serviceDir, file.path),\n line: table.line,\n snippet: snippet(file.content, table.line),\n },\n })\n })\n\n return out\n}\n\n// db/schema.rb foreign keys → REFERENCES table→table edges. Two literal sources,\n// both preferred over any model inference: an inline `t.references :user,\n// foreign_key: true` inside a create_table (parent pluralized, or `to_table:`\n// verbatim when given) and a top-level `add_foreign_key \"orders\", \"users\"` (both\n// names literal). A polymorphic reference names no single parent and is left\n// unclaimed. `column:` / `primary_key:` on `add_foreign_key` don't change the\n// table-grain edge and are ignored.\nexport function railsSchemaForeignKeys(\n file: SourceFile,\n serviceDir: string,\n): TableReference[] {\n if (!AR_SCHEMA_RE.test(file.content)) return []\n const tree = parseSource(makeRubyParser(), file.content)\n const out: TableReference[] = []\n const seen = new Set<string>()\n\n const emit = (childTable: string, parentTable: string, line: number): void => {\n if (childTable === parentTable) return // self-loop, dropped by the graph anyway\n const key = `${childTable}->${parentTable}`\n if (seen.has(key)) return\n seen.add(key)\n out.push({\n childTable,\n parentTable,\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n\n walk(tree.rootNode, (node) => {\n if (node.type !== 'call') return\n const method = blockCallMethod(node)\n\n // Top-level `add_foreign_key \"child\", \"parent\", …` — both names literal.\n if (method === 'add_foreign_key') {\n const args = rubyArgs(node)\n const pos = rubyPositional(args)\n const child = rubyLiteral(pos[0])\n const parent = rubyLiteral(pos[1])\n if (child && parent) emit(child, parent, node.startPosition.row + 1)\n return\n }\n\n // Inline `t.references :user, foreign_key: true` inside a create_table.\n if (method !== 'create_table') return\n const table = readCreateTable(node)\n if (!table) return\n for (const bc of rubyBodyCalls(node.childForFieldName('block'))) {\n if (!AR_REFERENCE_METHODS.has(blockCallMethod(bc) ?? '')) continue\n const bArgs = rubyArgs(bc)\n const refName = rubyLiteral(rubyPositional(bArgs)[0])\n if (!refName) continue\n const poly = rubyKwarg(bArgs, 'polymorphic')\n if (poly && poly.type === 'true') continue // no single parent to reference\n const fk = rubyKwarg(bArgs, 'foreign_key')\n if (!fk || fk.type === 'false') continue // no `foreign_key: true` → no DB FK\n // `foreign_key: { to_table: :accounts }` overrides the pluralized default.\n const toTable = fk.type === 'hash' ? rubyLiteral(rubyKwarg(fk, 'to_table')) : null\n const parent = toTable ?? pluralize(refName)\n emit(table.name, parent, bc.startPosition.row + 1)\n }\n })\n\n return out\n}\n\n// ── app/models/*.rb: class↔table link + associations ─────────────────────────\n\n// The superclass constant of a `class X < Y` — `Y`'s text, or null when the class\n// has no superclass. Reads the `superclass` node's constant / scope_resolution.\nfunction superclassName(cls: Parser.SyntaxNode): string | null {\n const sup = cls.childForFieldName('superclass')\n if (!sup) return null\n for (let i = 0; i < sup.namedChildCount; i++) {\n const c = sup.namedChild(i)\n if (c && (c.type === 'constant' || c.type === 'scope_resolution')) return c.text\n }\n return null\n}\n\n// Is this class an ActiveRecord model? `< ApplicationRecord` (the Rails ≥ 5 base)\n// or `< ActiveRecord::Base` (legacy), including a namespaced `Foo::ApplicationRecord`.\nfunction isActiveRecordModel(cls: Parser.SyntaxNode): boolean {\n const sup = superclassName(cls)\n if (!sup) return false\n return sup === 'ApplicationRecord' || sup.endsWith('::ApplicationRecord') || sup === 'ActiveRecord::Base'\n}\n\n// The table a model class maps to: an explicit `self.table_name = \"x\"` when set,\n// else the ActiveSupport pluralisation of the underscored class name (`Order` →\n// orders). schema.rb literals are the anchor; this only names the link.\nfunction modelTable(cls: Parser.SyntaxNode): string | null {\n const nameNode = cls.childForFieldName('name')\n if (!nameNode) return null\n const body = cls.childForFieldName('body')\n if (body) {\n for (let i = 0; i < body.namedChildCount; i++) {\n const stmt = body.namedChild(i)\n if (stmt?.type !== 'assignment') continue\n const left = stmt.childForFieldName('left')\n if (left?.text !== 'self.table_name') continue\n const explicit = rubyLiteral(stmt.childForFieldName('right'))\n if (explicit) return explicit\n }\n }\n return tableize(nameNode.text)\n}\n\n// app/models/*.rb: the model→table link. Emits a `sql-table` endpoint so the\n// model file gets a `file ──CALLS──▶ sql-table` edge (the SQLAlchemy model-file\n// pattern) — no columns, since ActiveRecord reads those from the DB, and schema.rb\n// is the column authority.\nexport function railsModelEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const tree = parseSource(makeRubyParser(), file.content)\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n\n walk(tree.rootNode, (node) => {\n if (node.type !== 'class' || !isActiveRecordModel(node)) return\n const table = modelTable(node)\n if (!table || seen.has(table)) return\n seen.add(table)\n const line = node.startPosition.row + 1\n out.push({\n infraId: infraId('sql-table', table),\n name: table,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n })\n\n return out\n}\n\nconst AR_ASSOCIATIONS = new Set(['belongs_to', 'has_many', 'has_one'])\n\n// app/models/*.rb associations → REFERENCES edges, deduped against the schema.rb\n// FKs at the graph level (table-edges.ts guards every write with `hasEdge`, and\n// the schema.rb refs are added first). The FK lives on the table that holds the\n// `<name>_id` column, so direction depends on the macro:\n// belongs_to :user -> this table REFERENCES the association's table (users)\n// has_many :line_items -> line_items REFERENCES this table (FK on the other side)\n// has_one :profile -> profiles REFERENCES this table\n// `class_name:` retargets the association's class (hence table); `through:` (a\n// join through a third model) and `has_and_belongs_to_many` are deferred.\nexport function railsModelForeignKeys(\n file: SourceFile,\n serviceDir: string,\n): TableReference[] {\n const tree = parseSource(makeRubyParser(), file.content)\n const out: TableReference[] = []\n const seen = new Set<string>()\n\n walk(tree.rootNode, (node) => {\n if (node.type !== 'class' || !isActiveRecordModel(node)) return\n const thisTable = modelTable(node)\n if (!thisTable) return\n\n for (const call of rubyBodyCalls(node.childForFieldName('body'))) {\n const macro = blockCallMethod(call)\n if (!macro || !AR_ASSOCIATIONS.has(macro)) continue\n const args = rubyArgs(call)\n const assoc = rubyLiteral(rubyPositional(args)[0])\n if (!assoc) continue\n if (rubyKwarg(args, 'through')) continue // has_many/has_one :through — deferred\n\n // The association's own table: `class_name:` when given, else the\n // conventional derivation. A has_many name is already the plural table\n // name; belongs_to / has_one names are singular and pluralize.\n const className = rubyLiteral(rubyKwarg(args, 'class_name'))\n const assocTable = className\n ? tableize(className)\n : macro === 'has_many'\n ? assoc\n : pluralize(assoc)\n\n const childTable = macro === 'belongs_to' ? thisTable : assocTable\n const parentTable = macro === 'belongs_to' ? assocTable : thisTable\n if (childTable === parentTable) continue\n const key = `${childTable}->${parentTable}`\n if (seen.has(key)) continue\n seen.add(key)\n const line = call.startPosition.row + 1\n out.push({\n childTable,\n parentTable,\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n })\n\n return out\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport Php from 'tree-sitter-php'\nimport { infraId } from '@neat.is/types'\nimport { snippet, type ExternalEndpoint, type SourceFile, type TableReference } from './shared.js'\n\n// PHP/Laravel data-axis recognizer (ADR-178), the second rung of the PHP pilot\n// after ADR-177's routes. Laravel declares its schema in two places — literally\n// in `database/migrations/*.php` and behaviourally in the Eloquent models — and\n// both feed the SAME `sql-table` nodes, `ColumnAttr` columns (ADR-157), and\n// `REFERENCES` edges (ADR-161) Prisma, Drizzle, and Rails' schema.rb already\n// produce, so the reasoning core learns nothing Laravel-specific\n// (calls/activerecord.ts is the closest analog — a migration is Rails' schema.rb,\n// an Eloquent model is an ActiveRecord model).\n//\n// Schema::create('orders', function (Blueprint $table) {\n// $table->id(); // -> id\n// $table->string('code'); // -> code\n// $table->foreignId('user_id')->constrained(); // -> user_id column + FK to users\n// $table->timestamps(); // -> created_at, updated_at\n// });\n//\n// class Order extends Model { // -> maps to the orders table\n// public function user() { return $this->belongsTo(User::class); } // orders -> users\n// }\n//\n// The fidelity rule (ADR-157 §3): the table/column names inside `Schema::create`\n// are LITERAL — the exact strings the database uses and a query's `db.statement`\n// carries at runtime — so the migration is the anchor and needs no pluralization.\n// The models add what the migration can't name on its own: the class↔table link\n// and the relation graph. There the snake_case pluralizer is a fallback only,\n// since a wrong guess there is a missed corroborating edge, never a wrong table\n// node (the migration literal anchors the node either way).\n\n// Cheap per-file gate — inert on every `.php` file that declares no schema, the\n// import-gate discipline the other data-axis readers follow. A migration always\n// names `Schema::create` / `Schema::table` (or the `createIfNotExists` variant),\n// including the fully-qualified `\\Illuminate\\Support\\Facades\\Schema::create`.\nconst LARAVEL_SCHEMA_RE = /\\bSchema::(create|createIfNotExists|table)\\b/\n\nconst PARSE_CHUNK = 16384\n\nfunction makePhpParser(): Parser {\n const p = new Parser()\n p.setLanguage(Php.php_only)\n return p\n}\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nfunction walk(node: Parser.SyntaxNode, visit: (n: Parser.SyntaxNode) => void): void {\n visit(node)\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c) walk(c, visit)\n }\n}\n\n// ── PHP literal / argument helpers (mirrors routes.ts) ───────────────────────\n\n// The interior text of a PHP single-quoted `string` or double-quoted\n// `encapsed_string`, or null when it carries interpolation (a schema name is a\n// static literal). An empty literal returns ''.\nfunction phpStaticString(node: Parser.SyntaxNode | null | undefined): string | null {\n if (!node) return null\n if (node.type !== 'string' && node.type !== 'encapsed_string') return null\n let text = ''\n let sawContent = false\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (!child) continue\n if (child.type === 'string_content') {\n text += child.text\n sawContent = true\n } else {\n return null // interpolation / escape / anything non-literal\n }\n }\n return sawContent ? text : ''\n}\n\n// The value nodes of a call's positional arguments. tree-sitter-php wraps each in\n// an `argument`; the value is its last named child (a PHP-8 named argument\n// `name: value` carries a `name` field that precedes the value).\nfunction phpArgumentValues(argsNode: Parser.SyntaxNode | null | undefined): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n if (!argsNode) return out\n for (let i = 0; i < argsNode.namedChildCount; i++) {\n const arg = argsNode.namedChild(i)\n if (!arg || arg.type !== 'argument') continue\n const val = arg.namedChild(arg.namedChildCount - 1)\n if (val) out.push(val)\n }\n return out\n}\n\n// The first positional argument as a static string, or null.\nfunction phpFirstString(argsNode: Parser.SyntaxNode | null | undefined): string | null {\n const vals = phpArgumentValues(argsNode)\n return vals.length > 0 ? phpStaticString(vals[0]) : null\n}\n\n// The basename of a class reference — `User::class` (`class_constant_access_expression`),\n// a namespaced `\\App\\Models\\User::class`, or a `'App\\Models\\User'` string literal —\n// stripped of its namespace: `User`. Null for anything else.\nfunction classRefBasename(node: Parser.SyntaxNode | null | undefined): string | null {\n if (!node) return null\n let raw: string | null = null\n if (node.type === 'class_constant_access_expression') {\n // First named child is the class name (`name` or `qualified_name`); the\n // second is the `class` keyword.\n raw = node.namedChild(0)?.text ?? null\n } else if (node.type === 'string' || node.type === 'encapsed_string') {\n // A `'App\\Models\\User'` string carries the namespace as literal backslashes\n // (an escape_sequence child), so read the raw interior rather than the\n // string_content pieces and split on the backslash separator below.\n const t = node.text\n raw = t.length >= 2 ? t.slice(1, -1) : null\n }\n if (raw === null) return null\n const seg = raw.replace(/^\\\\+/, '').split(/\\\\+/)\n return seg[seg.length - 1] || null\n}\n\n// ── inflection (convention / model fallback only) ────────────────────────────\n//\n// Migration `Schema::create` names are literal and never routed through here.\n// These reproduce the common-case Laravel rules for the model→table fallback, the\n// `foreignId('user_id')->constrained()` column→table convention, and the\n// `foreignIdFor(Model::class)` model→table derivation. Irregulars (person/people)\n// are a documented refinement (ADR-178) — since the migration literal is the\n// anchor, a wrong guess here is a missed corroborating edge, not a wrong node.\n\n// StudlyCase → snake_case: `LineItem` → line_item, `APIToken` → api_token.\nfunction snakeCase(name: string): string {\n return name\n .replace(/([A-Z\\d]+)([A-Z][a-z])/g, '$1_$2')\n .replace(/([a-z\\d])([A-Z])/g, '$1_$2')\n .replace(/-/g, '_')\n .toLowerCase()\n}\n\n// Common-case English pluralizer — the frequent endings Laravel's Str::plural\n// covers. Irregulars are the documented refinement.\nfunction pluralize(word: string): string {\n if (/[^aeiou]y$/i.test(word)) return word.replace(/y$/i, 'ies')\n if (/(s|x|z|ch|sh)$/i.test(word)) return word + 'es'\n return word + 's'\n}\n\n// class name → table name (Laravel's default `getTable()`): the snake_case plural\n// of the class basename. `Order` → orders, `LineItem` → line_items.\nfunction tableize(className: string): string {\n return pluralize(snakeCase(className))\n}\n\n// `foreignId('user_id')->constrained()` infers its parent table from the column\n// name the way Laravel does: strip a trailing `_id`, then pluralize the singular.\n// `user_id` → users, `author_id` → authors, `category_id` → categories.\nfunction tableFromForeignKeyColumn(column: string): string | null {\n const singular = column.replace(/_id$/, '')\n if (singular.length === 0 || singular === column) return null\n return pluralize(singular)\n}\n\n// ── member-call chain flattening ─────────────────────────────────────────────\n\ninterface PhpSeg {\n method: string\n args: Parser.SyntaxNode | null\n node: Parser.SyntaxNode\n}\n\n// Flatten a `$table->foreignId('x')->constrained()` fluent chain into its ordered\n// (method, args) segments, innermost first, plus the base receiver. A bare\n// `$table->id()` → { base: $table, segs: [id] }; `$table->foreign('x')->on('y')` →\n// [foreign, on]. Returns null when the chain doesn't root at a `variable_name`.\nfunction unrollMemberChain(\n node: Parser.SyntaxNode,\n): { base: Parser.SyntaxNode; segs: PhpSeg[] } | null {\n const segs: PhpSeg[] = []\n let cur: Parser.SyntaxNode | null = node\n while (cur && cur.type === 'member_call_expression') {\n const name = cur.childForFieldName('name')?.text\n if (!name) return null\n segs.unshift({ method: name, args: cur.childForFieldName('arguments'), node: cur })\n cur = cur.childForFieldName('object')\n }\n if (!cur || cur.type !== 'variable_name') return null\n return { base: cur, segs }\n}\n\n// The name of a `variable_name` (`$table` → 'table'), or null.\nfunction variableName(node: Parser.SyntaxNode | null | undefined): string | null {\n if (!node || node.type !== 'variable_name') return null\n return node.namedChild(0)?.text ?? null\n}\n\n// ── Schema::create / Schema::table discovery ─────────────────────────────────\n\ninterface Blueprint {\n table: string\n line: number\n blueprintVar: string | null // the closure's Blueprint parameter name (`table`)\n closureBody: Parser.SyntaxNode | null // compound_statement or an arrow body\n}\n\n// The closure a `Schema::create('x', …)` carries — an anonymous `function () {}`\n// (`anonymous_function_creation_expression`) or a `fn () => …` (`arrow_function`).\nfunction schemaClosure(argsNode: Parser.SyntaxNode | null): Parser.SyntaxNode | null {\n for (const v of phpArgumentValues(argsNode)) {\n if (v.type === 'anonymous_function_creation_expression' || v.type === 'arrow_function') return v\n }\n return null\n}\n\n// The Blueprint parameter name of a schema closure (`function (Blueprint $table)`\n// → 'table'), so column calls can be matched to that receiver and unrelated\n// `$other->foo()` statements in the body are ignored.\nfunction blueprintParamName(closure: Parser.SyntaxNode): string | null {\n const params = closure.childForFieldName('parameters')\n if (!params) return null\n for (let i = 0; i < params.namedChildCount; i++) {\n const p = params.namedChild(i)\n if (p?.type === 'simple_parameter') return variableName(p.childForFieldName('name'))\n }\n return null\n}\n\n// The `$table->…()` statement chains inside a schema closure — the outer\n// `member_call_expression` of each, whether in a `{ … }` block or a single-\n// expression arrow body.\nfunction closureCalls(closure: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n const body = closure.childForFieldName('body')\n if (!body) return out\n if (closure.type === 'arrow_function') {\n if (body.type === 'member_call_expression') out.push(body)\n return out\n }\n for (let i = 0; i < body.namedChildCount; i++) {\n const stmt = body.namedChild(i)\n if (stmt?.type !== 'expression_statement') continue\n const call = stmt.namedChild(0)\n if (call?.type === 'member_call_expression') out.push(call)\n }\n return out\n}\n\n// Is this scoped call a `Schema::create` / `Schema::table` (or the FQ facade)?\nfunction isSchemaCall(node: Parser.SyntaxNode): boolean {\n if (node.type !== 'scoped_call_expression') return false\n const scope = node.childForFieldName('scope')?.text ?? ''\n const rooted = scope === 'Schema' || scope.endsWith('\\\\Schema')\n if (!rooted) return false\n const method = node.childForFieldName('name')?.text ?? ''\n return method === 'create' || method === 'createIfNotExists' || method === 'table'\n}\n\n// Read a `Schema::create('orders', function (Blueprint $table) { … })` into its\n// literal table name and the closure to scan for columns / FKs. A computed table\n// name (interpolated, a constant) is left unclaimed — never guessed.\nfunction readBlueprint(node: Parser.SyntaxNode): Blueprint | null {\n const args = node.childForFieldName('arguments')\n const table = phpFirstString(args)\n if (table === null || table.length === 0) return null\n const closure = schemaClosure(args)\n return {\n table,\n line: node.startPosition.row + 1,\n blueprintVar: closure ? blueprintParamName(closure) : null,\n closureBody: closure,\n }\n}\n\n// ── columns ──────────────────────────────────────────────────────────────────\n\n// The Blueprint methods whose FIRST string argument is the column name — the\n// bounded set the fidelity discipline keeps (the analog of ActiveRecord's\n// AR_COLUMN_TYPES and Drizzle's TABLE_BUILDERS). Non-column methods (index,\n// unique, primary, dropColumn, foreign) are absent, so they contribute nothing.\n// `id` / `timestamps` / `softDeletes` / `rememberToken` / `foreignId` /\n// `foreignIdFor` expand specially below.\nconst NAMED_COLUMN_METHODS = new Set([\n 'bigIncrements', 'bigInteger', 'binary', 'boolean', 'char', 'dateTime', 'dateTimeTz',\n 'date', 'decimal', 'double', 'enum', 'float', 'foreignUlid', 'foreignUuid', 'fullText',\n 'geography', 'geometry', 'increments', 'integer', 'ipAddress', 'json', 'jsonb', 'lineString',\n 'longText', 'macAddress', 'mediumIncrements', 'mediumInteger', 'mediumText', 'multiLineString',\n 'multiPoint', 'multiPolygon', 'point', 'polygon', 'set', 'smallIncrements', 'smallInteger',\n 'string', 'text', 'time', 'timeTz', 'timestamp', 'timestampTz', 'tinyIncrements',\n 'tinyInteger', 'tinyText', 'ulid', 'unsignedBigInteger', 'unsignedDecimal', 'unsignedInteger',\n 'unsignedMediumInteger', 'unsignedSmallInteger', 'unsignedTinyInteger', 'uuid', 'year',\n])\n\n// The column(s) a single `$table->…()` statement declares, or [] for a\n// non-column statement (an index, a bare `foreign()` constraint, a deferred\n// `morphs()`). The base segment is the column-defining method; chained modifiers\n// (`->nullable()`, `->constrained()`, …) don't rename the column.\nfunction columnsFromCall(chain: Parser.SyntaxNode, blueprintVar: string | null): string[] {\n const un = unrollMemberChain(chain)\n if (!un) return []\n // Only columns on the Blueprint receiver, when the param name is known.\n if (blueprintVar !== null && variableName(un.base) !== blueprintVar) return []\n const base = un.segs[0]\n if (!base) return []\n const m = base.method\n const first = phpFirstString(base.args)\n\n if (m === 'id') return [first ?? 'id']\n if (m === 'timestamps' || m === 'timestampsTz' || m === 'nullableTimestamps') {\n return ['created_at', 'updated_at']\n }\n if (m === 'softDeletes' || m === 'softDeletesTz') return [first ?? 'deleted_at']\n if (m === 'rememberToken') return ['remember_token']\n if (m === 'foreignId') return first ? [first] : []\n if (m === 'foreignIdFor') {\n // The column Laravel derives from the model: snake_case(basename) + '_id'.\n const cls = classRefBasename(phpArgumentValues(base.args)[0])\n return cls ? [`${snakeCase(cls)}_id`] : []\n }\n if (NAMED_COLUMN_METHODS.has(m)) return first ? [first] : []\n // Everything else (index, unique, primary, foreign, morphs, dropColumn, …)\n // declares no column here.\n return []\n}\n\n// database/migrations/*.php tables + columns. Gated on the `Schema::` marker so it\n// is inert on every other `.php` file. Emits one `sql-table` endpoint per\n// `Schema::create` / `Schema::table`, carrying the literal column names — the\n// orchestrator (calls/index.ts) mints the node and folds the columns onto it as\n// EXTRACTED `ColumnAttr`s (ADR-157 §3). `Schema::table` (an alter) folds its added\n// columns onto the same node the create minted.\nexport function laravelMigrationEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n if (!LARAVEL_SCHEMA_RE.test(file.content)) return []\n const tree = parseSource(makePhpParser(), file.content)\n const out: ExternalEndpoint[] = []\n\n walk(tree.rootNode, (node) => {\n if (!isSchemaCall(node)) return\n const bp = readBlueprint(node)\n if (!bp) return\n\n const columns: string[] = []\n const seen = new Set<string>()\n if (bp.closureBody) {\n for (const call of closureCalls(bp.closureBody)) {\n for (const col of columnsFromCall(call, bp.blueprintVar)) {\n if (!seen.has(col)) {\n seen.add(col)\n columns.push(col)\n }\n }\n }\n }\n\n out.push({\n infraId: infraId('sql-table', bp.table),\n name: bp.table,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'structural',\n ...(columns.length > 0 ? { columns } : {}),\n evidence: {\n file: path.relative(serviceDir, file.path),\n line: bp.line,\n snippet: snippet(file.content, bp.line),\n },\n })\n })\n\n return out\n}\n\n// ── migration foreign keys ───────────────────────────────────────────────────\n\n// database/migrations/*.php foreign keys → REFERENCES table→table edges (ADR-161),\n// from the three literal migration sources:\n// $table->foreignId('user_id')->constrained() -> orders REFERENCES users\n// $table->foreignId('acct_id')->constrained('accounts') -> orders REFERENCES accounts\n// $table->foreignIdFor(User::class) -> orders REFERENCES users\n// $table->foreign('buyer_id')->references('id')->on('buyers') -> orders REFERENCES buyers\n// A bare `foreignId('user_id')` with no `->constrained()` is a column only (no DB\n// FK), so it mints no edge. A computed / unresolvable target is left unclaimed.\nexport function laravelMigrationForeignKeys(\n file: SourceFile,\n serviceDir: string,\n): TableReference[] {\n if (!LARAVEL_SCHEMA_RE.test(file.content)) return []\n const tree = parseSource(makePhpParser(), file.content)\n const out: TableReference[] = []\n const seen = new Set<string>()\n\n const emit = (childTable: string, parentTable: string, line: number): void => {\n if (!parentTable || childTable === parentTable) return\n const key = `${childTable}->${parentTable}`\n if (seen.has(key)) return\n seen.add(key)\n out.push({\n childTable,\n parentTable,\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n\n walk(tree.rootNode, (node) => {\n if (!isSchemaCall(node)) return\n const bp = readBlueprint(node)\n if (!bp || !bp.closureBody) return\n\n for (const chain of closureCalls(bp.closureBody)) {\n const un = unrollMemberChain(chain)\n if (!un) continue\n if (bp.blueprintVar !== null && variableName(un.base) !== bp.blueprintVar) continue\n const base = un.segs[0]\n if (!base) continue\n const line = chain.startPosition.row + 1\n\n // `foreignId('user_id')->constrained(['accounts'])` — FK only when a\n // `constrained` segment is present; the parent is its explicit table\n // argument, else the column-name convention.\n if (base.method === 'foreignId' || base.method === 'foreignUuid') {\n const constrained = un.segs.find((s) => s.method === 'constrained')\n if (!constrained) continue\n const column = phpFirstString(base.args)\n if (!column) continue\n const explicit = phpFirstString(constrained.args)\n const parent = explicit ?? tableFromForeignKeyColumn(column)\n if (parent) emit(bp.table, parent, line)\n continue\n }\n\n // `foreignIdFor(User::class)` — the model names its table directly.\n if (base.method === 'foreignIdFor') {\n const cls = classRefBasename(phpArgumentValues(base.args)[0])\n if (cls) emit(bp.table, tableize(cls), line)\n continue\n }\n\n // `foreign('buyer_id')->references('id')->on('buyers')` — the parent table\n // is the `on(...)` argument; `references(...)` is the parent column, ignored\n // at table grain.\n if (base.method === 'foreign') {\n const on = un.segs.find((s) => s.method === 'on')\n const parent = on ? phpFirstString(on.args) : null\n if (parent) emit(bp.table, parent, line)\n continue\n }\n }\n })\n\n return out\n}\n\n// ── app/Models/*.php: class↔table link + relations ───────────────────────────\n\n// The base-class basename of a `class X extends Y` — `Y`'s trailing name, or null.\nfunction baseClassName(cls: Parser.SyntaxNode): string | null {\n for (let i = 0; i < cls.namedChildCount; i++) {\n const c = cls.namedChild(i)\n if (c?.type !== 'base_clause') continue\n const ref = c.namedChild(0)\n if (!ref) return null\n const seg = ref.text.replace(/^\\\\+/, '').split('\\\\')\n return seg[seg.length - 1] || null\n }\n return null\n}\n\n// Is this class an Eloquent model? `extends Model` (the Eloquent base) or\n// `extends Authenticatable` (the base Laravel's own `User` model uses), including\n// a fully-qualified `\\Illuminate\\Database\\Eloquent\\Model`.\nfunction isEloquentModel(cls: Parser.SyntaxNode): boolean {\n const base = baseClassName(cls)\n return base === 'Model' || base === 'Authenticatable'\n}\n\n// The `declaration_list` body of a class.\nfunction classBody(cls: Parser.SyntaxNode): Parser.SyntaxNode | null {\n for (let i = 0; i < cls.namedChildCount; i++) {\n const c = cls.namedChild(i)\n if (c?.type === 'declaration_list') return c\n }\n return null\n}\n\n// The explicit `protected $table = 'x'` override on a model, or null.\nfunction modelTableOverride(cls: Parser.SyntaxNode): string | null {\n const body = classBody(cls)\n if (!body) return null\n for (let i = 0; i < body.namedChildCount; i++) {\n const decl = body.namedChild(i)\n if (decl?.type !== 'property_declaration') continue\n for (let j = 0; j < decl.namedChildCount; j++) {\n const el = decl.namedChild(j)\n if (el?.type !== 'property_element') continue\n // property_element's first named child is the `variable_name`; a\n // `property_initializer` (when the property has a default) wraps its value.\n if (variableName(el.namedChild(0)) !== 'table') continue\n for (let k = 0; k < el.namedChildCount; k++) {\n const child = el.namedChild(k)\n if (child?.type !== 'property_initializer') continue\n const str = phpStaticString(child.namedChild(child.namedChildCount - 1))\n if (str) return str\n }\n }\n }\n return null\n}\n\n// The table a model maps to: `protected $table` when set, else the snake_case\n// plural of the class name. The migration literal is the anchor; this only names\n// the link.\nfunction modelTable(cls: Parser.SyntaxNode): string | null {\n const nameNode = cls.childForFieldName('name')\n if (!nameNode) return null\n return modelTableOverride(cls) ?? tableize(nameNode.text)\n}\n\n// Walk a class subtree WITHOUT crossing into a nested class / anonymous class, so\n// `$this->belongsTo(...)` calls inside the model's relation methods are found but\n// a nested declaration's calls are not attributed here.\nfunction walkWithinClass(cls: Parser.SyntaxNode, visit: (n: Parser.SyntaxNode) => void): void {\n const body = classBody(cls)\n if (!body) return\n const recur = (n: Parser.SyntaxNode): void => {\n visit(n)\n for (let i = 0; i < n.namedChildCount; i++) {\n const c = n.namedChild(i)\n if (!c) continue\n if (c.type === 'class_declaration' || c.type === 'object_creation_expression') continue\n recur(c)\n }\n }\n recur(body)\n}\n\n// app/Models/*.php: the model→table link. Emits a `sql-table` endpoint so the\n// model file gets a `file ──CALLS──▶ sql-table` edge (the ActiveRecord/SQLAlchemy\n// model-file pattern) — no columns, since Eloquent reads those from the DB and the\n// migration is the column authority.\nexport function laravelModelEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n const tree = parseSource(makePhpParser(), file.content)\n const out: ExternalEndpoint[] = []\n const seen = new Set<string>()\n\n walk(tree.rootNode, (node) => {\n if (node.type !== 'class_declaration' || !isEloquentModel(node)) return\n const table = modelTable(node)\n if (!table || seen.has(table)) return\n seen.add(table)\n const line = node.startPosition.row + 1\n out.push({\n infraId: infraId('sql-table', table),\n name: table,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'verified-call-site',\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n })\n\n return out\n}\n\nconst ELOQUENT_RELATIONS = new Set(['belongsTo', 'hasMany', 'hasOne'])\n\n// app/Models/*.php relations → REFERENCES edges, deduped against the migration FKs\n// at the graph level (table-edges.ts guards every write with `hasEdge`, and the\n// migration refs are added first). The FK lives on the table that holds the\n// `<name>_id` column, so direction depends on the relation macro:\n// belongsTo(User::class) -> this table REFERENCES the relation's table (users)\n// hasMany(LineItem::class) -> line_items REFERENCES this table (FK on the other side)\n// hasOne(Profile::class) -> profiles REFERENCES this table\n// The first argument is the related model class; `belongsToMany` (pivot) and\n// `hasManyThrough` are deferred.\nexport function laravelModelForeignKeys(\n file: SourceFile,\n serviceDir: string,\n): TableReference[] {\n const tree = parseSource(makePhpParser(), file.content)\n const out: TableReference[] = []\n const seen = new Set<string>()\n\n walk(tree.rootNode, (node) => {\n if (node.type !== 'class_declaration' || !isEloquentModel(node)) return\n const thisTable = modelTable(node)\n if (!thisTable) return\n\n walkWithinClass(node, (call) => {\n if (call.type !== 'member_call_expression') return\n if (variableName(call.childForFieldName('object')) !== 'this') return\n const macro = call.childForFieldName('name')?.text\n if (!macro || !ELOQUENT_RELATIONS.has(macro)) return\n const related = classRefBasename(phpArgumentValues(call.childForFieldName('arguments'))[0])\n if (!related) return\n const relatedTable = tableize(related)\n\n const childTable = macro === 'belongsTo' ? thisTable : relatedTable\n const parentTable = macro === 'belongsTo' ? relatedTable : thisTable\n if (childTable === parentTable) return\n const key = `${childTable}->${parentTable}`\n if (seen.has(key)) return\n seen.add(key)\n const line = call.startPosition.row + 1\n out.push({\n childTable,\n parentTable,\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n })\n })\n\n return out\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport Go from 'tree-sitter-go'\nimport { infraId } from '@neat.is/types'\nimport { tableFromSqlStatement } from '../../otel.js'\nimport type { ExternalEndpoint, SourceFile } from './shared.js'\nimport { snippet, toPosix } from './shared.js'\n\nconst SQL_METHODS = new Set(['Exec', 'ExecContext', 'Query', 'QueryContext', 'QueryRow', 'QueryRowContext'])\nconst PARSE_CHUNK = 16384\n\nfunction walk(node: Parser.SyntaxNode, visit: (node: Parser.SyntaxNode) => void): void {\n visit(node)\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i)\n if (child) walk(child, visit)\n }\n}\n\nexport function goSqlEndpointsFromFile(file: SourceFile, serviceDir: string): ExternalEndpoint[] {\n if (path.extname(file.path) !== '.go') return []\n const parser = new Parser()\n parser.setLanguage(Go)\n const tree = parser.parse((index: number) =>\n index >= file.content.length ? '' : file.content.slice(index, index + PARSE_CHUNK),\n )\n const out: ExternalEndpoint[] = []\n walk(tree.rootNode, (node) => {\n if (node.type !== 'call_expression') return\n const fn = node.childForFieldName('function')\n if (fn?.type !== 'selector_expression') return\n const method = fn.childForFieldName('field')?.text\n if (!method || !SQL_METHODS.has(method)) return\n const arg = node.childForFieldName('arguments')?.namedChild(0)\n if (!arg || (arg.type !== 'interpreted_string_literal' && arg.type !== 'raw_string_literal')) return\n const sql = arg.text.slice(1, -1)\n const table = tableFromSqlStatement(sql)\n if (!table) return\n const line = node.startPosition.row + 1\n out.push({\n infraId: infraId('sql-table', table),\n name: table,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'verified-call-site',\n evidence: { file: toPosix(path.relative(serviceDir, file.path)), line, snippet: snippet(file.content, line) },\n })\n })\n return out\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport Go from 'tree-sitter-go'\nimport { infraId } from '@neat.is/types'\nimport { snippet, toPosix, type ExternalEndpoint, type SourceFile, type TableReference } from './shared.js'\n\n// Go/GORM data-axis recognizer (ADR-180), the data rung for Go after ADR-154's\n// route + `database/sql` call sites. GORM model structs feed the SAME `sql-table`\n// nodes, `ColumnAttr` columns (ADR-157), and `REFERENCES` edges (ADR-161) Prisma,\n// Drizzle, Rails, and Laravel already produce, so the reasoning core learns nothing\n// GORM-specific (calls/activerecord.ts and calls/eloquent.ts are the closest\n// analogs — a Rails model or an Eloquent model is a GORM struct).\n//\n// type User struct {\n// gorm.Model // -> id, created_at, updated_at, deleted_at\n// Name string // -> name\n// CompanyID uint // -> company_id (the belongs-to FK column)\n// Company Company // belongs-to: users -> companies\n// Orders []Order // has-many: orders -> users\n// }\n// db.AutoMigrate(&User{}, &Company{}, &Order{})\n//\n// THE LOAD-BEARING DIFFERENCE from Rails/Laravel: GORM has no literal schema file.\n// Rails' `db/schema.rb` and Laravel's migrations spell the table/column names out\n// as the exact database strings; GORM DERIVES the table name from the struct name\n// by `inflection.Plural(toDBName(StructName))` (schema.NamingStrategy.TableName,\n// https://gorm.io/docs/conventions.html). So the pluralizer must reproduce GORM's\n// own — an initialism-aware snake_case plus a jinzhu/inflection-style pluralizer\n// with irregulars — because a wrong derivation mints a table node the runtime\n// never creates (a FALSE node), not merely a missed edge. The OBSERVED table is\n// the ground truth: the GORM OTel plugin reports `tx.Statement.Table` as\n// `db.collection.name`, which ADR-179 taught ingest to read into the table key, so\n// the extracted node fuses onto `infra:sql-table:<name>` by table NAME — the\n// derivation is aimed at exactly that string. Where the derivation is wrong, the\n// OBSERVED side is the anchor and the divergence surfaces the miss.\n\n// Cheap per-file gate — inert on every `.go` file that isn't a GORM model file,\n// the import-gate discipline the other data-axis readers follow.\nconst GORM_IMPORT_RE = /gorm\\.io\\/gorm/\n\nconst PARSE_CHUNK = 16384\n\nfunction makeGoParser(): Parser {\n const p = new Parser()\n p.setLanguage(Go)\n return p\n}\n\nfunction parseSource(parser: Parser, source: string): Parser.Tree {\n return parser.parse((index: number) =>\n index >= source.length ? '' : source.slice(index, index + PARSE_CHUNK),\n )\n}\n\nfunction walk(node: Parser.SyntaxNode, visit: (n: Parser.SyntaxNode) => void): void {\n visit(node)\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c) walk(c, visit)\n }\n}\n\n// ── inflection (GORM's schema.NamingStrategy, reproduced) ────────────────────\n//\n// Unlike the Rails/Laravel readers, where the schema literal is the anchor and the\n// pluralizer is a fallback, here the derived name IS the table name for every\n// struct without a `TableName()` override — so it must match GORM byte-for-byte or\n// the node fuses onto nothing.\n\n// The golint common-initialism list GORM's naming strategy links to. GORM replaces\n// each with its Title-case form BEFORE snake-casing, so a run like `API` collapses\n// to one word (`api`) instead of `a_p_i`. The exact set is GORM-version-sensitive\n// (GORM trims a few from golint's list across releases); this is the documented\n// best-effort surface where the OBSERVED table (ADR-179) is the ground-truth\n// anchor. Sorted longest-first so `HTTPS` wins over its `HTTP` prefix, matching\n// Go's `strings.Replacer` trie (longest match).\nconst COMMON_INITIALISMS = [\n 'ASCII', 'HTTPS', 'UTF8', 'XSRF', 'HTML', 'HTTP', 'JSON', 'UUID', 'XMPP',\n 'ACL', 'API', 'CPU', 'CSS', 'DNS', 'EOF', 'GUID', 'LHS', 'QPS', 'RAM', 'RHS',\n 'RPC', 'SLA', 'SQL', 'SSH', 'TCP', 'TLS', 'TTL', 'UDP', 'UID', 'URI', 'URL',\n 'UID', 'XSS', 'ID', 'IP', 'UI', 'VM', 'XML',\n].sort((a, b) => b.length - a.length)\n\nfunction titleCase(word: string): string {\n return word.charAt(0) + word.slice(1).toLowerCase()\n}\n\n// Reproduce GORM's `commonInitialismsReplacer` — replace each initialism with its\n// Title-case form, scanning left-to-right, longest match first (Go's Replacer).\nfunction replaceInitialisms(name: string): string {\n let out = ''\n let i = 0\n while (i < name.length) {\n let matched = false\n for (const init of COMMON_INITIALISMS) {\n if (name.startsWith(init, i)) {\n out += titleCase(init)\n i += init.length\n matched = true\n break\n }\n }\n if (!matched) {\n out += name[i]\n i++\n }\n }\n return out\n}\n\nconst isUpper = (c: string): boolean => c >= 'A' && c <= 'Z'\nconst isDigit = (c: string): boolean => c >= '0' && c <= '9'\n\n// A faithful port of GORM's `NamingStrategy.toDBName` (schema/naming.go): run the\n// initialism replacer, then a CamelCase→snake pass that keeps capital runs\n// together (`APIKey` → `api_key`, `LineItem` → `line_item`, `GormUserName` →\n// `gorm_user_name`). Ported char-by-char since Go identifiers are ASCII.\nfunction toDBName(name: string): string {\n if (name === '') return ''\n const value = replaceInitialisms(name)\n if (value.length === 1) return value.toLowerCase()\n\n let buf = ''\n let lastCase = false\n let curCase = isUpper(value[0]!)\n for (let i = 0; i < value.length - 1; i++) {\n const v = value[i]!\n const nextCase = isUpper(value[i + 1]!)\n const nextNumber = isDigit(value[i + 1]!)\n if (curCase) {\n if (lastCase && (nextCase || nextNumber)) {\n buf += v.toLowerCase()\n } else {\n if (i > 0 && value[i - 1] !== '_' && lastCase !== curCase) buf += '_'\n buf += v.toLowerCase()\n }\n } else {\n buf += v\n }\n lastCase = curCase\n curCase = nextCase\n }\n const last = value[value.length - 1]!\n if (curCase) {\n if (!lastCase && value.length > 1) buf += '_'\n buf += last.toLowerCase()\n } else {\n buf += last\n }\n return buf\n}\n\n// github.com/jinzhu/inflection (a Rails-ActiveSupport port) is what GORM pluralizes\n// with. Uncountables pass through, irregulars map directly, then the ordered rules\n// fire first-match-wins (specific before the generic `+s`). The irregular set is\n// finite/best-effort — documented in ADR-180, with the OBSERVED table as the\n// anchor where a rule is wrong. Operates on the snake_cased struct name, so the\n// rules anchor on the trailing word of a multi-word name (`line_item` → `line_items`).\nconst UNCOUNTABLE = new Set([\n 'equipment', 'information', 'rice', 'money', 'species', 'series', 'fish',\n 'sheep', 'jeans', 'police',\n])\n\nconst IRREGULAR: Array<[string, string]> = [\n ['person', 'people'],\n ['man', 'men'],\n ['child', 'children'],\n ['sex', 'sexes'],\n ['move', 'moves'],\n]\n\nconst PLURAL_RULES: Array<[RegExp, string]> = [\n [/(quiz)$/i, '$1zes'],\n [/^(ox)$/i, '$1en'],\n [/([ml])ouse$/i, '$1ice'],\n [/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'],\n [/(x|ch|ss|sh)$/i, '$1es'],\n [/([^aeiouy]|qu)y$/i, '$1ies'],\n [/(hive)$/i, '$1s'],\n [/(?:([^f])fe|([lr])f)$/i, '$1$2ves'],\n [/sis$/i, 'ses'],\n [/([ti])um$/i, '$1a'],\n [/([ti])a$/i, '$1a'],\n [/(buffal|tomat)o$/i, '$1oes'],\n [/(bu)s$/i, '$1ses'],\n [/(alias|status)$/i, '$1es'],\n [/(octop|vir)i$/i, '$1i'],\n [/(octop|vir)us$/i, '$1i'],\n [/(ax|test)is$/i, '$1es'],\n [/s$/i, 's'],\n]\n\nfunction pluralize(word: string): string {\n if (word === '') return word\n const lower = word.toLowerCase()\n for (const u of UNCOUNTABLE) {\n if (lower === u || lower.endsWith('_' + u)) return word\n }\n for (const [sing, plur] of IRREGULAR) {\n const re = new RegExp(sing + '$', 'i')\n if (re.test(word)) return word.replace(re, plur)\n }\n for (const [re, rep] of PLURAL_RULES) {\n if (re.test(word)) return word.replace(re, rep)\n }\n return word + 's'\n}\n\n// struct name → GORM's default table name: `inflection.Plural(toDBName(name))`.\n// `User` → users, `LineItem` → line_items, `APIKey` → api_keys, `Person` → people.\nfunction deriveTableName(structName: string): string {\n return pluralize(toDBName(structName))\n}\n\n// ── struct / field model ─────────────────────────────────────────────────────\n\ninterface GormTag {\n column?: string\n skip?: boolean // gorm:\"-\"\n primaryKey?: boolean\n foreignKey?: string\n many2many?: string\n embedded?: boolean\n embeddedPrefix?: string\n}\n\ninterface GoField {\n names: string[] // empty for an embedded (anonymous) field\n typeName: string | null // base type name, slice/pointer/array unwrapped\n qualifier: string | null // package part of a qualified type (`gorm` in gorm.Model)\n isSlice: boolean\n isPointer: boolean\n isQualified: boolean\n tag: GormTag\n line: number\n}\n\ninterface StructInfo {\n name: string\n fields: GoField[]\n line: number\n}\n\nfunction stringLiteralValue(node: Parser.SyntaxNode | null | undefined): string | null {\n if (!node) return null\n if (node.type === 'interpreted_string_literal' || node.type === 'raw_string_literal') {\n const t = node.text\n return t.length >= 2 ? t.slice(1, -1) : ''\n }\n return null\n}\n\n// Parse a struct field's `gorm:\"...\"` tag. The tag node text carries its delimiters\n// (backticks for a raw string, quotes for an interpreted one); an interpreted tag\n// escapes its inner quotes, so unescape first.\nfunction parseGormTag(tagNode: Parser.SyntaxNode | null | undefined): GormTag {\n const tag: GormTag = {}\n if (!tagNode) return tag\n let inner = tagNode.text\n if (inner.length >= 2) inner = inner.slice(1, -1)\n if (tagNode.type === 'interpreted_string_literal') inner = inner.replace(/\\\\\"/g, '\"')\n const m = inner.match(/gorm:\"([^\"]*)\"/)\n if (!m) return tag\n for (const part of m[1]!.split(';')) {\n if (part === '') continue\n const idx = part.indexOf(':')\n const key = (idx >= 0 ? part.slice(0, idx) : part).trim().toLowerCase()\n const value = idx >= 0 ? part.slice(idx + 1).trim() : ''\n if (key === '-') tag.skip = true\n else if (key === 'column') tag.column = value\n else if (key === 'primarykey' || key === 'primary_key') tag.primaryKey = true\n else if (key === 'foreignkey') tag.foreignKey = value\n else if (key === 'many2many') tag.many2many = value\n else if (key === 'embedded') tag.embedded = true\n else if (key === 'embeddedprefix') tag.embeddedPrefix = value\n }\n return tag\n}\n\n// Unwrap a field's type through any slice / array / pointer layers to the base\n// named type. `[]Order` → { name: 'Order', isSlice }, `*Company` → { name:\n// 'Company', isPointer }, `time.Time` → { name: 'Time', qualifier: 'time' }.\nfunction unwrapType(typeNode: Parser.SyntaxNode | null): {\n name: string | null\n qualifier: string | null\n isSlice: boolean\n isPointer: boolean\n isQualified: boolean\n} {\n let isSlice = false\n let isPointer = false\n let n: Parser.SyntaxNode | null = typeNode\n while (n && (n.type === 'slice_type' || n.type === 'array_type' || n.type === 'pointer_type')) {\n if (n.type === 'slice_type' || n.type === 'array_type') isSlice = true\n if (n.type === 'pointer_type') isPointer = true\n n = n.childForFieldName('element') ?? n.namedChild(n.namedChildCount - 1)\n }\n if (!n) return { name: null, qualifier: null, isSlice, isPointer, isQualified: false }\n if (n.type === 'type_identifier') {\n return { name: n.text, qualifier: null, isSlice, isPointer, isQualified: false }\n }\n if (n.type === 'qualified_type') {\n const pkg = n.childForFieldName('package')?.text ?? n.namedChild(0)?.text ?? null\n const nm = n.childForFieldName('name')?.text ?? n.namedChild(1)?.text ?? null\n return { name: nm, qualifier: pkg, isSlice, isPointer, isQualified: true }\n }\n // map / func / interface / channel types are never a column or a relation.\n return { name: null, qualifier: null, isSlice, isPointer, isQualified: false }\n}\n\nfunction readField(fieldDecl: Parser.SyntaxNode): GoField {\n const names: string[] = []\n let tagNode: Parser.SyntaxNode | null = null\n for (let i = 0; i < fieldDecl.namedChildCount; i++) {\n const c = fieldDecl.namedChild(i)\n if (!c) continue\n if (c.type === 'field_identifier') names.push(c.text)\n else if (c.type === 'raw_string_literal' || c.type === 'interpreted_string_literal') tagNode = c\n }\n const typeNode = fieldDecl.childForFieldName('type')\n const t = unwrapType(typeNode)\n return {\n names,\n typeName: t.name,\n qualifier: t.qualifier,\n isSlice: t.isSlice,\n isPointer: t.isPointer,\n isQualified: t.isQualified,\n tag: parseGormTag(tagNode),\n line: fieldDecl.startPosition.row + 1,\n }\n}\n\n// Every `type X struct { … }` in the file, keyed by name.\nfunction collectStructs(tree: Parser.Tree): Map<string, StructInfo> {\n const structs = new Map<string, StructInfo>()\n walk(tree.rootNode, (node) => {\n if (node.type !== 'type_spec') return\n const nameNode = node.childForFieldName('name')\n const typeNode = node.childForFieldName('type')\n if (!nameNode || typeNode?.type !== 'struct_type') return\n const list = typeNode.childForFieldName('body') ?? typeNode.namedChild(0)\n const fields: GoField[] = []\n if (list && list.type === 'field_declaration_list') {\n for (let i = 0; i < list.namedChildCount; i++) {\n const fd = list.namedChild(i)\n if (fd?.type === 'field_declaration') fields.push(readField(fd))\n }\n }\n structs.set(nameNode.text, {\n name: nameNode.text,\n fields,\n line: node.startPosition.row + 1,\n })\n })\n return structs\n}\n\n// GORM entry points that take a model pointer/value and so name a model struct.\n// `AutoMigrate(&A{}, &B{})` is the richest — it enumerates the whole model set in\n// one call.\nconst GORM_MODEL_METHODS = new Set([\n 'AutoMigrate', 'Model', 'Create', 'Find', 'First', 'Take', 'Last', 'Save',\n 'Delete', 'Where', 'FirstOrCreate', 'FirstOrInit',\n])\n\n// The struct name a `&X{}` / `X{}` argument composite-literal names, or null.\nfunction compositeStructName(arg: Parser.SyntaxNode): string | null {\n let n: Parser.SyntaxNode | null = arg\n if (n.type === 'unary_expression') n = n.childForFieldName('operand') ?? n.namedChild(0)\n if (!n || n.type !== 'composite_literal') return null\n const typeNode = n.childForFieldName('type')\n if (!typeNode) return null\n if (typeNode.type === 'type_identifier') return typeNode.text\n if (typeNode.type === 'qualified_type') {\n return typeNode.childForFieldName('name')?.text ?? typeNode.namedChild(1)?.text ?? null\n }\n return null\n}\n\n// Model structs referenced through a GORM db-method call anywhere in the file.\nfunction collectCallModels(tree: Parser.Tree): Set<string> {\n const models = new Set<string>()\n walk(tree.rootNode, (node) => {\n if (node.type !== 'call_expression') return\n const fn = node.childForFieldName('function')\n if (fn?.type !== 'selector_expression') return\n const method = fn.childForFieldName('field')?.text\n if (!method || !GORM_MODEL_METHODS.has(method)) return\n const args = node.childForFieldName('arguments')\n if (!args) return\n for (let i = 0; i < args.namedChildCount; i++) {\n const arg = args.namedChild(i)\n if (!arg) continue\n const name = compositeStructName(arg)\n if (name) models.add(name)\n }\n })\n return models\n}\n\n// A `func (X) TableName() string { return \"literal\" }` override map. A computed\n// return (a variable, a concatenation) yields no entry — the struct falls back to\n// the derived name — but still counts as a model signal.\nfunction collectTableNameOverrides(\n tree: Parser.Tree,\n): { overrides: Map<string, string>; declarers: Set<string> } {\n const overrides = new Map<string, string>()\n const declarers = new Set<string>()\n walk(tree.rootNode, (node) => {\n if (node.type !== 'method_declaration') return\n if (node.childForFieldName('name')?.text !== 'TableName') return\n const receiver = node.childForFieldName('receiver')\n if (!receiver) return\n let recvType: string | null = null\n for (let i = 0; i < receiver.namedChildCount; i++) {\n const pd = receiver.namedChild(i)\n if (pd?.type !== 'parameter_declaration') continue\n const t = unwrapType(pd.childForFieldName('type'))\n recvType = t.name\n }\n if (!recvType) return\n declarers.add(recvType)\n const body = node.childForFieldName('body')\n if (!body) return\n let literal: string | null = null\n walk(body, (n) => {\n if (literal !== null) return\n if (n.type !== 'return_statement') return\n const exprList = n.namedChild(0)\n const first = exprList?.namedChild(0) ?? exprList\n const v = stringLiteralValue(first)\n if (v) literal = v\n })\n if (literal !== null) overrides.set(recvType, literal)\n })\n return { overrides, declarers }\n}\n\n// A field is a RELATION (association), not a column, when its base type — slice or\n// pointer unwrapped — is another struct declared in this file, and it is not a\n// qualified type (`time.Time`, `sql.NullString`) or the embedded `gorm.Model`.\nfunction isRelationField(field: GoField, structs: Map<string, StructInfo>): boolean {\n if (field.names.length === 0) return false // embedded, handled separately\n if (field.isQualified) return false\n if (!field.typeName) return false\n return structs.has(field.typeName)\n}\n\n// Is `gorm.Model`?\nfunction isGormModelEmbed(field: GoField): boolean {\n return field.names.length === 0 && field.qualifier === 'gorm' && field.typeName === 'Model'\n}\n\n// ── analysis (shared by the endpoint and FK readers) ─────────────────────────\n\ninterface Analysis {\n structs: Map<string, StructInfo>\n models: Set<string>\n tableFor: (structName: string) => string\n}\n\nfunction analyze(tree: Parser.Tree): Analysis {\n const structs = collectStructs(tree)\n const { overrides, declarers } = collectTableNameOverrides(tree)\n const callModels = collectCallModels(tree)\n\n // Seed the model set from the three direct signals: a gorm.Model embed, a GORM\n // db-method call arg, or a TableName() declaration.\n const models = new Set<string>()\n for (const [name, info] of structs) {\n if (info.fields.some(isGormModelEmbed)) models.add(name)\n }\n for (const name of callModels) if (structs.has(name)) models.add(name)\n for (const name of declarers) if (structs.has(name)) models.add(name)\n\n // Expand: a struct referenced as a relation field by a model is itself a model\n // (GORM requires the related type to be a registered model). Fixpoint over the\n // relation graph so a related model's own relations pull their targets in too.\n let grew = true\n while (grew) {\n grew = false\n for (const name of Array.from(models)) {\n const info = structs.get(name)\n if (!info) continue\n for (const field of info.fields) {\n if (!isRelationField(field, structs)) continue\n const target = field.typeName!\n if (!models.has(target) && structs.has(target)) {\n models.add(target)\n grew = true\n }\n }\n }\n }\n\n const tableFor = (structName: string): string =>\n overrides.get(structName) ?? deriveTableName(structName)\n\n return { structs, models, tableFor }\n}\n\n// Expand a model struct's fields into its database column names. Anonymous\n// embedded fields inline: `gorm.Model` → the four audit columns, and an embedded\n// LOCAL struct (`type Base struct{…}; type User struct{ Base; … }`) inlines its own\n// columns (one level, cycle-guarded). A named field with `gorm:\"embedded\"` inlines\n// with an optional prefix. `gorm:\"-\"` skips; a relation field is not a column.\nfunction collectColumns(\n struct: StructInfo,\n structs: Map<string, StructInfo>,\n seen: Set<string>,\n prefix: string,\n out: string[],\n emitted: Set<string>,\n): void {\n if (seen.has(struct.name)) return\n seen.add(struct.name)\n\n const add = (col: string): void => {\n const full = prefix + col\n if (!emitted.has(full)) {\n emitted.add(full)\n out.push(full)\n }\n }\n\n for (const field of struct.fields) {\n if (field.tag.skip) continue\n\n // Anonymous embedded field.\n if (field.names.length === 0) {\n if (isGormModelEmbed(field)) {\n add('id')\n add('created_at')\n add('updated_at')\n add('deleted_at')\n } else if (!field.isQualified && field.typeName && structs.has(field.typeName)) {\n collectColumns(structs.get(field.typeName)!, structs, seen, prefix, out, emitted)\n }\n // An unresolved external embed (some other package's base) can't be inlined.\n continue\n }\n\n // A named field tagged embedded inlines the target struct's columns.\n if (field.tag.embedded && !field.isQualified && field.typeName && structs.has(field.typeName)) {\n collectColumns(\n structs.get(field.typeName)!,\n structs,\n seen,\n prefix + (field.tag.embeddedPrefix ?? ''),\n out,\n emitted,\n )\n continue\n }\n\n // A relation to another model is not a column (its FK companion scalar is).\n if (isRelationField(field, structs)) continue\n\n // A scalar / time.Time / sql.Null* / []byte / named-scalar field is a column.\n // The `gorm:\"column:x\"` override wins (verbatim); otherwise snake_case each\n // name. The override only makes sense for a single-name declaration.\n if (field.names.length === 1 && field.tag.column) {\n add(field.tag.column)\n } else {\n for (const n of field.names) add(toDBName(n))\n }\n }\n\n seen.delete(struct.name)\n}\n\n// GORM model structs → `infra:sql-table:<name>` endpoints carrying EXTRACTED\n// columns (ADR-157 §3). Gated on the `gorm.io/gorm` import. One endpoint per\n// distinct table; the orchestrator (calls/index.ts) mints the node, folds the\n// columns, and originates the `file ──CALLS──▶ sql-table` edge.\nexport function gormEndpointsFromFile(\n file: SourceFile,\n serviceDir: string,\n): ExternalEndpoint[] {\n if (path.extname(file.path) !== '.go') return []\n if (!GORM_IMPORT_RE.test(file.content)) return []\n const tree = parseSource(makeGoParser(), file.content)\n const { structs, models, tableFor } = analyze(tree)\n\n const out: ExternalEndpoint[] = []\n const seenTables = new Set<string>()\n for (const name of models) {\n const struct = structs.get(name)\n if (!struct) continue\n const table = tableFor(name)\n if (seenTables.has(table)) continue\n seenTables.add(table)\n\n const columns: string[] = []\n collectColumns(struct, structs, new Set(), '', columns, new Set())\n\n out.push({\n infraId: infraId('sql-table', table),\n name: table,\n kind: 'sql-table',\n edgeType: 'CALLS',\n confidenceKind: 'structural',\n ...(columns.length > 0 ? { columns } : {}),\n evidence: {\n file: toPosix(path.relative(serviceDir, file.path)),\n line: struct.line,\n snippet: snippet(file.content, struct.line),\n },\n })\n }\n return out\n}\n\n// GORM associations → `REFERENCES` table→table edges (ADR-161). The FK lives on the\n// table holding the `<Field>ID` column, so direction depends on the association:\n// belongs to (User has CompanyID + Company Company) -> users REFERENCES companies\n// has one (User has CreditCard CreditCard) -> credit_cards REFERENCES users\n// has many (User has Orders []Order) -> orders REFERENCES users\n// many2many (User has Languages []Language `many2many:user_languages`)\n// -> user_languages REFERENCES users + languages\n// A singular struct field is belongs-to when THIS struct also holds the\n// conventional `<Field>ID` (or `foreignKey:`-named) scalar; otherwise has-one.\n// `table-edges.ts` dedupes at the graph level and drops self-loops.\nexport function gormForeignKeys(file: SourceFile, serviceDir: string): TableReference[] {\n if (path.extname(file.path) !== '.go') return []\n if (!GORM_IMPORT_RE.test(file.content)) return []\n const tree = parseSource(makeGoParser(), file.content)\n const { structs, models, tableFor } = analyze(tree)\n\n const out: TableReference[] = []\n const seen = new Set<string>()\n const emit = (childTable: string, parentTable: string, line: number): void => {\n if (!childTable || !parentTable || childTable === parentTable) return\n const key = `${childTable}->${parentTable}`\n if (seen.has(key)) return\n seen.add(key)\n out.push({\n childTable,\n parentTable,\n evidence: {\n file: toPosix(path.relative(serviceDir, file.path)),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n\n for (const name of models) {\n const struct = structs.get(name)\n if (!struct) continue\n const thisTable = tableFor(name)\n const scalarNames = new Set(\n struct.fields\n .filter((f) => f.names.length > 0 && !isRelationField(f, structs))\n .flatMap((f) => f.names),\n )\n\n for (const field of struct.fields) {\n if (field.tag.skip) continue\n if (!isRelationField(field, structs)) continue\n const relTable = tableFor(field.typeName!)\n\n // many2many: synthesize the join table and reference both sides.\n if (field.tag.many2many) {\n emit(field.tag.many2many, thisTable, field.line)\n emit(field.tag.many2many, relTable, field.line)\n continue\n }\n\n if (field.isSlice) {\n // has many — FK on the other table.\n emit(relTable, thisTable, field.line)\n continue\n }\n\n // Singular struct field: belongs-to when this struct carries the FK column.\n const convFk = field.names[0]! + 'ID'\n const belongsTo =\n scalarNames.has(convFk) ||\n (field.tag.foreignKey ? scalarNames.has(field.tag.foreignKey) : false)\n if (belongsTo) emit(thisTable, relTable, field.line)\n else emit(relTable, thisTable, field.line)\n }\n }\n\n return out\n}\n","import type { GraphEdge, InfraNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n infraId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport type { DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { loadSourceFiles, type TableReference } from './calls/shared.js'\nimport { drizzleForeignKeys } from './calls/drizzle.js'\nimport { prismaForeignKeys } from './calls/prisma.js'\nimport { sqlalchemyForeignKeys } from './calls/sqlalchemy.js'\nimport { railsSchemaForeignKeys, railsModelForeignKeys } from './calls/activerecord.js'\nimport { laravelMigrationForeignKeys, laravelModelForeignKeys } from './calls/eloquent.js'\nimport { gormForeignKeys } from './calls/gorm.js'\n\n// Foreign-key table→table edges (ADR-161). The data-axis sibling of the symbol\n// heritage producer (`symbol-edges.ts`, ADR-158 §3): column grain (ADR-157) gave\n// a `infra:sql-table:<name>` node its columns but no edges *between* tables, so\n// blast-radius / dependencies / root-cause could not walk the data axis. This\n// mints one `infra:sql-table:<child> ──REFERENCES──▶ infra:sql-table:<parent>`\n// EXTRACTED edge per resolved foreign key.\n//\n// The per-ORM readers live beside the columns each already parses — Drizzle's\n// `.references(() => users.id)`, Prisma's `@relation(fields:[…], references:[…])`,\n// SQLAlchemy's `ForeignKey('users.id')`. Each resolves the parent to the DATABASE\n// table name (`infraId('sql-table', name)` — the same node the column/table\n// extractors and OTLP already target), reproduced verbatim the ORM's way, so the\n// FK lands on the fused table node rather than a code-model twin. A computed or\n// cross-file-unresolvable reference is left unclaimed by the reader (it returns\n// nothing) — never guessed, the never-fabricate discipline of file-awareness.md\n// §6. Every edge grades `structural` EXTRACTED with `evidence.file`/`line` pinned\n// to the FK declaration site, so ghost-edge cleanup (`retire.ts`) keys off it like\n// any other EXTRACTED edge, and every write is `hasNode` / `hasEdge`-guarded.\n//\n// Runs after `addCallEdges`, whose column/table read has already minted both\n// endpoints' InfraNodes; `ensureTableNode` is a self-contained safety net that\n// mints a bare table node (same id, no twin) when a referenced table was named in\n// a FK but not otherwise picked up. The reasoning core stays agnostic: REFERENCES\n// is a plain edge type the generic graph walk admits with no traversal change\n// (ADR-158 §6).\n\nexport async function addTableEdges(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const files = await loadSourceFiles(service.dir)\n const refs: TableReference[] = []\n // Rails model associations (ADR-174) and Laravel Eloquent relations\n // (ADR-178) corroborate the schema literals — a relationship is often\n // declared on both sides. They are collected here and appended AFTER the\n // schema/ORM literals, so when the same child→parent pair appears twice the\n // literal (schema.rb / migration) evidence wins the graph-level dedup.\n const modelRefs: TableReference[] = []\n\n for (const file of files) {\n try {\n refs.push(...drizzleForeignKeys(file, service.dir))\n refs.push(...sqlalchemyForeignKeys(file, service.dir))\n refs.push(...railsSchemaForeignKeys(file, service.dir))\n refs.push(...laravelMigrationForeignKeys(file, service.dir))\n refs.push(...gormForeignKeys(file, service.dir))\n modelRefs.push(...railsModelForeignKeys(file, service.dir))\n modelRefs.push(...laravelModelForeignKeys(file, service.dir))\n } catch (err) {\n recordExtractionError('foreign-key extraction', file.path, err)\n }\n }\n // Prisma's `schema.prisma` is not a walked source file — read once per service.\n try {\n refs.push(...(await prismaForeignKeys(service.dir)))\n } catch (err) {\n recordExtractionError('prisma foreign-key extraction', service.dir, err)\n }\n refs.push(...modelRefs)\n\n for (const ref of refs) {\n const childId = infraId('sql-table', ref.childTable)\n const parentId = infraId('sql-table', ref.parentTable)\n // A self-referential FK (a tree's `parent_id`) would be a self-loop; the\n // graph disallows them, and a table referencing itself carries no cross-table\n // blast radius, so it is skipped rather than emitted.\n if (childId === parentId) continue\n\n nodesAdded += ensureTableNode(graph, childId, ref.childTable)\n nodesAdded += ensureTableNode(graph, parentId, ref.parentTable)\n\n const edgeId = extractedEdgeId(childId, parentId, EdgeType.REFERENCES)\n if (graph.hasEdge(edgeId)) continue\n const edge: GraphEdge = {\n id: edgeId,\n source: childId,\n target: parentId,\n type: EdgeType.REFERENCES,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: ref.evidence,\n }\n graph.addEdgeWithKey(edgeId, childId, parentId, edge)\n edgesAdded++\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n\n// Mint a bare `sql-table` InfraNode if the FK named a table nothing else did.\n// Idempotent — same `infra:sql-table:<name>` id the column/table extractors and\n// OTLP use, so it fuses onto the existing node and never twins. Returns 1 when a\n// node was minted, 0 when it already existed.\nfunction ensureTableNode(graph: NeatGraph, id: string, name: string): number {\n if (graph.hasNode(id)) return 0\n const node: InfraNode = {\n id,\n type: NodeType.InfraNode,\n name,\n provider: 'self',\n kind: 'sql-table',\n }\n graph.addNode(id, node)\n return 1\n}\n","import path from 'node:path'\nimport type { GraphEdge } from '@neat.is/types'\nimport { EdgeType, Provenance, confidenceForExtracted } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { exists, makeEdgeId, readYaml, type DiscoveredService } from '../shared.js'\nimport { recordExtractionError } from '../errors.js'\nimport { classifyImage, makeInfraNode } from './shared.js'\n\ninterface ComposeService {\n image?: string\n build?: string | { context?: string }\n depends_on?: string[] | Record<string, unknown>\n}\n\ninterface ComposeFile {\n services?: Record<string, ComposeService>\n}\n\nfunction dependsOnList(value: ComposeService['depends_on']): string[] {\n if (!value) return []\n if (Array.isArray(value)) return value\n return Object.keys(value)\n}\n\nfunction serviceNameToServiceNode(\n name: string,\n services: DiscoveredService[],\n): string | null {\n for (const s of services) {\n if (s.node.name === name || path.basename(s.dir) === name) return s.node.id\n }\n return null\n}\n\n// Project-level docker-compose.yml describes deployment topology. Each compose\n// service that is *not* one of the discovered ServiceNodes becomes an\n// InfraNode (databases, brokers, caches). depends_on lists become DEPENDS_ON\n// edges from the dependent to its dependency, regardless of whether the\n// endpoint is a ServiceNode or InfraNode — the edge itself is the deployment\n// fact, not the role.\nexport async function addComposeInfra(\n graph: NeatGraph,\n scanPath: string,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n let composePath: string | null = null\n for (const name of ['docker-compose.yml', 'docker-compose.yaml']) {\n const abs = path.join(scanPath, name)\n if (await exists(abs)) {\n composePath = abs\n break\n }\n }\n if (!composePath) return { nodesAdded, edgesAdded }\n\n let compose: ComposeFile\n try {\n compose = await readYaml<ComposeFile>(composePath)\n } catch (err) {\n recordExtractionError(\n 'infra docker-compose',\n path.relative(scanPath, composePath),\n err,\n )\n return { nodesAdded, edgesAdded }\n }\n if (!compose?.services) return { nodesAdded, edgesAdded }\n const evidenceFile = path.relative(scanPath, composePath).split(path.sep).join('/')\n\n const composeNameToNodeId = new Map<string, string>()\n for (const [composeName, svc] of Object.entries(compose.services)) {\n const matchedServiceId = serviceNameToServiceNode(composeName, services)\n if (matchedServiceId) {\n composeNameToNodeId.set(composeName, matchedServiceId)\n continue\n }\n const kind = svc.image ? classifyImage(svc.image) : 'container'\n const node = makeInfraNode(kind, composeName)\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n composeNameToNodeId.set(composeName, node.id)\n }\n\n for (const [composeName, svc] of Object.entries(compose.services)) {\n const sourceId = composeNameToNodeId.get(composeName)\n if (!sourceId) continue\n for (const dep of dependsOnList(svc.depends_on)) {\n const targetId = composeNameToNodeId.get(dep)\n if (!targetId) continue\n const edgeId = makeEdgeId(sourceId, targetId, EdgeType.DEPENDS_ON)\n if (graph.hasEdge(edgeId)) continue\n // depends_on declaration from docker-compose.yml — structural deployment\n // fact, structural tier per ADR-066.\n const edge: GraphEdge = {\n id: edgeId,\n source: sourceId,\n target: targetId,\n type: EdgeType.DEPENDS_ON,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile },\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import type { GraphEdge, InfraNode } from '@neat.is/types'\nimport { EdgeTypeValue, NodeType, Provenance, confidenceForExtracted, infraId } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { makeEdgeId } from '../shared.js'\n\n// ADR-010 reserves the `infra:` prefix; the kind segment lets traversal and\n// MCP tools sub-type without inventing a new top-level NodeType per source.\nexport function makeInfraNode(\n kind: string,\n name: string,\n provider = 'self',\n extras?: { region?: string },\n): InfraNode {\n return {\n id: infraId(kind, name),\n type: NodeType.InfraNode,\n name,\n provider,\n kind,\n ...(extras?.region ? { region: extras.region } : {}),\n }\n}\n\n// Stable kind for an image string like \"postgres:15-alpine\" or \"mysql:8\".\n// The image name itself ends up in the InfraNode `name` field; this function\n// only classifies what the image *is*, so callers can group similar runtimes.\nexport function classifyImage(image: string): string {\n const lower = image.toLowerCase()\n const repo = lower.split(':')[0]!\n const last = repo.split('/').pop() ?? repo\n if (last.startsWith('postgres')) return 'postgres'\n if (last.startsWith('mysql') || last.startsWith('mariadb')) return 'mysql'\n if (last.startsWith('mongo')) return 'mongodb'\n if (last.startsWith('redis')) return 'redis'\n if (last.startsWith('rabbitmq')) return 'rabbitmq'\n if (last.startsWith('kafka') || last.includes('kafka')) return 'kafka'\n if (last.startsWith('memcached')) return 'memcached'\n return 'container'\n}\n\n// Best-effort 1-indexed line for a declared value in a config file's raw text —\n// the same \"read config as data\" text search cloudflare.ts and terraform.ts use\n// rather than a real AST.\nexport function lineContaining(raw: string, needle: string | undefined): number | undefined {\n if (!needle) return undefined\n const idx = raw.indexOf(needle)\n if (idx === -1) return undefined\n let line = 1\n for (let i = 0; i < idx; i++) if (raw[i] === '\\n') line++\n return line\n}\n\n// One declared-resource InfraNode + an edge from the anchor (a ServiceNode or an\n// entry FileNode). Idempotent; every edge carries evidence.file. Shared by the\n// platform extractors (vercel/railway/supabase); cloudflare.ts predates this and\n// keeps its own local copy. No self-loop when a resource id equals the anchor.\nexport function emitPlatformResourceEdge(\n graph: NeatGraph,\n anchorId: string,\n edgeType: EdgeTypeValue,\n kind: string,\n name: string,\n provider: string,\n evidenceFile: string,\n line?: number,\n): { nodesAdded: number; edgesAdded: number } {\n let nodesAdded = 0\n let edgesAdded = 0\n const node = makeInfraNode(kind, name, provider)\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n if (node.id === anchorId) return { nodesAdded, edgesAdded }\n const edgeId = makeEdgeId(anchorId, node.id, edgeType)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: anchorId,\n target: node.id,\n type: edgeType,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile, ...(line !== undefined ? { line } : {}) },\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n return { nodesAdded, edgesAdded }\n}\n","import path from 'node:path'\nimport { promises as fs } from 'node:fs'\nimport type { GraphEdge } from '@neat.is/types'\nimport { EdgeType, Provenance, confidenceForExtracted } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { exists, makeEdgeId, type DiscoveredService } from '../shared.js'\nimport { recordExtractionError } from '../errors.js'\nimport { makeInfraNode } from './shared.js'\nimport { ensureFileNode, toPosix } from '../calls/shared.js'\n\ninterface DockerfileFacts {\n image: string | null\n // Ports declared with `EXPOSE`, in declaration order, deduped. Each one is a\n // network endpoint the container listens on — the structural answer to \"what\n // is this service reachable at\".\n ports: number[]\n // The trimmed `ENTRYPOINT` / `CMD` line that names the process the container\n // runs. Carried as edge evidence so the entrypoint is queryable instead of\n // being a silent fact only the Dockerfile knows.\n entrypoint: string | null\n}\n\n// Read the runtime-shaping instructions out of a Dockerfile in one pass:\n//\n// - FROM → the runtime image. Multi-stage builds report the *runtime* image\n// (the last FROM that isn't `scratch` / a stage alias).\n// - EXPOSE → the ports the container listens on (`EXPOSE 8080 9090`,\n// `EXPOSE 8080/tcp`).\n// - CMD / ENTRYPOINT → the process the container runs. ENTRYPOINT wins when\n// both are present, matching Docker's precedence.\nfunction readDockerfile(content: string): DockerfileFacts {\n let image: string | null = null\n const ports: number[] = []\n let cmd: string | null = null\n let entrypoint: string | null = null\n for (const raw of content.split('\\n')) {\n const line = raw.trim()\n if (!line || line.startsWith('#')) continue\n if (/^from\\s+/i.test(line)) {\n const candidate = line.split(/\\s+/)[1]\n if (candidate && candidate.toLowerCase() !== 'scratch') image = candidate\n } else if (/^expose\\s+/i.test(line)) {\n for (const token of line.split(/\\s+/).slice(1)) {\n const port = Number.parseInt(token.split('/')[0]!, 10)\n if (Number.isInteger(port) && !ports.includes(port)) ports.push(port)\n }\n } else if (/^entrypoint\\s+/i.test(line)) {\n entrypoint = line\n } else if (/^cmd\\s+/i.test(line)) {\n cmd = line\n }\n }\n return { image, ports, entrypoint: entrypoint ?? cmd }\n}\n\n// For each ServiceNode that has a Dockerfile in its dir, emit a\n// `infra:container-image:<image>` InfraNode and a RUNS_ON edge from the\n// service to the image.\nexport async function addDockerfileRuntimes(\n graph: NeatGraph,\n services: DiscoveredService[],\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const dockerfilePath = path.join(service.dir, 'Dockerfile')\n if (!(await exists(dockerfilePath))) continue\n let content: string\n try {\n content = await fs.readFile(dockerfilePath, 'utf8')\n } catch (err) {\n recordExtractionError(\n 'infra dockerfile',\n path.relative(scanPath, dockerfilePath),\n err,\n )\n continue\n }\n const facts = readDockerfile(content)\n if (!facts.image) continue\n\n const node = makeInfraNode('container-image', facts.image)\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n\n // file-awareness §1 — the Dockerfile IS the file that declares the runtime;\n // anchor the infra edges on a FileNode for it, not on the service. The file\n // node is service-scoped, so two services that both `FROM node:20` keep\n // distinct entrypoints and exposed ports instead of colliding on the shared\n // image node.\n const relDockerfile = toPosix(path.relative(service.dir, dockerfilePath))\n const evidenceFile = toPosix(path.relative(scanPath, dockerfilePath))\n const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relDockerfile,\n )\n nodesAdded += fn\n edgesAdded += fe\n\n // RUNS_ON carries the runtime image, and — when the Dockerfile declares one\n // — the entrypoint/CMD line as evidence.snippet, so the process the\n // container runs is queryable instead of a silent fact.\n const edgeId = makeEdgeId(fileNodeId, node.id, EdgeType.RUNS_ON)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: fileNodeId,\n target: node.id,\n type: EdgeType.RUNS_ON,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: evidenceFile,\n ...(facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}),\n },\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n\n // Each EXPOSE port becomes an `infra:port:<n>` node the Dockerfile\n // CONNECTS_TO — the structural answer to \"what is this service reachable\n // at\". Without this, a declared listener is a fact only the Dockerfile\n // knows; with it, the port is a first-class node the topology can reach.\n for (const port of facts.ports) {\n const portNode = makeInfraNode('port', String(port))\n if (!graph.hasNode(portNode.id)) {\n graph.addNode(portNode.id, portNode)\n nodesAdded++\n }\n const portEdgeId = makeEdgeId(fileNodeId, portNode.id, EdgeType.CONNECTS_TO)\n if (graph.hasEdge(portEdgeId)) continue\n const portEdge: GraphEdge = {\n id: portEdgeId,\n source: fileNodeId,\n target: portNode.id,\n type: EdgeType.CONNECTS_TO,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` },\n }\n graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge)\n edgesAdded++\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { GraphEdge } from '@neat.is/types'\nimport { EdgeType, Provenance, confidenceForExtracted } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { IGNORED_DIRS, isPythonVenvDir, makeEdgeId } from '../shared.js'\nimport { toPosix } from '../calls/shared.js'\nimport { makeInfraNode } from './shared.js'\n\n// Light pass: catalogue `resource \"aws_*\" \"name\"` blocks in any *.tf file and\n// wire the references between them. We don't run a real Terraform backend, but\n// the body of every resource block names the other resources it consumes\n// (`aws_db_instance.main.endpoint`, `${aws_security_group.db.id}`, …). Those\n// `<type>.<name>` references are the structural fact that turns an edgeless\n// catalogue into a topology: a resource nothing points at is declared-but-unused,\n// a resource something points at is in use. Without this, an RDS instance the\n// app server depends on looks identical to a forgotten S3 bucket.\nconst RESOURCE_RE = /resource\\s+\"(aws_[A-Za-z0-9_]+)\"\\s+\"([A-Za-z0-9_-]+)\"/g\n// A reference to another declared resource. The negative look-behind keeps us\n// from matching the tail of a longer identifier (`my_aws_thing.x`).\nconst REFERENCE_RE = /(?<![\\w.])(aws_[A-Za-z0-9_]+)\\.([A-Za-z0-9_-]+)/g\n\ninterface TfResource {\n type: string\n name: string\n nodeId: string\n body: string\n bodyOffset: number\n}\n\nasync function walkTfFiles(start: string, depth = 0, max = 5): Promise<string[]> {\n if (depth > max) return []\n const out: string[] = []\n const entries = await fs.readdir(start, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name) || entry.name === '.terraform') continue\n const child = path.join(start, entry.name)\n if (await isPythonVenvDir(child)) continue\n out.push(...(await walkTfFiles(child, depth + 1, max)))\n } else if (entry.isFile() && entry.name.endsWith('.tf')) {\n out.push(path.join(start, entry.name))\n }\n }\n return out\n}\n\n// Find the index just past the `}` that closes the block whose opening `{`\n// sits at or after `from`. Returns the body span (exclusive of the braces).\n// Balanced-brace scan — good enough for HCL, which doesn't put bare braces in\n// string literals often enough to matter for a reference catalogue.\nfunction blockBody(content: string, from: number): { body: string; offset: number } | null {\n const open = content.indexOf('{', from)\n if (open === -1) return null\n let depth = 0\n for (let i = open; i < content.length; i++) {\n const ch = content[i]\n if (ch === '{') depth++\n else if (ch === '}') {\n depth--\n if (depth === 0) return { body: content.slice(open + 1, i), offset: open + 1 }\n }\n }\n return null\n}\n\nfunction lineAt(content: string, index: number): number {\n let line = 1\n for (let i = 0; i < index && i < content.length; i++) {\n if (content[i] === '\\n') line++\n }\n return line\n}\n\nexport async function addTerraformResources(\n graph: NeatGraph,\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n const files = await walkTfFiles(scanPath)\n for (const file of files) {\n const content = await fs.readFile(file, 'utf8')\n const evidenceFile = toPosix(path.relative(scanPath, file))\n\n // First pass: register every resource as a node and remember its body so\n // the second pass can resolve references against the set of declared names.\n const resources: TfResource[] = []\n const byKey = new Map<string, TfResource>()\n RESOURCE_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = RESOURCE_RE.exec(content)) !== null) {\n const type = m[1]!\n const name = m[2]!\n const node = makeInfraNode(type, name, 'aws')\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n const span = blockBody(content, RESOURCE_RE.lastIndex)\n const resource: TfResource = {\n type,\n name,\n nodeId: node.id,\n body: span?.body ?? '',\n bodyOffset: span?.offset ?? RESOURCE_RE.lastIndex,\n }\n resources.push(resource)\n byKey.set(`${type}.${name}`, resource)\n }\n\n // Second pass: a `<type>.<name>` reference inside a resource body that\n // names another declared resource becomes a DEPENDS_ON edge from the\n // referencing resource to the one it consumes. Structural tier (ADR-066) —\n // the HCL literally says it depends on it.\n for (const resource of resources) {\n const seen = new Set<string>()\n REFERENCE_RE.lastIndex = 0\n let ref: RegExpExecArray | null\n while ((ref = REFERENCE_RE.exec(resource.body)) !== null) {\n const key = `${ref[1]!}.${ref[2]!}`\n if (key === `${resource.type}.${resource.name}`) continue\n const target = byKey.get(key)\n if (!target) continue\n if (seen.has(target.nodeId)) continue\n seen.add(target.nodeId)\n const edgeId = makeEdgeId(resource.nodeId, target.nodeId, EdgeType.DEPENDS_ON)\n if (graph.hasEdge(edgeId)) continue\n const line = lineAt(content, resource.bodyOffset + ref.index)\n const edge: GraphEdge = {\n id: edgeId,\n source: resource.nodeId,\n target: target.nodeId,\n type: EdgeType.DEPENDS_ON,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile, line, snippet: key },\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n }\n return { nodesAdded, edgesAdded }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { parseAllDocuments } from 'yaml'\nimport type { NeatGraph } from '../../graph.js'\nimport { CONFIG_FILE_EXTENSIONS, IGNORED_DIRS, isPythonVenvDir } from '../shared.js'\nimport { makeInfraNode } from './shared.js'\n\ninterface K8sDoc {\n kind?: string\n metadata?: { name?: string; namespace?: string }\n}\n\nconst K8S_KIND_TO_INFRA_KIND: Record<string, string> = {\n Service: 'k8s-service',\n Deployment: 'k8s-deployment',\n StatefulSet: 'k8s-statefulset',\n DaemonSet: 'k8s-daemonset',\n CronJob: 'k8s-cronjob',\n Job: 'k8s-job',\n Ingress: 'k8s-ingress',\n}\n\nasync function walkYamlFiles(start: string, depth = 0, max = 5): Promise<string[]> {\n if (depth > max) return []\n const out: string[] = []\n const entries = await fs.readdir(start, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (IGNORED_DIRS.has(entry.name)) continue\n const child = path.join(start, entry.name)\n if (await isPythonVenvDir(child)) continue\n out.push(...(await walkYamlFiles(child, depth + 1, max)))\n } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path.extname(entry.name))) {\n out.push(path.join(start, entry.name))\n }\n }\n return out\n}\n\n// Multi-document YAML with kind/metadata.name. We keep the matching simple:\n// any file whose first doc looks k8s-shaped. The match is on `kind` only —\n// random YAML configs (db-config.yaml, etc.) are usually flat objects with no\n// `kind` field, so they're ignored without false positives.\nexport async function addK8sResources(\n graph: NeatGraph,\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n const files = await walkYamlFiles(scanPath)\n for (const file of files) {\n const content = await fs.readFile(file, 'utf8')\n let docs: K8sDoc[]\n try {\n docs = parseAllDocuments(content).map((d) => d.toJSON() as K8sDoc)\n } catch {\n continue\n }\n for (const doc of docs) {\n if (!doc?.kind || !doc.metadata?.name) continue\n const infraKind = K8S_KIND_TO_INFRA_KIND[doc.kind]\n if (!infraKind) continue\n const namespaced = doc.metadata.namespace\n ? `${doc.metadata.namespace}/${doc.metadata.name}`\n : doc.metadata.name\n const node = makeInfraNode(infraKind, namespaced, 'kubernetes')\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n }\n }\n return { nodesAdded, edgesAdded: 0 }\n}\n","// Cloudflare Workers/Pages extraction (ADR-133, docs/contracts/static-extraction.md).\n//\n// Reads a service's `wrangler.toml`/`wrangler.jsonc`/`wrangler.json` and stamps\n// the `platform: cloudflare` identifier every downstream consumer keys on: the\n// ServiceNode (the frontend's icon key at the service-rollup level) and the\n// Worker's entry FileNode, which also carries `platformName` — the Worker's own\n// script name, the only identifier Cloudflare's own telemetry carries and what\n// the Cloudflare connector's resolveTarget looks up against\n// (docs/contracts/connectors.md §4a).\n//\n// Declared resources (bindings, routes, cron triggers, env-var names) become\n// InfraNodes wired from the entry file, the same shape dockerfile.ts/k8s.ts/\n// docker-compose.ts already use — no new NodeType. Per-environment `[env.X]`\n// wrangler sections are out of scope for v1; only top-level config is read.\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { parse as parseToml } from 'smol-toml'\nimport type { FileNode, GraphEdge, ServiceNode } from '@neat.is/types'\nimport { EdgeType, EdgeTypeValue, Provenance, confidenceForExtracted } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { exists, maskCommentsInSource, makeEdgeId, type DiscoveredService } from '../shared.js'\nimport { recordExtractionError } from '../errors.js'\nimport { ensureFileNode, toPosix } from '../calls/shared.js'\nimport { makeInfraNode } from './shared.js'\n\ninterface WranglerBindingLike {\n binding?: string\n name?: string\n queue?: string\n}\n\ninterface WranglerRouteLike {\n pattern?: string\n}\n\ninterface WranglerServiceBinding {\n binding?: string\n service?: string\n}\n\ninterface WranglerConfig {\n name?: string\n main?: string\n compatibility_date?: string\n routes?: (string | WranglerRouteLike)[]\n route?: string | WranglerRouteLike\n kv_namespaces?: WranglerBindingLike[]\n d1_databases?: WranglerBindingLike[]\n r2_buckets?: WranglerBindingLike[]\n durable_objects?: { bindings?: WranglerBindingLike[] }\n queues?: { producers?: WranglerBindingLike[]; consumers?: WranglerBindingLike[] }\n triggers?: { crons?: string[] }\n services?: WranglerServiceBinding[]\n vars?: Record<string, unknown>\n}\n\ninterface WranglerRead {\n config: WranglerConfig\n relFile: string\n raw: string\n}\n\nconst WRANGLER_FILENAMES = ['wrangler.toml', 'wrangler.jsonc', 'wrangler.json']\n\nasync function readWranglerConfig(dir: string): Promise<WranglerRead | null> {\n for (const filename of WRANGLER_FILENAMES) {\n const abs = path.join(dir, filename)\n if (!(await exists(abs))) continue\n const raw = await fs.readFile(abs, 'utf8')\n const config =\n filename === 'wrangler.toml'\n ? (parseToml(raw) as unknown as WranglerConfig)\n : (JSON.parse(maskCommentsInSource(raw)) as WranglerConfig)\n return { config, relFile: filename, raw }\n }\n return null\n}\n\n// Best-effort 1-indexed line for a declared value — a simple text search, the\n// same \"read config as data\" discipline `proto.ts`'s brace-balanced scan and\n// `terraform.ts`'s `lineAt` already use rather than a real TOML/JSONC AST.\nfunction lineContaining(raw: string, needle: string | undefined): number | undefined {\n if (!needle) return undefined\n const idx = raw.indexOf(needle)\n if (idx === -1) return undefined\n let line = 1\n for (let i = 0; i < idx; i++) if (raw[i] === '\\n') line++\n return line\n}\n\nfunction normalizeRoutes(config: WranglerConfig): string[] {\n const out: string[] = []\n const pushOne = (r: unknown): void => {\n if (typeof r === 'string') out.push(r)\n else if (r && typeof r === 'object' && typeof (r as WranglerRouteLike).pattern === 'string') {\n out.push((r as WranglerRouteLike).pattern!)\n }\n }\n if (Array.isArray(config.routes)) config.routes.forEach(pushOne)\n else if (config.route) pushOne(config.route)\n return out\n}\n\n// One declared-resource InfraNode + edge from the entry anchor (FileNode, or\n// the ServiceNode itself when no entry file resolved — the honest\n// service-level fallback file-awareness.md already names for this case).\nfunction addResourceEdge(\n graph: NeatGraph,\n anchorId: string,\n edgeType: EdgeTypeValue,\n kind: string,\n name: string,\n evidenceFile: string,\n line: number | undefined,\n): { nodesAdded: number; edgesAdded: number } {\n let nodesAdded = 0\n let edgesAdded = 0\n const node = makeInfraNode(kind, name, 'cloudflare')\n if (!graph.hasNode(node.id)) {\n graph.addNode(node.id, node)\n nodesAdded++\n }\n if (node.id === anchorId) return { nodesAdded, edgesAdded } // no self-loops\n const edgeId = makeEdgeId(anchorId, node.id, edgeType)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: anchorId,\n target: node.id,\n type: edgeType,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile, ...(line !== undefined ? { line } : {}) },\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n return { nodesAdded, edgesAdded }\n}\n\ninterface DiscoveredWorker {\n service: DiscoveredService\n config: WranglerConfig\n relFile: string\n raw: string\n evidenceFile: string\n}\n\nexport async function addCloudflareWorkers(\n graph: NeatGraph,\n services: DiscoveredService[],\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n // Pass 1 — read every service's wrangler config (if any) before writing a\n // single node, so pass 2's service-binding resolution sees the full\n // worker-name → entry-file map for this scan, same discipline `route-match.ts`\n // uses running routes before calls.\n const discovered: DiscoveredWorker[] = []\n const workerIndex = new Map<string, { anchorId: string }>()\n\n for (const service of services) {\n let read: WranglerRead | null\n try {\n read = await readWranglerConfig(service.dir)\n } catch (err) {\n recordExtractionError('infra cloudflare', path.relative(scanPath, service.dir), err)\n continue\n }\n if (!read || !read.config.name) continue\n\n const evidenceFile = toPosix(path.relative(scanPath, path.join(service.dir, read.relFile)))\n discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile })\n }\n\n // Tag every ServiceNode + entry FileNode first, populating workerIndex, so\n // pass 2's service-binding resolution can target a real FileNode even when\n // the referenced Worker is later in `services` than the one declaring the\n // binding.\n for (const worker of discovered) {\n const { service, config } = worker\n\n const serviceNode = graph.getNodeAttributes(service.node.id) as ServiceNode\n if (serviceNode.platform !== 'cloudflare') {\n const updated: ServiceNode = { ...serviceNode, platform: 'cloudflare' }\n graph.replaceNodeAttributes(service.node.id, updated)\n }\n\n let anchorId = service.node.id\n if (config.main) {\n const entryRelPath = toPosix(path.normalize(config.main))\n const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n entryRelPath,\n )\n nodesAdded += fn\n edgesAdded += fe\n const fileNode = graph.getNodeAttributes(fileNodeId) as FileNode\n if (fileNode.platform !== 'cloudflare' || fileNode.platformName !== config.name) {\n const updated: FileNode = { ...fileNode, platform: 'cloudflare', platformName: config.name }\n graph.replaceNodeAttributes(fileNodeId, updated)\n }\n anchorId = fileNodeId\n }\n\n workerIndex.set(config.name!, { anchorId })\n }\n\n // Pass 2 — declared resources, wired from each worker's anchor (entry\n // FileNode, or the ServiceNode when `main` was absent).\n for (const worker of discovered) {\n const { config, evidenceFile, raw } = worker\n const anchorId = workerIndex.get(config.name!)!.anchorId\n\n // Runtime marker — one shared node every Cloudflare Worker in this scan\n // RUNS_ON, compat date carried as evidence.snippet (mirrors dockerfile.ts's\n // image-node + entrypoint-snippet pattern).\n const runtimeNode = makeInfraNode('workerd', 'cloudflare', 'cloudflare')\n if (!graph.hasNode(runtimeNode.id)) {\n graph.addNode(runtimeNode.id, runtimeNode)\n nodesAdded++\n }\n if (runtimeNode.id !== anchorId) {\n const runsOnId = makeEdgeId(anchorId, runtimeNode.id, EdgeType.RUNS_ON)\n if (!graph.hasEdge(runsOnId)) {\n const edge: GraphEdge = {\n id: runsOnId,\n source: anchorId,\n target: runtimeNode.id,\n type: EdgeType.RUNS_ON,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: {\n file: evidenceFile,\n ...(config.compatibility_date\n ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) }\n : {}),\n },\n }\n graph.addEdgeWithKey(runsOnId, edge.source, edge.target, edge)\n edgesAdded++\n }\n }\n\n // Routes / custom domains — network-reachability, same CONNECTS_TO\n // semantics dockerfile.ts's EXPOSE→port edge already uses.\n for (const route of normalizeRoutes(config)) {\n const result = addResourceEdge(\n graph,\n anchorId,\n EdgeType.CONNECTS_TO,\n 'cloudflare-route',\n route,\n evidenceFile,\n lineContaining(raw, route),\n )\n nodesAdded += result.nodesAdded\n edgesAdded += result.edgesAdded\n }\n\n // Bindings — declared runtime dependencies, DEPENDS_ON.\n const bindingGroups: { kind: string; entries: WranglerBindingLike[]; nameOf: (b: WranglerBindingLike) => string | undefined }[] = [\n { kind: 'cloudflare-kv', entries: config.kv_namespaces ?? [], nameOf: (b) => b.binding },\n { kind: 'cloudflare-d1', entries: config.d1_databases ?? [], nameOf: (b) => b.binding },\n { kind: 'cloudflare-r2', entries: config.r2_buckets ?? [], nameOf: (b) => b.binding },\n { kind: 'cloudflare-durable-object', entries: config.durable_objects?.bindings ?? [], nameOf: (b) => b.name },\n { kind: 'cloudflare-queue', entries: config.queues?.producers ?? [], nameOf: (b) => b.queue },\n { kind: 'cloudflare-queue', entries: config.queues?.consumers ?? [], nameOf: (b) => b.queue },\n ]\n for (const group of bindingGroups) {\n for (const entry of group.entries) {\n const name = group.nameOf(entry)\n if (!name) continue\n const result = addResourceEdge(\n graph,\n anchorId,\n EdgeType.DEPENDS_ON,\n group.kind,\n name,\n evidenceFile,\n lineContaining(raw, name),\n )\n nodesAdded += result.nodesAdded\n edgesAdded += result.edgesAdded\n }\n }\n\n // Cron triggers.\n for (const cron of config.triggers?.crons ?? []) {\n const result = addResourceEdge(\n graph,\n anchorId,\n EdgeType.DEPENDS_ON,\n 'cloudflare-cron',\n cron,\n evidenceFile,\n lineContaining(raw, cron),\n )\n nodesAdded += result.nodesAdded\n edgesAdded += result.edgesAdded\n }\n\n // Declared env vars — name only, value never read (ADR-016 spirit).\n for (const varName of Object.keys(config.vars ?? {})) {\n const result = addResourceEdge(\n graph,\n anchorId,\n EdgeType.DEPENDS_ON,\n 'cloudflare-env-var',\n varName,\n evidenceFile,\n lineContaining(raw, varName),\n )\n nodesAdded += result.nodesAdded\n edgesAdded += result.edgesAdded\n }\n\n // Service bindings — resolve directly onto the target Worker's own entry\n // node when it's tagged in this same scan (CALLS: this Worker can invoke\n // that one); otherwise an honest InfraNode fallback, never guessed.\n for (const svc of config.services ?? []) {\n if (!svc.service) continue\n const target = workerIndex.get(svc.service)\n if (target && target.anchorId !== anchorId) {\n const edgeId = makeEdgeId(anchorId, target.anchorId, EdgeType.CALLS)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: anchorId,\n target: target.anchorId,\n type: EdgeType.CALLS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: { file: evidenceFile, line: lineContaining(raw, svc.service) },\n }\n graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge)\n edgesAdded++\n }\n continue\n }\n const result = addResourceEdge(\n graph,\n anchorId,\n EdgeType.DEPENDS_ON,\n 'cloudflare-service-binding',\n svc.service,\n evidenceFile,\n lineContaining(raw, svc.service),\n )\n nodesAdded += result.nodesAdded\n edgesAdded += result.edgesAdded\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","// Vercel project extraction. Stamps the `platform: vercel` identifier the\n// frontend service-rollup badge keys on (static-extraction.md, mirroring ADR-133\n// for Cloudflare) whenever a service carries a `vercel.json`/`vercel.jsonc` or a\n// linked `.vercel/project.json`, and models the config's declared resources —\n// crons, env-var names, routes/rewrites — as InfraNodes wired from the\n// ServiceNode. No new NodeType; env-var values are never read (ADR-016 spirit),\n// only names. Vercel apps have no single Worker-style entry file, so the tag and\n// the resource edges anchor on the ServiceNode itself.\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport type { ServiceNode } from '@neat.is/types'\nimport { EdgeType, EdgeTypeValue } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { exists, maskCommentsInSource, type DiscoveredService } from '../shared.js'\nimport { recordExtractionError } from '../errors.js'\nimport { toPosix } from '../calls/shared.js'\nimport { emitPlatformResourceEdge, lineContaining } from './shared.js'\n\ninterface VercelCron {\n path?: string\n schedule?: string\n}\n\ninterface VercelRoute {\n source?: string\n src?: string\n}\n\ninterface VercelConfig {\n crons?: VercelCron[]\n env?: Record<string, unknown>\n build?: { env?: Record<string, unknown> }\n rewrites?: VercelRoute[]\n redirects?: VercelRoute[]\n routes?: VercelRoute[]\n}\n\nconst VERCEL_CONFIG_FILENAMES = ['vercel.json', 'vercel.jsonc']\n\ninterface VercelRead {\n config: VercelConfig\n relFile: string\n raw: string\n}\n\nasync function readVercelConfig(dir: string): Promise<VercelRead | null> {\n for (const filename of VERCEL_CONFIG_FILENAMES) {\n const abs = path.join(dir, filename)\n if (!(await exists(abs))) continue\n const raw = await fs.readFile(abs, 'utf8')\n const config = JSON.parse(maskCommentsInSource(raw)) as VercelConfig\n return { config, relFile: filename, raw }\n }\n return null\n}\n\n// `vercel link` writes `.vercel/project.json` — a strong platform signal even\n// when the repo carries no vercel.json, and the only place the human-readable\n// project name lives (the equivalent of a Worker's script name).\nasync function readLinkedProjectName(dir: string): Promise<string | undefined> {\n const abs = path.join(dir, '.vercel', 'project.json')\n if (!(await exists(abs))) return undefined\n const parsed = JSON.parse(await fs.readFile(abs, 'utf8')) as { projectName?: unknown }\n return typeof parsed.projectName === 'string' ? parsed.projectName : undefined\n}\n\nfunction routeSource(route: VercelRoute): string | undefined {\n return route.source ?? route.src\n}\n\nexport async function addVercelServices(\n graph: NeatGraph,\n services: DiscoveredService[],\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n let read: VercelRead | null = null\n let projectName: string | undefined\n try {\n read = await readVercelConfig(service.dir)\n projectName = await readLinkedProjectName(service.dir)\n } catch (err) {\n recordExtractionError('infra vercel', path.relative(scanPath, service.dir), err)\n continue\n }\n // A Vercel service is one with a vercel.json or a linked .vercel/project.json.\n if (!read && !projectName) continue\n\n const serviceNode = graph.getNodeAttributes(service.node.id) as ServiceNode\n if (serviceNode.platform !== 'vercel' || (projectName && serviceNode.platformName !== projectName)) {\n const updated: ServiceNode = {\n ...serviceNode,\n platform: 'vercel',\n ...(projectName ? { platformName: projectName } : {}),\n }\n graph.replaceNodeAttributes(service.node.id, updated)\n }\n\n const anchorId = service.node.id\n if (!read) continue // linked but no config file to mine resources from\n const { config, relFile, raw } = read\n const evidenceFile = toPosix(path.relative(scanPath, path.join(service.dir, relFile)))\n\n const add = (edgeType: EdgeTypeValue, kind: string, name: string | undefined): void => {\n if (!name) return\n const result = emitPlatformResourceEdge(\n graph,\n anchorId,\n edgeType,\n kind,\n name,\n 'vercel',\n evidenceFile,\n lineContaining(raw, name),\n )\n nodesAdded += result.nodesAdded\n edgesAdded += result.edgesAdded\n }\n\n // Runtime marker — one shared node every Vercel service RUNS_ON.\n add(EdgeType.RUNS_ON, 'vercel', 'vercel')\n\n // Scheduled functions.\n for (const cron of config.crons ?? []) add(EdgeType.DEPENDS_ON, 'vercel-cron', cron.path ?? cron.schedule)\n\n // Declared env vars — names only, value never read.\n for (const varName of Object.keys(config.env ?? {})) add(EdgeType.DEPENDS_ON, 'vercel-env-var', varName)\n for (const varName of Object.keys(config.build?.env ?? {})) add(EdgeType.DEPENDS_ON, 'vercel-env-var', varName)\n\n // Routes / rewrites / redirects — network-reachability, CONNECTS_TO.\n for (const route of [...(config.rewrites ?? []), ...(config.redirects ?? []), ...(config.routes ?? [])]) {\n add(EdgeType.CONNECTS_TO, 'vercel-route', routeSource(route))\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","// Railway service extraction. Stamps the `platform: railway` identifier the\n// frontend service-rollup badge keys on (mirroring ADR-133 for Cloudflare) when a\n// service carries a `railway.toml`/`railway.json`/`railway.jsonc`, and models the\n// config's declared surfaces — the healthcheck path and a cron schedule — as\n// InfraNodes wired from the ServiceNode. No new NodeType. Railway's config names\n// no service (that lives in Railway's own system, which the connector resolves by\n// deploymentId), so no platformName is stamped here.\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { parse as parseToml } from 'smol-toml'\nimport type { ServiceNode } from '@neat.is/types'\nimport { EdgeType, EdgeTypeValue } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { exists, maskCommentsInSource, type DiscoveredService } from '../shared.js'\nimport { recordExtractionError } from '../errors.js'\nimport { toPosix } from '../calls/shared.js'\nimport { emitPlatformResourceEdge, lineContaining } from './shared.js'\n\ninterface RailwayDeploy {\n healthcheckPath?: string\n cronSchedule?: string\n}\n\ninterface RailwayConfig {\n deploy?: RailwayDeploy\n}\n\nconst RAILWAY_FILENAMES = ['railway.toml', 'railway.json', 'railway.jsonc']\n\ninterface RailwayRead {\n config: RailwayConfig\n relFile: string\n raw: string\n}\n\nasync function readRailwayConfig(dir: string): Promise<RailwayRead | null> {\n for (const filename of RAILWAY_FILENAMES) {\n const abs = path.join(dir, filename)\n if (!(await exists(abs))) continue\n const raw = await fs.readFile(abs, 'utf8')\n const config =\n filename === 'railway.toml'\n ? (parseToml(raw) as unknown as RailwayConfig)\n : (JSON.parse(maskCommentsInSource(raw)) as RailwayConfig)\n return { config, relFile: filename, raw }\n }\n return null\n}\n\nexport async function addRailwayServices(\n graph: NeatGraph,\n services: DiscoveredService[],\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n let read: RailwayRead | null = null\n try {\n read = await readRailwayConfig(service.dir)\n } catch (err) {\n recordExtractionError('infra railway', path.relative(scanPath, service.dir), err)\n continue\n }\n if (!read) continue\n\n const serviceNode = graph.getNodeAttributes(service.node.id) as ServiceNode\n if (serviceNode.platform !== 'railway') {\n graph.replaceNodeAttributes(service.node.id, { ...serviceNode, platform: 'railway' })\n }\n\n const anchorId = service.node.id\n const { config, relFile, raw } = read\n const evidenceFile = toPosix(path.relative(scanPath, path.join(service.dir, relFile)))\n\n const add = (edgeType: EdgeTypeValue, kind: string, name: string | undefined): void => {\n if (!name) return\n const result = emitPlatformResourceEdge(\n graph,\n anchorId,\n edgeType,\n kind,\n name,\n 'railway',\n evidenceFile,\n lineContaining(raw, name),\n )\n nodesAdded += result.nodesAdded\n edgesAdded += result.edgesAdded\n }\n\n add(EdgeType.RUNS_ON, 'railway', 'railway')\n add(EdgeType.CONNECTS_TO, 'railway-route', config.deploy?.healthcheckPath)\n add(EdgeType.DEPENDS_ON, 'railway-cron', config.deploy?.cronSchedule)\n }\n\n return { nodesAdded, edgesAdded }\n}\n","// Supabase project extraction. Stamps the `platform: supabase` identifier the\n// frontend service-rollup badge keys on (mirroring ADR-133 for Cloudflare) when a\n// service carries a `supabase/config.toml`, using the config's `project_id` as\n// platformName (the ref the Supabase connector resolves against), and models the\n// declared surfaces — edge functions, storage, auth — as InfraNodes wired from\n// the ServiceNode. No new NodeType.\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { parse as parseToml } from 'smol-toml'\nimport type { ServiceNode } from '@neat.is/types'\nimport { EdgeType, EdgeTypeValue } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { exists, type DiscoveredService } from '../shared.js'\nimport { recordExtractionError } from '../errors.js'\nimport { toPosix } from '../calls/shared.js'\nimport { emitPlatformResourceEdge, lineContaining } from './shared.js'\n\ninterface SupabaseConfig {\n project_id?: string\n functions?: Record<string, unknown>\n storage?: Record<string, unknown>\n auth?: Record<string, unknown>\n}\n\ninterface SupabaseRead {\n config: SupabaseConfig\n relFile: string\n raw: string\n}\n\n// Supabase's config lives at <service>/supabase/config.toml, not the service root.\nasync function readSupabaseConfig(dir: string): Promise<SupabaseRead | null> {\n const relFile = path.join('supabase', 'config.toml')\n const abs = path.join(dir, relFile)\n if (!(await exists(abs))) return null\n const raw = await fs.readFile(abs, 'utf8')\n const config = parseToml(raw) as unknown as SupabaseConfig\n return { config, relFile, raw }\n}\n\nexport async function addSupabaseProjects(\n graph: NeatGraph,\n services: DiscoveredService[],\n scanPath: string,\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n let read: SupabaseRead | null = null\n try {\n read = await readSupabaseConfig(service.dir)\n } catch (err) {\n recordExtractionError('infra supabase', path.relative(scanPath, service.dir), err)\n continue\n }\n if (!read) continue\n\n const { config, relFile, raw } = read\n const projectId = typeof config.project_id === 'string' ? config.project_id : undefined\n\n const serviceNode = graph.getNodeAttributes(service.node.id) as ServiceNode\n if (serviceNode.platform !== 'supabase' || (projectId && serviceNode.platformName !== projectId)) {\n graph.replaceNodeAttributes(service.node.id, {\n ...serviceNode,\n platform: 'supabase',\n ...(projectId ? { platformName: projectId } : {}),\n })\n }\n\n const anchorId = service.node.id\n const evidenceFile = toPosix(path.relative(scanPath, path.join(service.dir, relFile)))\n\n const add = (edgeType: EdgeTypeValue, kind: string, name: string | undefined): void => {\n if (!name) return\n const result = emitPlatformResourceEdge(\n graph,\n anchorId,\n edgeType,\n kind,\n name,\n 'supabase',\n evidenceFile,\n lineContaining(raw, name),\n )\n nodesAdded += result.nodesAdded\n edgesAdded += result.edgesAdded\n }\n\n add(EdgeType.RUNS_ON, 'supabase', 'supabase')\n // Edge functions — each [functions.X] section becomes a declared function.\n for (const fn of Object.keys(config.functions ?? {})) add(EdgeType.DEPENDS_ON, 'supabase-function', fn)\n // Managed surfaces the project declares.\n if (config.storage) add(EdgeType.DEPENDS_ON, 'supabase-storage', 'storage')\n if (config.auth) add(EdgeType.DEPENDS_ON, 'supabase-auth', 'auth')\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import type { NeatGraph } from '../../graph.js'\nimport { type DiscoveredService } from '../shared.js'\nimport { addComposeInfra } from './docker-compose.js'\nimport { addDockerfileRuntimes } from './dockerfile.js'\nimport { addTerraformResources } from './terraform.js'\nimport { addK8sResources } from './k8s.js'\nimport { addCloudflareWorkers } from './cloudflare.js'\nimport { addVercelServices } from './vercel.js'\nimport { addRailwayServices } from './railway.js'\nimport { addSupabaseProjects } from './supabase.js'\n\nexport interface InfraExtractResult {\n nodesAdded: number\n edgesAdded: number\n}\n\n// Phase 5 — infrastructure. Runs after services so RUNS_ON edges have a\n// ServiceNode to anchor on. Each sub-source contributes its own nodes/edges\n// independently. The platform producers (`cloudflare.ts`, `vercel.ts`,\n// `railway.ts`, `supabase.ts`) also tag existing ServiceNode/FileNode\n// attributes (`platform`/`platformName`, ADR-133) rather than only adding new\n// nodes/edges — property updates on existing nodes are allowed per ADR-030.\nexport async function addInfra(\n graph: NeatGraph,\n scanPath: string,\n services: DiscoveredService[],\n): Promise<InfraExtractResult> {\n const compose = await addComposeInfra(graph, scanPath, services)\n const dockerfile = await addDockerfileRuntimes(graph, services, scanPath)\n const terraform = await addTerraformResources(graph, scanPath)\n const k8s = await addK8sResources(graph, scanPath)\n const cloudflare = await addCloudflareWorkers(graph, services, scanPath)\n const vercel = await addVercelServices(graph, services, scanPath)\n const railway = await addRailwayServices(graph, services, scanPath)\n const supabase = await addSupabaseProjects(graph, services, scanPath)\n\n return {\n nodesAdded:\n compose.nodesAdded +\n dockerfile.nodesAdded +\n terraform.nodesAdded +\n k8s.nodesAdded +\n cloudflare.nodesAdded +\n vercel.nodesAdded +\n railway.nodesAdded +\n supabase.nodesAdded,\n edgesAdded:\n compose.edgesAdded +\n dockerfile.edgesAdded +\n terraform.edgesAdded +\n k8s.edgesAdded +\n cloudflare.edgesAdded +\n vercel.edgesAdded +\n railway.edgesAdded +\n supabase.edgesAdded,\n }\n}\n","import path from 'node:path'\nimport Parser from 'tree-sitter'\nimport JavaScript from 'tree-sitter-javascript'\nimport type { EdgeEvidence, GraphEdge, InfraNode } from '@neat.is/types'\nimport {\n EdgeType,\n NodeType,\n Provenance,\n confidenceForExtracted,\n extractedEdgeId,\n infraId,\n} from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport { isTestPath, type DiscoveredService } from './shared.js'\nimport { recordExtractionError } from './errors.js'\nimport { GRAMMAR_BY_EXT, parseSource } from './symbols.js'\nimport {\n ensureFileNode,\n loadSourceFiles,\n snippet,\n toPosix,\n type SourceFile,\n} from './calls/shared.js'\nimport { foldColumns } from '../columns.js'\n\n// Zod-as-contract declared-shape recognizer (ADR-170). For an app that treats\n// Zod as the source of truth, the declared shape is invisible to the graph — a\n// `const UserSchema` bound to a `z.object` shape is not even a SymbolNode\n// (`collectSymbolDefs` mints `const` only for arrow / function values, not call\n// expressions). This producer reads the top-level `z.object` / `z.enum` shape\n// literals and mints each as an `InfraNode` of kind `zod-schema`, its fields\n// folded on as `ColumnAttr` — the same column grain a `sql-table` node carries\n// (ADR-157 §3), so a future declared-vs-observed field comparison has a static\n// twin to land on.\n//\n// A module-level const UserSchema = z.object whose body is\n// { id: z.string(), age: z.number() } becomes an InfraNode\n// infra:zod-schema:UserSchema with columns [id, age].\n//\n// A dedicated `zod-schema` kind — NOT a new NodeType and NOT a SymbolKind\n// (minting Zod consts as symbols would ripple into symbol-edges.ts, symbol-grain\n// OBSERVED fusion, and divergences.ts). `kind` is an open string, so this is a\n// zero-schema-version change, and the read is EXTRACTED-only (no OBSERVED fusion:\n// a runtime parse failure is not observed at field grain today).\n//\n// Scope is deliberately narrow — only a literal top-level `z.object` or `z.enum`\n// shape bound directly to a module-level `const`. Composed / computed forms\n// (`.extend`, `.merge`, `.pick`, unions, refinements, spreads, computed keys)\n// name a partial or dynamic field set, so claiming them would misrepresent the\n// contract — they are left unclaimed rather than guessed, a follow-on rung.\n// Object-literal keys are read the way `calls/drizzle.ts` `columnsFromObject`\n// reads a table's second-argument object.\n//\n// NOTE: comments here name the forms as `z.object` / `z.enum` without a trailing\n// call paren on purpose — the Rule-5 audit (contracts.test.ts) forbids the literal\n// `z.<object|enum>(` token in core/mcp src to stop schema *definitions* leaking in.\n// This file recognizes those shapes, it does not define any.\n\nconst ZOD_IMPORT_RE = /\\bzod\\b/\n// The conventional binding names for the Zod namespace: `import { z } from 'zod'`\n// and `import * as z from 'zod'` (or `zod`). Any other alias is left unclaimed.\nconst ZOD_OBJECTS = new Set(['z', 'zod'])\n\n// A schema recognized in one file: the InfraNode id it implies, its display\n// name, and the declared top-level field set at code-name fidelity.\nexport interface ZodShape {\n infraId: string\n name: string\n fields: string[]\n evidence: EdgeEvidence\n}\n\nfunction parserForExt(ext: string): Parser {\n const p = new Parser()\n p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript)\n return p\n}\n\nfunction namedChildren(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const out: Parser.SyntaxNode[] = []\n for (let i = 0; i < node.namedChildCount; i++) {\n const c = node.namedChild(i)\n if (c) out.push(c)\n }\n return out\n}\n\n// The literal text inside a `string` node (quotes stripped). Null for a template\n// / interpolated string — never guessed at. (Cloned from calls/drizzle.ts.)\nfunction stringLiteralText(node: Parser.SyntaxNode | null): string | null {\n if (!node || node.type !== 'string') return null\n for (const child of namedChildren(node)) {\n if (child.type === 'string_fragment') return child.text\n }\n return '' // an empty '' literal has no string_fragment child\n}\n\n// A pair key → its field-name string (a bare `id:` or a quoted `'id':`).\nfunction keyName(key: Parser.SyntaxNode | null): string | null {\n if (!key) return null\n if (key.type === 'property_identifier') return key.text\n if (key.type === 'string') return stringLiteralText(key)\n return null\n}\n\n// `z.object` / `z.enum` classification for a call, or null when the callee is\n// not a `<z|zod>.object` / `<z|zod>.enum` member expression.\nfunction zodCallKind(call: Parser.SyntaxNode): 'object' | 'enum' | null {\n const fn = call.childForFieldName('function')\n if (fn?.type !== 'member_expression') return null\n const obj = fn.childForFieldName('object')\n const prop = fn.childForFieldName('property')\n if (obj?.type !== 'identifier' || !ZOD_OBJECTS.has(obj.text)) return null\n if (prop?.type !== 'property_identifier') return null\n if (prop.text === 'object') return 'object'\n if (prop.text === 'enum') return 'enum'\n return null\n}\n\n// The first positional argument node of a call, or null.\nfunction firstArg(call: Parser.SyntaxNode): Parser.SyntaxNode | null {\n const args = call.childForFieldName('arguments')\n if (!args) return null\n return namedChildren(args)[0] ?? null\n}\n\n// The declared field names of a `z.object` shape literal, or null when the\n// object is a composed / computed form (a spread or a computed key) — those name\n// a partial or dynamic set, so the whole schema is left unclaimed.\nfunction fieldsFromObject(obj: Parser.SyntaxNode): string[] | null {\n const out: string[] = []\n const seen = new Set<string>()\n for (const child of namedChildren(obj)) {\n if (child.type === 'spread_element') return null\n if (child.type !== 'pair') continue\n const key = child.childForFieldName('key')\n if (key?.type === 'computed_property_name') return null\n const name = keyName(key)\n if (name && !seen.has(name)) {\n seen.add(name)\n out.push(name)\n }\n }\n return out\n}\n\n// The declared member set of a `z.enum` shape literal, or null when a member is\n// not a plain string literal (a spread or a computed value) — left unclaimed.\nfunction membersFromEnum(arr: Parser.SyntaxNode): string[] | null {\n const out: string[] = []\n const seen = new Set<string>()\n for (const child of namedChildren(arr)) {\n if (child.type !== 'string') return null\n const value = stringLiteralText(child)\n if (value !== null && value !== '' && !seen.has(value)) {\n seen.add(value)\n out.push(value)\n }\n }\n return out.length > 0 ? out : null\n}\n\n// Every top-level `const <Name>` bound directly to a `z.object` / `z.enum` shape\n// literal in the program, as its schema name and the bound call node. Direct\n// binding is the claim gate: a `.extend` / `.merge` wrapper makes the declarator's\n// value the wrapping call, not the recognized shape call, so a composed form never\n// matches.\nfunction topLevelSchemas(root: Parser.SyntaxNode): { name: string; call: Parser.SyntaxNode }[] {\n const out: { name: string; call: Parser.SyntaxNode }[] = []\n for (const stmt of namedChildren(root)) {\n // Unwrap `export const …` to the declaration it fronts.\n const decl = stmt.type === 'export_statement' ? stmt.childForFieldName('declaration') : stmt\n if (decl?.type !== 'lexical_declaration' && decl?.type !== 'variable_declaration') continue\n for (const declarator of namedChildren(decl)) {\n if (declarator.type !== 'variable_declarator') continue\n const name = declarator.childForFieldName('name')\n const value = declarator.childForFieldName('value')\n if (name?.type !== 'identifier' || value?.type !== 'call_expression') continue\n if (zodCallKind(value) === null) continue\n out.push({ name: name.text, call: value })\n }\n }\n return out\n}\n\nexport function zodShapesFromFile(file: SourceFile, serviceDir: string): ZodShape[] {\n if (!ZOD_IMPORT_RE.test(file.content)) return []\n const tree = parseSource(parserForExt(path.extname(file.path)), file.content)\n const out: ZodShape[] = []\n const seen = new Set<string>()\n\n for (const { name, call } of topLevelSchemas(tree.rootNode)) {\n const kind = zodCallKind(call)\n const arg = firstArg(call)\n let fields: string[] | null = null\n if (kind === 'object' && arg?.type === 'object') fields = fieldsFromObject(arg)\n else if (kind === 'enum' && arg?.type === 'array') fields = membersFromEnum(arg)\n // A composed / computed form (fields === null) or a non-literal argument is\n // left unclaimed — no node, rather than a partial contract.\n if (fields === null) continue\n if (seen.has(name)) continue\n seen.add(name)\n const line = call.startPosition.row + 1\n out.push({\n infraId: infraId('zod-schema', name),\n name,\n fields,\n evidence: {\n file: path.relative(serviceDir, file.path),\n line,\n snippet: snippet(file.content, line),\n },\n })\n }\n return out\n}\n\nexport interface ZodExtractResult {\n nodesAdded: number\n edgesAdded: number\n}\n\n// Standalone extraction phase (registered in extract/index.ts). Gated per service\n// on the `zod` dependency — a service that doesn't declare Zod is skipped. Each\n// recognized schema mints an `infra:zod-schema:<name>` InfraNode with its fields\n// folded on as EXTRACTED `ColumnAttr` (via the read-only `foldColumns`), owned by\n// its file through a `file ──CONTAINS──▶ zod-schema` edge — the same containment\n// spine symbols use under files (file-awareness.md §2). CONTAINS is structural,\n// divergence-excluded, so the node carries no spurious `missing-observed` finding.\nexport async function addZodShapes(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<ZodExtractResult> {\n let nodesAdded = 0\n let edgesAdded = 0\n\n for (const service of services) {\n const deps = {\n ...(service.pkg.dependencies ?? {}),\n ...(service.pkg.devDependencies ?? {}),\n }\n if (deps['zod'] === undefined) continue\n\n const files = await loadSourceFiles(service.dir)\n for (const file of files) {\n // Test scope is excluded from outbound inference the same way the CALLS\n // producers exclude it (ADR-065 #1) — a fixture schema is not app surface.\n if (isTestPath(file.path)) continue\n let shapes: ZodShape[]\n try {\n shapes = zodShapesFromFile(file, service.dir)\n } catch (err) {\n recordExtractionError('zod shape extraction', file.path, err)\n continue\n }\n for (const shape of shapes) {\n if (!graph.hasNode(shape.infraId)) {\n const node: InfraNode = {\n id: shape.infraId,\n type: NodeType.InfraNode,\n name: shape.name,\n provider: 'self',\n kind: 'zod-schema',\n }\n graph.addNode(node.id, node)\n nodesAdded++\n }\n\n // Fold the declared fields onto the node as EXTRACTED columns (ADR-157 §3\n // grain), read-only through `foldColumns`. Mutation is allowed here —\n // extract/* is a lifecycle authority (lifecycle.md §3).\n if (shape.fields.length > 0) {\n const node = graph.getNodeAttributes(shape.infraId) as InfraNode\n if (node.type === NodeType.InfraNode) {\n graph.replaceNodeAttributes(shape.infraId, {\n ...node,\n columns: foldColumns(\n node.columns,\n shape.fields,\n Provenance.EXTRACTED,\n confidenceForExtracted('structural'),\n ),\n })\n }\n }\n\n // File-first ownership (file-awareness.md §1–2): the schema is owned by\n // the file it is declared in, through a `file ──CONTAINS──▶ zod-schema`\n // edge alongside the `service ──CONTAINS──▶ file` edge ensureFileNode mints.\n const relFile = toPosix(shape.evidence.file)\n const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(\n graph,\n service.pkg.name,\n service.node.id,\n relFile,\n )\n nodesAdded += n\n edgesAdded += e\n const edgeId = extractedEdgeId(fileNodeId, shape.infraId, EdgeType.CONTAINS)\n if (!graph.hasEdge(edgeId)) {\n const edge: GraphEdge = {\n id: edgeId,\n source: fileNodeId,\n target: shape.infraId,\n type: EdgeType.CONTAINS,\n provenance: Provenance.EXTRACTED,\n confidence: confidenceForExtracted('structural'),\n evidence: shape.evidence,\n }\n graph.addEdgeWithKey(edgeId, fileNodeId, shape.infraId, edge)\n edgesAdded++\n }\n }\n }\n }\n\n return { nodesAdded, edgesAdded }\n}\n","import type { GraphNode, InfraNode } from '@neat.is/types'\nimport { NodeType } from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport type { DiscoveredService } from './shared.js'\nimport { findFirst, readIfExists } from './databases/shared.js'\n\n// firestore.rules guard-set producer (ADR-169). A **standalone** extract phase —\n// not a `calls/` producer — that reads each service's checked-in `firestore.rules`\n// as a declared artifact and folds a `guardedFields` set onto the\n// `firestore-collection` InfraNodes the F1 recognizer (ADR-167) mints in the calls\n// phase. Reading a checked-in rules file as text is the same read-polyglot-as-data\n// discipline `calls/prisma.ts` follows for `schema.prisma`.\n//\n// The `field-guard` policy (ADR-169, packages/core/src/policy.ts) is the consumer:\n// it asserts every client-written column on a collection appears in that\n// collection's `guardedFields`. The load-bearing rule is honesty about what the\n// scanner can and cannot reduce — a guard that is condition-based,\n// function-indirected, or otherwise not an explicit field list leaves the\n// collection INDETERMINATE (no `guardedFields` folded), and the policy then stays\n// silent rather than firing a false positive.\n\nexport const FIRESTORE_COLLECTION_KIND = 'firestore-collection'\n\n// The write-family rule methods. A guard only matters on the write path — a read\n// rule constrains no field the code writes, so it never contributes.\nconst WRITE_METHODS = new Set(['write', 'create', 'update'])\n\n// Strip `//` line and `/* */` block comments, honoring string literals so a `/`\n// inside a quoted path or message is not mistaken for a comment.\nfunction stripComments(src: string): string {\n let out = ''\n let i = 0\n const n = src.length\n let inStr: string | null = null\n while (i < n) {\n const ch = src[i]!\n const next = src[i + 1]\n if (inStr) {\n out += ch\n if (ch === '\\\\') {\n out += next ?? ''\n i += 2\n continue\n }\n if (ch === inStr) inStr = null\n i++\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n inStr = ch\n out += ch\n i++\n continue\n }\n if (ch === '/' && next === '/') {\n while (i < n && src[i] !== '\\n') i++\n continue\n }\n if (ch === '/' && next === '*') {\n i += 2\n while (i < n && !(src[i] === '*' && src[i + 1] === '/')) i++\n i += 2\n continue\n }\n out += ch\n i++\n }\n return out\n}\n\nfunction segmentsFromMatchPath(pathToken: string): string[] {\n return pathToken.split('/').filter((s) => s.length > 0)\n}\n\n// Normalize a sequence of match-path segments to the collection join key: strip\n// the standard `/databases/{db}/documents` root wrapper, drop `{wildcard}`\n// document-id segments, lowercase the remaining collection names, join with `/`.\n// A nested `/users/{userId}/posts/{postId}` becomes `users/posts`. Field names in\n// the graph are lowercased by `foldColumns`, so the key is lowercased too, keeping\n// the join case-insensitive.\nfunction collectionKeyFromSegments(segments: string[]): string {\n let segs = segments\n if (\n segs.length >= 3 &&\n segs[0] === 'databases' &&\n /^\\{.*\\}$/.test(segs[1]!) &&\n segs[2] === 'documents'\n ) {\n segs = segs.slice(3)\n }\n return segs\n .filter((seg) => !/^\\{.*\\}$/.test(seg))\n .map((seg) => seg.toLowerCase())\n .join('/')\n}\n\n// A `firestore-collection` InfraNode's `name` is its collection path (F1 names it\n// from the code call site). Normalize it the same way as the rules match path so\n// the two join, regardless of whether F1 carries wildcard segments verbatim.\nfunction collectionKeyFromName(name: string): string {\n return collectionKeyFromSegments(name.split('/').filter((s) => s.length > 0))\n}\n\n// Pull the explicit field-name literals out of a rule condition — the string\n// arguments to `keys().hasAll([...])` / `hasOnly([...])` / `hasAny([...])`. These\n// are the fields the rules explicitly name; any other condition shape (a function\n// call, an `request.auth` check, a comparison) names no field and contributes\n// nothing. Field names are lowercased to match `foldColumns`.\nfunction fieldsFromCondition(cond: string): string[] {\n const out: string[] = []\n const callRe = /\\b(?:hasAll|hasOnly|hasAny)\\s*\\(\\s*\\[([^\\]]*)\\]\\s*\\)/g\n let m: RegExpExecArray | null\n while ((m = callRe.exec(cond)) !== null) {\n const inner = m[1]!\n const litRe = /'([^']*)'|\"([^\"]*)\"/g\n let lm: RegExpExecArray | null\n while ((lm = litRe.exec(inner)) !== null) {\n const val = lm[1] ?? lm[2] ?? ''\n if (val.length > 0) out.push(val.toLowerCase())\n }\n }\n return out\n}\n\n// Parse a `firestore.rules` file into a map of collection key → the explicit set\n// of guarded fields on its write path. Pure over the file content so a test can\n// drive it without touching the filesystem, mirroring `prismaColumnsFromSchema`.\n//\n// A collection appears in the result ONLY when at least one write-family rule\n// reduces to an explicit field list AND no write-family rule on the same\n// collection is non-reducible. Any condition-based / function-indirected write\n// guard poisons the collection to INDETERMINATE — it is omitted, and the check\n// stays silent. This is the conservative choice: we opine only where the rules\n// give us an explicit set to opine with.\nexport function parseFirestoreRules(content: string): Map<string, string[]> {\n const src = stripComments(content)\n\n // One positional scan interleaving, in document order: a `match <path> {`\n // opener (which consumes its own brace), an `allow …;` statement, a bare `{`\n // (a `service {` / `function …() {` block), and a `}` closer. The wildcard\n // `{userId}` inside a match path is matched as part of the path, so it is not\n // mistaken for the block-opening brace.\n const tokenRe = /match\\s+((?:\\/(?:\\{[^}]*\\}|[^\\s/{}]+))+)\\s*\\{|allow\\b([^;{}]*);|(\\{)|(\\})/g\n\n interface Frame {\n depth: number\n segs: string[]\n }\n const matchStack: Frame[] = []\n let depth = 0\n\n const determinate = new Map<string, Set<string>>()\n const indeterminate = new Set<string>()\n\n const currentKey = (): string =>\n collectionKeyFromSegments(matchStack.flatMap((f) => f.segs))\n\n let m: RegExpExecArray | null\n while ((m = tokenRe.exec(src)) !== null) {\n if (m[1] !== undefined) {\n // `match <path> {` — opens a block and consumes its brace.\n depth++\n matchStack.push({ depth, segs: segmentsFromMatchPath(m[1]) })\n continue\n }\n if (m[2] !== undefined) {\n // `allow <methods> [: if <cond>] ;`\n const body = m[2]\n const colon = body.indexOf(':')\n const methodsPart = colon === -1 ? body : body.slice(0, colon)\n const methods = methodsPart\n .split(',')\n .map((s) => s.trim().toLowerCase())\n .filter(Boolean)\n if (!methods.some((mm) => WRITE_METHODS.has(mm))) continue\n const key = currentKey()\n if (key.length === 0) continue\n const cond = colon === -1 ? '' : body.slice(colon + 1)\n const fields = fieldsFromCondition(cond)\n if (fields.length > 0) {\n const set = determinate.get(key) ?? new Set<string>()\n for (const f of fields) set.add(f)\n determinate.set(key, set)\n } else {\n // A write-family rule with no reducible field list — condition-based or\n // function-indirected. Indeterminate.\n indeterminate.add(key)\n }\n continue\n }\n if (m[3] !== undefined) {\n // A bare `{` (a `service {` / `function {` block).\n depth++\n continue\n }\n if (m[4] !== undefined) {\n // A `}` closer. Pop the match frame opened at this depth, if any.\n if (matchStack.length > 0 && matchStack[matchStack.length - 1]!.depth === depth) {\n matchStack.pop()\n }\n depth--\n continue\n }\n }\n\n const out = new Map<string, string[]>()\n for (const [key, set] of determinate) {\n if (indeterminate.has(key)) continue\n out.set(key, [...set].sort())\n }\n return out\n}\n\n// Standalone extract phase (ADR-169). Reads every service's `firestore.rules` and\n// folds the parsed `guardedFields` onto the matching `firestore-collection`\n// InfraNodes. Adds no nodes or edges — it only enriches existing ones — so it\n// contributes nothing to the extract counts. Inert until the F1 recognizer\n// (ADR-167) has minted `firestore-collection` nodes; when it hasn't, there is\n// nothing to fold onto and this is a no-op. Mutation is allowed here — extract/*\n// is a lifecycle authority (lifecycle.md §3).\nexport async function addFirestoreRules(\n graph: NeatGraph,\n services: DiscoveredService[],\n): Promise<{ nodesAdded: number; edgesAdded: number }> {\n // Merge every service's parsed rules into one collection-key → guarded-fields\n // map. A monorepo can hold a single `firestore.rules` governing collections\n // named across services; folding by collection key keeps the join independent\n // of which service's calls minted the node.\n const guards = new Map<string, Set<string>>()\n for (const service of services) {\n const rulesPath = await findFirst(service.dir, ['firestore.rules'])\n if (!rulesPath) continue\n const content = await readIfExists(rulesPath)\n if (!content) continue\n for (const [key, fields] of parseFirestoreRules(content)) {\n const set = guards.get(key) ?? new Set<string>()\n for (const f of fields) set.add(f)\n guards.set(key, set)\n }\n }\n if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 }\n\n graph.forEachNode((id, attrs) => {\n const node = attrs as GraphNode\n if (node.type !== NodeType.InfraNode) return\n if (node.kind !== FIRESTORE_COLLECTION_KIND) return\n const fields = guards.get(collectionKeyFromName(node.name))\n if (!fields || fields.size === 0) return\n graph.replaceNodeAttributes(id, {\n ...node,\n guardedFields: [...fields].sort(),\n } as InfraNode)\n })\n\n return { nodesAdded: 0, edgesAdded: 0 }\n}\n","// Static-extraction pipeline. Phase order is load-bearing:\n// services → aliases → databases (+ compat) → configs → routes → grpc → calls → infra → frontier promotion.\n// Routes precede calls so the cross-service matcher (ADR-119) sees the full\n// RouteNode table when it resolves client call sites to server routes. gRPC\n// `.proto` extraction (ADR-123) mints the method nodes an OBSERVED span fuses onto.\n//\n// Contract anchors (see /docs/contracts.md):\n// * Rule 1 — Every emitted edge carries Provenance.EXTRACTED from @neat.is/types.\n// * Rule 2 — EXTRACTED edges use the plain `${type}:src->tgt` id pattern.\n// Never write under the OBSERVED id pattern; that's ingest.ts's territory.\n// * Rule 5 — Nodes/edges constructed against schemas in @neat.is/types; no\n// local interface redefinitions in this tree.\n// * Rule 8 — No demo-name hardcoding. Driver names come from package.json\n// dependencies; engine names from compat.json via compatPairs().\n// * Rule 14 — ConfigNodes record file existence only; never the contents.\nimport type { NeatGraph } from '../graph.js'\nimport { DEFAULT_PROJECT } from '../graph.js'\nimport { promoteFrontierNodes } from '../ingest.js'\nimport { ensureCompatLoaded } from '../compat.js'\nimport { emitNeatEvent } from '../events.js'\nimport { addServiceNodes, discoverServices } from './services.js'\nimport { addServiceAliases } from './aliases.js'\nimport { addFiles } from './files.js'\nimport { addSymbols } from './symbols.js'\nimport { addSymbolEdges } from './symbol-edges.js'\nimport { addImports } from './imports.js'\nimport { addServerActions } from './actions.js'\nimport { addDatabasesAndCompat } from './databases/index.js'\nimport { addConfigNodes } from './configs.js'\nimport { addRoutes } from './routes.js'\nimport { addGrpcMethods } from './proto.js'\nimport { addCallEdges } from './calls/index.js'\nimport { addTableEdges } from './table-edges.js'\nimport { addInfra } from './infra/index.js'\nimport { addZodShapes } from './zod-shapes.js'\nimport { addFirestoreRules } from './firestore-rules.js'\nimport {\n drainExtractionErrors,\n writeExtractionErrors,\n writeExtractionHealth,\n extractionHealthPathFor,\n drainDroppedExtracted,\n isRejectedLogEnabled,\n writeRejectedExtracted,\n type ExtractionError,\n type DroppedExtractedEdge,\n} from './errors.js'\nimport path from 'node:path'\nimport { retireExtractedEdgesByMissingFile } from './retire.js'\n\nexport interface ExtractResult {\n nodesAdded: number\n edgesAdded: number\n frontiersPromoted: number\n // ADR-065 — per-file extraction failures collected during the pass.\n // `extractionErrors` is the count; `errorEntries` is the drained list,\n // available for callers that want to surface per-file context. Both are\n // present on every pass (zero is observable as a positive signal).\n extractionErrors: number\n errorEntries: ExtractionError[]\n // #140 — count of EXTRACTED edges retired this pass because their\n // evidence.file no longer exists on disk. Zero on a clean pass; non-zero\n // means the snapshot was carrying ghosts from deleted source.\n ghostsRetired: number\n // ADR-066 — count of EXTRACTED candidates dropped at emit time because\n // their graded confidence fell below NEAT_EXTRACTED_PRECISION_FLOOR.\n // Always reported (zero is observable). Detail entries surface in\n // `droppedEntries` and route to rejected.ndjson when\n // NEAT_EXTRACTED_REJECTED_LOG=1.\n extractedDropped: number\n droppedEntries: DroppedExtractedEdge[]\n}\n\nexport interface ExtractOptions {\n // Post-extract policy trigger (ADR-043). Awaited after frontier promotion\n // so policies see the final post-pass graph state. Daemons wire this to\n // evaluateAllPolicies + PolicyViolationsLog.append.\n onPolicyTrigger?: (graph: NeatGraph) => Promise<void> | void\n // Project tag for the extraction-complete event (ADR-051). Defaults to\n // DEFAULT_PROJECT when omitted.\n project?: string\n // ADR-065 — when set, drained extraction errors are appended to this\n // path as ndjson with `source: 'extract'`. Daemons / `neat init` /\n // `neat watch` wire this to `<projectDir>/neat-out/errors.ndjson`. When\n // omitted, errors are still drained and returned in the result, just\n // not persisted.\n errorsPath?: string\n}\n\nexport async function extractFromDirectory(\n graph: NeatGraph,\n scanPath: string,\n opts: ExtractOptions = {},\n): Promise<ExtractResult> {\n await ensureCompatLoaded()\n // Clear any stale entries from a prior pass (the producer-side sink is\n // process-local). Per ADR-065, every pass collects its own errors; we drain\n // again at the end to capture this pass's failures.\n drainExtractionErrors()\n\n const services = await discoverServices(scanPath)\n\n const phase1Nodes = addServiceNodes(graph, services)\n await addServiceAliases(graph, scanPath, services)\n const fileEnum = await addFiles(graph, services)\n // Symbol nodes under files (ADR-158). Runs after addFiles so the FileNodes it\n // hangs symbols off already exist; before calls so a future Phase-2 symbol→\n // symbol CALLS producer has the symbol inventory to resolve against.\n const symbolEnum = await addSymbols(graph, services)\n const importGraph = await addImports(graph, services)\n // Static symbol edges (ADR-158 §3). Runs after addSymbols (the inventory it\n // resolves targets against) and after addImports (so the import graph is in\n // place). Confident heritage + call edges only, symbol→symbol, never guessed.\n const symbolEdges = await addSymbolEdges(graph, services)\n // Next.js Server Actions (ADR-168). Runs after addImports so its client-stitch\n // can resolve an imported action binding through the same import graph. Mints a\n // ServerActionNode per exported `\"use server\"` function (owned by its file via\n // CONTAINS) and a `file ──CALLS──▶ action` edge on any client reference. Gated\n // on the `next` dependency; a no-op for every other service.\n const actionPhase = await addServerActions(graph, services)\n const phase2 = await addDatabasesAndCompat(graph, services, scanPath)\n const phase3 = await addConfigNodes(graph, services, scanPath)\n // Route extraction (ADR-119) runs before calls so the RouteNodes exist when\n // addCallEdges' cross-service matcher (route-match.ts) looks them up.\n const routePhase = await addRoutes(graph, services)\n // gRPC `.proto` service/method extraction (ADR-123). Independent of the call\n // phase — it reads the proto contract surface, not JS/TS call sites — and mints\n // the same method nodes an OBSERVED gRPC span lands on, so declared and observed\n // methods fuse.\n const grpcPhase = await addGrpcMethods(graph, services)\n const phase4 = await addCallEdges(graph, services)\n // Foreign-key table→table edges (ADR-161). Runs after addCallEdges so the\n // column/table read has already minted both endpoints' `infra:sql-table:<name>`\n // InfraNodes; it mints the `child ──REFERENCES──▶ parent` edges the data-axis\n // traversal walks. The data-axis sibling of symbol-edges (ADR-158 §3).\n const tableEdges = await addTableEdges(graph, services)\n const phase5 = await addInfra(graph, scanPath, services)\n // Zod declared shapes (ADR-170). A standalone, self-contained phase (it reads\n // its own files, gated per service on the `zod` dep) minting an\n // `infra:zod-schema:<name>` InfraNode + EXTRACTED columns per top-level\n // `z.object` / `z.enum` literal. Independent of the other phases — it neither\n // reads nor mutates their nodes — so order is not load-bearing; it runs here,\n // after infra, before the ghost-retire sweep so its file-relative edges are\n // dropped like any other producer's when their source disappears.\n const zodPhase = await addZodShapes(graph, services)\n // firestore.rules guard sets (ADR-169). A standalone phase — not a calls\n // producer — that reads each service's checked-in `firestore.rules` and folds\n // `guardedFields` onto the `firestore-collection` InfraNodes the F1 recognizer\n // (ADR-167) mints in the calls phase, so it must run after addCallEdges. It\n // adds no nodes or edges (it only enriches existing nodes), and is inert until\n // firestore-collection nodes exist.\n const firestoreRulesPhase = await addFirestoreRules(graph, services)\n // #140 — drop EXTRACTED edges whose evidence.file no longer exists on disk.\n // Catches the deleted-file ghost case for the full-pass entry point\n // (init / daemon bootstrap). The edited-file case is handled per-mtime by\n // watch.ts's `retireEdgesByFile`. Service dirs are passed alongside scanPath\n // because CALLS-family producers store service-dir-relative paths while\n // configs / databases / infra store scanPath-relative.\n const ghostsRetired = retireExtractedEdgesByMissingFile(\n graph,\n scanPath,\n services.map((s) => s.dir),\n )\n const frontiersPromoted = promoteFrontierNodes(graph)\n\n // Post-extract policy trigger (ADR-043). Fires after frontier promotion so\n // policies see the post-pass graph (including any FRONTIER → OBSERVED edge\n // upgrades that just landed).\n if (opts.onPolicyTrigger) await opts.onPolicyTrigger(graph)\n\n // ADR-065 — drain the per-file extraction errors collected during the pass.\n // If a sidecar path was supplied, append the entries; otherwise return them\n // for the caller to surface.\n const errorEntries = drainExtractionErrors()\n if (opts.errorsPath && errorEntries.length > 0) {\n try {\n await writeExtractionErrors(errorEntries, opts.errorsPath)\n } catch (err) {\n console.warn(\n `[neat] failed to write extraction errors to ${opts.errorsPath}: ${(err as Error).message}`,\n )\n }\n }\n // #883 — overwrite the coverage sidecar every pass (even zero, to clear a\n // prior gap) so a daemon can put this pass's skipped-file count on /health.\n // errors.ndjson is append-only and can't answer \"is the current graph\n // complete?\"; this can. Best-effort — a failed write never fails extraction.\n if (opts.errorsPath) {\n try {\n await writeExtractionHealth(errorEntries, extractionHealthPathFor(opts.errorsPath))\n } catch (err) {\n console.warn(\n `[neat] failed to write extraction health sidecar: ${(err as Error).message}`,\n )\n }\n }\n\n // ADR-066 — drain the precision-floor drops. Always returned; only\n // persisted when NEAT_EXTRACTED_REJECTED_LOG=1 (opt-in to keep the\n // default sidecar surface quiet).\n const droppedEntries = drainDroppedExtracted()\n if (\n isRejectedLogEnabled() &&\n opts.errorsPath &&\n droppedEntries.length > 0\n ) {\n // rejected.ndjson lives alongside errors.ndjson under neat-out/. Derive\n // from errorsPath rather than re-plumbing a new option.\n const rejectedPath = path.join(path.dirname(opts.errorsPath), 'rejected.ndjson')\n try {\n await writeRejectedExtracted(droppedEntries, rejectedPath)\n } catch (err) {\n console.warn(\n `[neat] failed to write rejected extracted edges to ${rejectedPath}: ${(err as Error).message}`,\n )\n }\n }\n\n const result: ExtractResult = {\n nodesAdded:\n phase1Nodes +\n fileEnum.nodesAdded +\n symbolEnum.nodesAdded +\n importGraph.nodesAdded +\n symbolEdges.nodesAdded +\n actionPhase.nodesAdded +\n phase2.nodesAdded +\n phase3.nodesAdded +\n routePhase.nodesAdded +\n grpcPhase.nodesAdded +\n phase4.nodesAdded +\n tableEdges.nodesAdded +\n phase5.nodesAdded +\n zodPhase.nodesAdded +\n firestoreRulesPhase.nodesAdded,\n edgesAdded:\n fileEnum.edgesAdded +\n symbolEnum.edgesAdded +\n importGraph.edgesAdded +\n symbolEdges.edgesAdded +\n actionPhase.edgesAdded +\n phase2.edgesAdded +\n phase3.edgesAdded +\n routePhase.edgesAdded +\n grpcPhase.edgesAdded +\n phase4.edgesAdded +\n tableEdges.edgesAdded +\n phase5.edgesAdded +\n zodPhase.edgesAdded +\n firestoreRulesPhase.edgesAdded,\n frontiersPromoted,\n extractionErrors: errorEntries.length,\n errorEntries,\n ghostsRetired,\n extractedDropped: droppedEntries.length,\n droppedEntries,\n }\n\n // extraction-complete (ADR-051). fileCount is the number of services\n // discovered — the closest proxy we have for \"how much source did this\n // pass touch\" without a per-phase file accountant.\n emitNeatEvent({\n type: 'extraction-complete',\n project: opts.project ?? DEFAULT_PROJECT,\n payload: {\n project: opts.project ?? DEFAULT_PROJECT,\n fileCount: services.length,\n nodesAdded: result.nodesAdded,\n edgesAdded: result.edgesAdded,\n },\n })\n\n return result\n}\n","import { existsSync } from 'node:fs'\nimport path from 'node:path'\nimport type { GraphEdge, GraphNode } from '@neat.is/types'\nimport { NodeType, Provenance } from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\n\n// Drop any FileNode left with no edges. A FileNode exists to originate a\n// relationship (file-awareness.md §1); once its CALLS / CONTAINS edges are\n// retired and no OBSERVED traffic remains, the bare node carries nothing and\n// goes too. Called after edge retirement so the snapshot stays consistent with\n// what's on disk. Returns the count dropped.\nfunction dropOrphanedFileNodes(graph: NeatGraph): number {\n const orphans: string[] = []\n graph.forEachNode((id, attrs) => {\n if ((attrs as GraphNode).type !== NodeType.FileNode) return\n if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {\n orphans.push(id)\n }\n })\n for (const id of orphans) graph.dropNode(id)\n return orphans.length\n}\n\n// Drop every EXTRACTED edge whose evidence.file matches the given path, then\n// sweep any FileNode the retirement left orphaned. Called from watch.ts before\n// re-running an extract phase, so the producer's idempotent re-write recreates\n// only the edges that still apply. Edges from the deleted code stay deleted.\n// See docs/contracts/static-extraction.md §Ghost-edge cleanup. Mutation\n// authority lives under extract/* per ADR-030, so the dropEdge call must happen\n// here, not in watch.ts. The returned count is edges dropped (FileNode cleanup\n// is a structural side effect, not a ghost-edge count).\nexport function retireEdgesByFile(graph: NeatGraph, file: string): number {\n const normalized = file.split('\\\\').join('/')\n const toDrop: string[] = []\n graph.forEachEdge((id, attrs) => {\n const edge = attrs as GraphEdge\n if (edge.provenance !== Provenance.EXTRACTED) return\n if (!edge.evidence?.file) return\n if (edge.evidence.file === normalized) toDrop.push(id)\n })\n for (const id of toDrop) graph.dropEdge(id)\n dropOrphanedFileNodes(graph)\n return toDrop.length\n}\n\n// #140 — full-pass cleanup. Walk every EXTRACTED edge in the graph; if its\n// `evidence.file` cannot be resolved on disk against the scan root or any\n// discovered service directory, drop it. extractFromDirectory calls this at\n// the end of every pass so a daemon bootstrap (or a re-init after the\n// operator deleted some source) gets a snapshot consistent with what's\n// actually on disk.\n//\n// Handles the deleted-file half of the ghost-edge bug. The edited-file half\n// (file still exists, producer no longer emits the edge) is handled by\n// watch.ts's per-file `retireEdgesByFile` on the mtime trigger.\n//\n// Path resolution is tolerant: producers in this tree are inconsistent about\n// whether `evidence.file` is scanPath-relative (configs, databases, infra)\n// or service-dir-relative (calls/*). We try every candidate base before\n// concluding the file is gone — the cost is one extra `existsSync` per\n// service dir per ghost candidate, which is cheap.\nexport function retireExtractedEdgesByMissingFile(\n graph: NeatGraph,\n scanPath: string,\n serviceDirs: readonly string[] = [],\n): number {\n const toDrop: string[] = []\n const bases = [scanPath, ...serviceDirs]\n graph.forEachEdge((id, attrs) => {\n const edge = attrs as GraphEdge\n if (edge.provenance !== Provenance.EXTRACTED) return\n const evidenceFile = edge.evidence?.file\n if (!evidenceFile) return\n if (path.isAbsolute(evidenceFile)) {\n if (!existsSync(evidenceFile)) toDrop.push(id)\n return\n }\n // Tolerant: the file is \"present\" if any base resolves it.\n const found = bases.some((base) => existsSync(path.join(base, evidenceFile)))\n if (!found) toDrop.push(id)\n })\n for (const id of toDrop) graph.dropEdge(id)\n dropOrphanedFileNodes(graph)\n return toDrop.length\n}\n","// computeDivergences — the thesis surface, derived (ADR-060).\n//\n// Walks the live graph and surfaces the five locked divergence shapes:\n// missing-observed, missing-extracted, version-mismatch, host-mismatch,\n// and compat-violation. Pure: no I/O, no mutation, no async. The function\n// operates on a NeatGraph reference and returns a fresh DivergenceResult\n// each call — there is no persistence (binding rule 2).\n//\n// Mutation authority (ADR-030 / contract #3) is locked to ingest.ts and\n// extract/*; this module reads only. The contract test\n// `packages/core/test/audits/contracts.test.ts` enforces it.\n\nimport type {\n CompatRuleRef,\n Divergence,\n DivergenceResult,\n DivergenceType,\n EdgeTypeValue,\n GraphEdge,\n GraphNode,\n InfraNode,\n ServiceNode,\n} from '@neat.is/types'\nimport {\n databaseId,\n DivergenceResultSchema,\n EdgeType,\n NodeType,\n parseEdgeId,\n parseFileId,\n Provenance,\n serviceId,\n} from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\nimport { columnIsDeclared, columnIsObserved } from './columns.js'\nimport {\n checkCompatibility,\n checkDeprecatedApi,\n compatPairs,\n deprecatedApis,\n} from './compat.js'\nimport { confidenceForEdge } from './traverse.js'\n\nexport interface DivergenceQueryOpts {\n // Filter the result to a subset of divergence types. Undefined keeps all\n // five. Empty set returns nothing.\n type?: ReadonlySet<DivergenceType>\n // Drop divergences below this confidence threshold. Undefined keeps all.\n minConfidence?: number\n // Scope to divergences that involve this node (as source or target).\n node?: string\n}\n\n// (source, target, type) → which provenance variants are present. Each\n// bucket is the unit the missing-observed / missing-extracted detectors\n// operate over.\ninterface EdgeBucket {\n source: string\n target: string\n type: GraphEdge['type']\n extracted?: GraphEdge\n observed?: GraphEdge\n inferred?: GraphEdge\n stale?: GraphEdge\n}\n\nfunction bucketKey(source: string, target: string, type: string): string {\n return `${type}|${source}|${target}`\n}\n\n// A database CONNECTS_TO is *declared* in a config file (its connection string)\n// but *executed* from a code file (the query call site) — inherently different\n// files. Comparing them at file grain would flag both a `missing-observed` (on\n// the config-file edge) and a `missing-extracted` (on the code-file edge) for a\n// database the service both declares and drives — a false pair on every ORM app\n// (ADR-141). So roll a database CONNECTS_TO's source up to its owning service:\n// declared and observed then compare at the grain they actually share. The\n// file-grained edges stay in the graph untouched — only this comparison\n// coarsens, and only for database targets (a route or service edge keeps its\n// file grain per the ADR-119 route-grained comparison).\nfunction bucketSourceFor(graph: NeatGraph, edge: GraphEdge): string {\n if (edge.type !== EdgeType.CONNECTS_TO) return edge.source\n const parsed = parseFileId(edge.source)\n if (!parsed || !graph.hasNode(edge.target)) return edge.source\n const target = graph.getNodeAttributes(edge.target) as GraphNode\n if (target.type !== NodeType.DatabaseNode) return edge.source\n return serviceId(parsed.service)\n}\n\nfunction bucketEdges(graph: NeatGraph): Map<string, EdgeBucket> {\n const buckets = new Map<string, EdgeBucket>()\n graph.forEachEdge((id, attrs) => {\n const e = attrs as GraphEdge\n const parsed = parseEdgeId(id)\n // parseEdgeId can fall through to EXTRACTED for unknown shapes — fall\n // back to the edge's own provenance when the id doesn't parse cleanly.\n const provenance = parsed?.provenance ?? e.provenance\n const source = bucketSourceFor(graph, e)\n const key = bucketKey(source, e.target, e.type)\n const cur =\n buckets.get(key) ?? { source, target: e.target, type: e.type }\n switch (provenance) {\n case Provenance.EXTRACTED:\n cur.extracted = e\n break\n case Provenance.OBSERVED:\n cur.observed = e\n break\n case Provenance.INFERRED:\n cur.inferred = e\n break\n default:\n // STALE rides on what used to be an OBSERVED edge — the id format\n // stays OBSERVED per identity.ts, so this branch is mostly defensive.\n if (e.provenance === Provenance.STALE) cur.stale = e\n }\n buckets.set(key, cur)\n })\n return buckets\n}\n\nfunction nodeIsFrontier(graph: NeatGraph, nodeId: string): boolean {\n if (!graph.hasNode(nodeId)) return false\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n return attrs.type === NodeType.FrontierNode\n}\n\n// A WebSocketChannelNode is minted OBSERVED-only from the HTTP upgrade span\n// (ADR-125): a channel is known from observation, never from static extraction,\n// so it has no declared twin to diverge against. Its edge is a `CONNECTS_TO`,\n// which is in the OBSERVABLE_EDGE_TYPES allowlist, so an OBSERVED-only\n// `service ──CONNECTS_TO──▶ ws-channel` would otherwise flag a spurious\n// `missing-extracted`. Suppressing it where the target is a channel node is\n// signal-preserving — there is no static edge that \"should\" exist — not\n// signal-hiding. See docs/contracts/divergence-query.md.\nfunction nodeIsWebsocketChannel(graph: NeatGraph, nodeId: string): boolean {\n if (!graph.hasNode(nodeId)) return false\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n return attrs.type === NodeType.WebSocketChannelNode\n}\n\n// A ServerActionNode is minted EXTRACTED-only from a `\"use server\"` directive\n// (ADR-168); its inbound `file ──CALLS──▶ action` client-stitch has no OBSERVED\n// twin by construction (Next serialises actions to opaque `Next-Action` hashes,\n// so runtime never names the action). CALLS is in OBSERVABLE_EDGE_TYPES, so an\n// EXTRACTED-only CALLS onto an action would otherwise flag a spurious\n// `missing-observed`. Suppressing it where the target is an action node is\n// signal-preserving — the OBSERVED twin cannot exist yet — not signal-hiding.\n// A bounded target-exclusion, mirroring nodeIsWebsocketChannel; not a taxonomy\n// change. See docs/contracts/divergence-query.md.\nfunction nodeIsServerAction(graph: NeatGraph, nodeId: string): boolean {\n if (!graph.hasNode(nodeId)) return false\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n return attrs.type === NodeType.ServerActionNode\n}\n\n// A SymbolNode endpoint marks a symbol-grained edge (static heritage, or a\n// symbol→symbol EXTRACTED CALLS from Phase 2). Those live one grain below the\n// file/service grain this query compares (file-awareness.md §7 — \"compare\n// CALLS-family edges at the shared grain\"), and a static intra-process symbol\n// call has no boundary-observed twin by construction (observed CALLS are\n// boundary-grained, §5). Comparing them here would report every declared\n// heritage link and every static call as a spurious `missing-observed`. Symbol-\n// grain divergence is deliberately wired later (ADR-158 §7, Phase 3); until then\n// a symbol-grained bucket stays out of this surface.\nfunction nodeIsSymbol(graph: NeatGraph, nodeId: string): boolean {\n if (!graph.hasNode(nodeId)) return false\n const attrs = graph.getNodeAttributes(nodeId) as GraphNode\n return attrs.type === NodeType.SymbolNode\n}\n\nfunction clampConfidence(n: number): number {\n if (!Number.isFinite(n)) return 0\n return Math.max(0, Math.min(1, n))\n}\n\nfunction reasonForMissingObserved(source: string, target: string, type: string): string {\n return `Code declares ${source} → ${target} (${type}) but no production traffic has been observed for this edge.`\n}\n\nfunction reasonForMissingExtracted(source: string, target: string, type: string): string {\n return `Production observed ${source} → ${target} (${type}) but static analysis did not surface this edge.`\n}\n\nconst RECOMMENDATION_MISSING_OBSERVED =\n 'Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.'\nconst RECOMMENDATION_MISSING_EXTRACTED =\n 'Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.'\nconst RECOMMENDATION_HOST_MISMATCH =\n 'Check environment-specific config overrides — the runtime host differs from what static configuration declares.'\n\n// ADR-066 §4 — reweight against graded confidence.\n//\n// `missing-extracted` (OBSERVED-led) cascades from the OBSERVED edge's\n// graded confidence (signal-block grade per ADR-066 §2). `missing-observed`\n// weights by the EXTRACTED edge's graded confidence (per-extractor grade\n// per ADR-066 §1). Sub-floor EXTRACTED candidates never enter the graph\n// (precision floor, §3) so what surfaces here is backed by structural or\n// verified-call-site evidence.\n//\n// Falls back to confidenceForEdge for legacy edges loaded from a pre-v0.3.4\n// snapshot that don't carry a stored `confidence` field.\nfunction gradedConfidence(edge: GraphEdge): number {\n if (typeof edge.confidence === 'number') return clampConfidence(edge.confidence)\n return clampConfidence(confidenceForEdge(edge))\n}\n\n// Edge types production can actually emit as OBSERVED traffic — the CALLS\n// family plus cross-service and connection edges. `missing-observed` only\n// makes sense for these: an EXTRACTED edge of one of these types with no\n// OBSERVED twin is a real \"code declares it, production never ran it\" finding.\n//\n// Structural / static-only edge types (IMPORTS, CONFIGURED_BY, CONTAINS,\n// DEPENDS_ON, RUNS_ON) have no runtime span behind them, so there is never an\n// OBSERVED twin to be \"missing\" — measuring their tiers would report every\n// import and every config wire as a missing-observed divergence, which is\n// noise, not signal. Keep this an allowlist (not a denylist) so a new\n// structural edge type stays out of the missing-observed surface by default\n// until it is deliberately added here (divergence-query.md — the five locked\n// types compare CALLS-family edges at the shared grain).\nconst OBSERVABLE_EDGE_TYPES: ReadonlySet<EdgeTypeValue> = new Set([\n EdgeType.CALLS,\n EdgeType.CONNECTS_TO,\n EdgeType.PUBLISHES_TO,\n EdgeType.CONSUMES_FROM,\n])\n\nfunction detectMissingDivergences(\n graph: NeatGraph,\n bucket: EdgeBucket,\n): Divergence[] {\n const out: Divergence[] = []\n\n // CONTAINS is structural ownership (service → file), not a declared-vs-\n // observed relationship — comparing its tiers would surface an OTel-only\n // file node as a spurious missing-extracted finding (file-awareness.md §2).\n // Divergence compares CALLS-family edges at the shared grain (§7).\n if (bucket.type === EdgeType.CONTAINS) return out\n\n // Symbol-grained buckets (heritage, symbol→symbol CALLS) are out of scope for\n // the file/service-grain divergence surface — see nodeIsSymbol.\n if (nodeIsSymbol(graph, bucket.source) || nodeIsSymbol(graph, bucket.target)) return out\n\n if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {\n // Skip when the would-be target is a FrontierNode — those represent\n // unresolved span peers, not real entities we expect OBSERVED traffic\n // to. The coexistence contract is between EXTRACTED and OBSERVED on\n // real nodes; FRONTIER is unknown territory.\n if (!nodeIsFrontier(graph, bucket.target) && !nodeIsServerAction(graph, bucket.target)) {\n // ADR-066 §4 — weight by the EXTRACTED edge's graded confidence.\n // Substring/hostname-shape candidates already dropped at the precision\n // floor; what remains is structural or verified-call-site evidence.\n out.push({\n type: 'missing-observed',\n source: bucket.source,\n target: bucket.target,\n edgeType: bucket.type,\n extracted: bucket.extracted,\n confidence: gradedConfidence(bucket.extracted),\n reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),\n recommendation: RECOMMENDATION_MISSING_OBSERVED,\n })\n }\n }\n\n if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {\n // ADR-066 §4 — cascade from the OBSERVED edge's graded confidence.\n // OBSERVED-led finding; the headline divergence type. A WebSocketChannelNode\n // target is skipped: it is OBSERVED-only by design (ADR-125), so an\n // OBSERVED-only CONNECTS_TO onto it is expected, not a missing-extracted\n // divergence — mirrors the CONTAINS exclusion above, keyed on target type.\n out.push({\n type: 'missing-extracted',\n source: bucket.source,\n target: bucket.target,\n edgeType: bucket.type,\n observed: bucket.observed,\n confidence: gradedConfidence(bucket.observed),\n reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),\n recommendation: RECOMMENDATION_MISSING_EXTRACTED,\n })\n }\n\n return out\n}\n\n// Returns the declared host of the service's static DB target, when\n// recoverable. ServiceNode.dbConnectionTarget is the static-extraction\n// surface for \"this service connects to X\" — `X` is host[:port] or a\n// docker-compose-style service name. Empty / undefined means we have no\n// EXTRACTED host to compare against and host-mismatch can't fire.\nfunction declaredHostFor(svc: ServiceNode): string | null {\n const raw = svc.dbConnectionTarget?.trim()\n if (!raw) return null\n // Strip a trailing port if present so it lines up with DatabaseNode.host\n // (ADR-028 §6 — DatabaseNode id excludes port).\n const colon = raw.lastIndexOf(':')\n if (colon === -1) return raw\n const port = raw.slice(colon + 1)\n if (/^\\d+$/.test(port)) return raw.slice(0, colon)\n return raw\n}\n\nfunction hasExtractedConfiguredBy(graph: NeatGraph, svcId: string): boolean {\n for (const edgeId of graph.outboundEdges(svcId)) {\n const e = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (e.type === EdgeType.CONFIGURED_BY && e.provenance === Provenance.EXTRACTED) {\n return true\n }\n }\n return false\n}\n\nfunction detectHostMismatch(\n graph: NeatGraph,\n svcId: string,\n svc: ServiceNode,\n): Divergence[] {\n const declaredHost = declaredHostFor(svc)\n if (!declaredHost) return []\n if (!hasExtractedConfiguredBy(graph, svcId)) return []\n\n const out: Divergence[] = []\n for (const edgeId of graph.outboundEdges(svcId)) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (edge.type !== EdgeType.CONNECTS_TO) continue\n if (edge.provenance !== Provenance.OBSERVED) continue\n const target = graph.getNodeAttributes(edge.target) as GraphNode\n if (target.type !== NodeType.DatabaseNode) continue\n const observedHost = target.host?.trim()\n if (!observedHost) continue\n if (observedHost === declaredHost) continue\n\n out.push({\n type: 'host-mismatch',\n source: svcId,\n target: edge.target,\n extractedHost: declaredHost,\n observedHost,\n confidence: clampConfidence(confidenceForEdge(edge)),\n reason: `Config declares ${svcId} connects to ${declaredHost}; production connects to ${observedHost}.`,\n recommendation: RECOMMENDATION_HOST_MISMATCH,\n })\n }\n return out\n}\n\nfunction detectCompatDivergences(\n graph: NeatGraph,\n svcId: string,\n svc: ServiceNode,\n): Divergence[] {\n const out: Divergence[] = []\n const deps = svc.dependencies ?? {}\n\n for (const edgeId of graph.outboundEdges(svcId)) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (edge.type !== EdgeType.CONNECTS_TO) continue\n if (edge.provenance !== Provenance.OBSERVED) continue\n const target = graph.getNodeAttributes(edge.target) as GraphNode\n if (target.type !== NodeType.DatabaseNode) continue\n\n // Driver-engine compat. Definitive — when a rule fires it's a\n // version-mismatch with confidence 1.0.\n for (const pair of compatPairs()) {\n if (pair.engine !== target.engine) continue\n const declared = deps[pair.driver]\n if (!declared) continue\n const result = checkCompatibility(\n pair.driver,\n declared,\n target.engine,\n target.engineVersion,\n )\n if (!result.compatible && result.reason) {\n out.push({\n type: 'version-mismatch',\n source: svcId,\n target: edge.target,\n extractedVersion: declared,\n observedVersion: target.engineVersion,\n compatibility: 'incompatible',\n confidence: 1.0,\n reason: result.reason,\n recommendation: result.minDriverVersion\n ? `Upgrade ${pair.driver} to >= ${result.minDriverVersion}.`\n : `Update the ${pair.driver} driver to a version compatible with ${target.engine} ${target.engineVersion}.`,\n })\n }\n }\n\n // Deprecated-api compat. Broader than version-mismatch — surfaces as\n // compat-violation. Driver-engine rules above already covered the\n // \"version is too low\" shape; deprecated covers \"version is too high\n // / no longer supported.\"\n for (const rule of deprecatedApis()) {\n const declared = deps[rule.package]\n if (!declared) continue\n const result = checkDeprecatedApi(rule, declared)\n if (!result.compatible && result.reason) {\n const ruleRef: CompatRuleRef = {\n kind: rule.kind ?? 'deprecated-api',\n reason: result.reason,\n package: rule.package,\n }\n out.push({\n type: 'compat-violation',\n source: svcId,\n target: edge.target,\n rule: ruleRef,\n observed: edge,\n confidence: 1.0,\n reason: result.reason,\n recommendation: `Replace deprecated ${rule.package}@${declared} with a supported version.`,\n })\n }\n }\n }\n return out\n}\n\nconst RECOMMENDATION_COLUMN_MISSING_OBSERVED =\n 'Verify the column is exercised in production; a migration that renamed or dropped it may have left a writer declaring the old name.'\nconst RECOMMENDATION_COLUMN_MISSING_EXTRACTED =\n 'The schema or migration is likely behind the code — production writes a column the declared schema does not carry. Check for a field rename that updated the query but not the model.'\n\n// Column-drift (ADR-157 §4) — the same missing-observed / missing-extracted\n// semantics, computed over the declared and observed column sets on one\n// `sql-table` node instead of across an edge triple. A declared-only column is\n// `missing-observed`, an observed-only column is `missing-extracted`, reported at\n// column grain (`orders.total`). Each column carries the sides it was seen with in\n// its `provenances` set, so the declared set and the observed set read straight off\n// one node: a column with EXTRACTED but no OBSERVED is declared-only, OBSERVED but\n// no EXTRACTED is observed-only, and a column with both is fused — not drift.\n//\n// A node with only one side present anywhere emits nothing — no drift claim without\n// both a declared and an observed column somewhere on the table (the ADR-141 fusion\n// discipline). A table that is only declared, or only observed, is the edge-grain\n// detectors' business, not column drift: the observed half stands alone as the\n// \"which columns does production touch\" view, and a schema NEAT has parsed but never\n// seen driven should not read as all-columns-missing.\nfunction detectColumnDrift(node: InfraNode): Divergence[] {\n const columns = node.columns\n if (!columns || columns.length === 0) return []\n\n // Both sides must be present *somewhere* on the table, or there is no fused\n // picture to diverge against.\n const anyDeclared = columns.some(columnIsDeclared)\n const anyObserved = columns.some(columnIsObserved)\n if (!anyDeclared || !anyObserved) return []\n\n const out: Divergence[] = []\n for (const col of columns) {\n const declared = columnIsDeclared(col)\n const observed = columnIsObserved(col)\n if (declared && !observed) {\n out.push({\n type: 'missing-observed',\n source: node.id,\n target: node.id,\n table: node.id,\n column: col.name,\n confidence: clampConfidence(col.confidence),\n reason: `Schema declares column ${node.name}.${col.name} but no production statement has touched it.`,\n recommendation: RECOMMENDATION_COLUMN_MISSING_OBSERVED,\n })\n } else if (observed && !declared) {\n out.push({\n type: 'missing-extracted',\n source: node.id,\n target: node.id,\n table: node.id,\n column: col.name,\n confidence: clampConfidence(col.confidence),\n reason: `Production touched column ${node.name}.${col.name} but the schema does not declare it.`,\n recommendation: RECOMMENDATION_COLUMN_MISSING_EXTRACTED,\n })\n }\n }\n return out\n}\n\nfunction involvesNode(d: Divergence, nodeId: string): boolean {\n return d.source === nodeId || d.target === nodeId\n}\n\n// A single service<->DB host drift lights up three ways: the host-mismatch\n// itself, a missing-extracted on the observed DB node (the OBSERVED CONNECTS_TO\n// edge went to the *wrong* host, so it has no EXTRACTED twin), and a\n// missing-observed on the declared DB node (the EXTRACTED wire to the declared\n// host was never driven because traffic went elsewhere). The two missing-*\n// findings are just the halves of the drift the host-mismatch already names in\n// full, so they collapse into it — one divergence per distinct problem, so\n// blast-radius counts stay honest (issue #591). Pass 1 and Pass 2 run\n// independently, so this reconciles them after both have emitted.\nfunction suppressHostMismatchHalves(all: Divergence[]): Divergence[] {\n const observedHalf = new Set<string>() // `${source}->${target}` of the observed DB edge\n const declaredHalf = new Set<string>() // declared DB node id\n for (const d of all) {\n if (d.type !== 'host-mismatch') continue\n observedHalf.add(`${d.source}->${d.target}`)\n declaredHalf.add(databaseId(d.extractedHost))\n }\n if (observedHalf.size === 0) return all\n return all.filter((d) => {\n if (\n d.type === 'missing-extracted' &&\n observedHalf.has(`${d.source}->${d.target}`)\n ) {\n return false\n }\n if (d.type === 'missing-observed' && declaredHalf.has(d.target)) return false\n return true\n })\n}\n\nexport function computeDivergences(\n graph: NeatGraph,\n opts: DivergenceQueryOpts = {},\n): DivergenceResult {\n const all: Divergence[] = []\n\n // Pass 1 — bucket every edge and emit missing-observed / missing-extracted.\n const buckets = bucketEdges(graph)\n for (const bucket of buckets.values()) {\n for (const d of detectMissingDivergences(graph, bucket)) all.push(d)\n }\n\n // Pass 2 — per-service host + compat rules, and per-table column drift.\n graph.forEachNode((nodeId, attrs) => {\n const n = attrs as GraphNode\n if (n.type === NodeType.ServiceNode) {\n const svc = n as ServiceNode\n for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d)\n for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d)\n return\n }\n // Column drift (ADR-157 §4) rides on `sql-table` InfraNodes.\n if (n.type === NodeType.InfraNode && n.kind === 'sql-table') {\n for (const d of detectColumnDrift(n)) all.push(d)\n }\n })\n\n // Reconcile the two passes: a fired host-mismatch already tells the whole\n // service<->DB drift story, so drop the redundant missing-* halves (#591).\n const reconciled = suppressHostMismatchHalves(all)\n\n // Filter + sort. Higher confidence first; within the same confidence,\n // stable on (type, source, target) so callers see deterministic output.\n let filtered = reconciled\n if (opts.type) {\n const allowed = opts.type\n filtered = filtered.filter((d) => allowed.has(d.type))\n }\n if (opts.minConfidence !== undefined) {\n const threshold = opts.minConfidence\n filtered = filtered.filter((d) => d.confidence >= threshold)\n }\n if (opts.node) {\n const target = opts.node\n filtered = filtered.filter((d) => involvesNode(d, target))\n }\n\n // ADR-066 §4 / §5 — confidence desc; missing-extracted leads\n // missing-observed at equal confidence (OBSERVED-led tiebreaker); then\n // stable on (type, source, target).\n const TYPE_LEADERSHIP: Record<DivergenceType, number> = {\n 'missing-extracted': 0,\n 'missing-observed': 1,\n 'version-mismatch': 2,\n 'host-mismatch': 3,\n 'compat-violation': 4,\n }\n filtered.sort((a, b) => {\n if (b.confidence !== a.confidence) return b.confidence - a.confidence\n const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type]\n if (lead !== 0) return lead\n if (a.type !== b.type) return a.type.localeCompare(b.type)\n if (a.source !== b.source) return a.source.localeCompare(b.source)\n if (a.target !== b.target) return a.target.localeCompare(b.target)\n // Column-locus drift shares source == target (the table node), so break the\n // final tie on the column name to keep column-grain output deterministic.\n const ac = 'column' in a && a.column ? a.column : ''\n const bc = 'column' in b && b.column ? b.column : ''\n return ac.localeCompare(bc)\n })\n\n return DivergenceResultSchema.parse({\n divergences: filtered,\n totalAffected: filtered.length,\n computedAt: new Date().toISOString(),\n })\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { NodeType, Provenance, observedEdgeId } from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\n\nexport const SCHEMA_VERSION = 7\n\nexport interface PersistedGraph {\n schemaVersion: number\n exportedAt: string\n graph: ReturnType<NeatGraph['export']>\n}\n\n// v1 → v2: ServiceNode shed `pgDriverVersion` (ADR-019). Compat traversal reads\n// `dependencies[driver]` instead. Strip the field from any v1 snapshot rather\n// than hard-failing — a stale snapshot on disk shouldn't cost a re-extract.\nfunction migrateV1ToV2(payload: PersistedGraph): PersistedGraph {\n const nodes = (payload.graph as { nodes?: Array<{ attributes?: Record<string, unknown> }> })\n .nodes\n if (Array.isArray(nodes)) {\n for (const node of nodes) {\n if (node.attributes && 'pgDriverVersion' in node.attributes) {\n delete node.attributes.pgDriverVersion\n }\n }\n }\n return { ...payload, schemaVersion: 2 }\n}\n\n// v2 → v3: Provenance enum shrinks to four values (ADR-068). Any edge whose\n// provenance still carries the pre-v0.3.5 'FRONTIER' literal is rewritten to\n// Provenance.OBSERVED on load. The target ref is unchanged — FrontierNodes\n// remain placeholders for unresolved peers; only how the edge was labelled\n// changes. If the edge id still carries the legacy provenance segment, it\n// is re-keyed to the OBSERVED wire format so consumers that parse the id\n// (traversal, divergence query, MCP) see the same shape downstream.\n//\n// The 'FRONTIER' string literal here is the only place in the codebase that\n// recognises the legacy value; the Rule 1 contract scan exempts persist.ts\n// for exactly this reason.\n// v3 → v4: ServiceNode identity gains an optional env discriminator\n// (ADR-074 §2). The v4 wire format reads as a superset of v3 — the\n// env-less `service:<name>` form is preserved as the env=`'unknown'`\n// node and the v3 → v4 migration is a version-only bump.\n//\n// Edges and node ids that pre-date the env discriminator remain valid v4\n// ids; no rewrite is needed. Idempotent — re-running on a v4 snapshot\n// produces an identical payload.\nfunction migrateV3ToV4(payload: PersistedGraph): PersistedGraph {\n return { ...payload, schemaVersion: 4 }\n}\n\n// v4 → v5: the node union gains `SymbolNode` — a symbol under a file at\n// definition-span grain (ADR-158). The v5 wire format is a strict superset of\n// v4: a v4 snapshot simply carries no symbols, and re-extraction mints them on\n// the next pass. There is no field to rewrite and nothing to backfill — an older\n// snapshot's files and edges stay valid v5 verbatim — so this is a version-only\n// bump, the same shape as the v3 → v4 env-discriminator migration. Idempotent.\nfunction migrateV4ToV5(payload: PersistedGraph): PersistedGraph {\n return { ...payload, schemaVersion: 5 }\n}\n\n// v5 → v6: a table-grain InfraNode gains a `columns` attribute list (ADR-157 §1)\n// — columns are provenanced attributes on the table node, observed from the\n// `db.statement` production runs. The v6 wire format is a strict superset of v5:\n// an older snapshot's table nodes simply carry no columns, and re-ingestion lands\n// them on the next production statement. Backfill `columns: []` on every table\n// InfraNode (`sql-table` / `supabase-table`) that lacks it, so a loaded snapshot\n// reads uniform — present-and-empty rather than absent — the same shape the mint\n// path grows thereafter. A non-table InfraNode is left untouched (it carries no\n// columns). Idempotent — re-running on a v6 snapshot changes nothing.\nfunction migrateV5ToV6(payload: PersistedGraph): PersistedGraph {\n const nodes = (payload.graph as {\n nodes?: Array<{ attributes?: Record<string, unknown> }>\n }).nodes\n if (Array.isArray(nodes)) {\n for (const node of nodes) {\n const attrs = node.attributes\n if (!attrs || attrs.type !== NodeType.InfraNode) continue\n if (attrs.kind !== 'sql-table' && attrs.kind !== 'supabase-table') continue\n if (!Array.isArray(attrs.columns)) attrs.columns = []\n }\n }\n return { ...payload, schemaVersion: 6 }\n}\n\n// v6 → v7: the node union gains `ServerActionNode` — a Next.js Server Action at\n// (service, module, exportName) grain (ADR-168). The v7 wire format is a strict\n// superset of v6: a v6 snapshot simply carries no actions, and re-extraction\n// mints them on the next pass. There is no field to rewrite and nothing to\n// backfill — an older snapshot's nodes and edges stay valid v7 verbatim — so this\n// is a version-only bump, the same shape as the v4 → v5 SymbolNode migration.\n// Idempotent — re-running on a v7 snapshot produces an identical payload.\nfunction migrateV6ToV7(payload: PersistedGraph): PersistedGraph {\n return { ...payload, schemaVersion: 7 }\n}\n\nfunction migrateV2ToV3(payload: PersistedGraph): PersistedGraph {\n const edges = (payload.graph as {\n edges?: Array<{\n key?: string\n attributes?: Record<string, unknown>\n }>\n }).edges\n if (Array.isArray(edges)) {\n for (const edge of edges) {\n const attrs = edge.attributes\n // 'FRONTIER' is the pre-v0.3.5 literal — read-only here, never written\n // by current producers. The rewrite swaps to Provenance.OBSERVED.\n if (!attrs || attrs.provenance !== 'FRONTIER') continue\n attrs.provenance = Provenance.OBSERVED\n const type = typeof attrs.type === 'string' ? attrs.type : undefined\n const source = typeof attrs.source === 'string' ? attrs.source : undefined\n const target = typeof attrs.target === 'string' ? attrs.target : undefined\n if (type && source && target) {\n const newId = observedEdgeId(source, target, type)\n attrs.id = newId\n if (edge.key) edge.key = newId\n }\n }\n }\n return { ...payload, schemaVersion: 3 }\n}\n\nasync function ensureDir(filePath: string): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true })\n}\n\nexport async function saveGraphToDisk(graph: NeatGraph, outPath: string): Promise<void> {\n await ensureDir(outPath)\n const payload: PersistedGraph = {\n schemaVersion: SCHEMA_VERSION,\n exportedAt: new Date().toISOString(),\n graph: graph.export(),\n }\n // Atomic write: drop into <name>.tmp first, then rename. A crash mid-write\n // leaves the previous snapshot intact instead of a half-truncated file.\n const tmp = `${outPath}.tmp`\n await fs.writeFile(tmp, JSON.stringify(payload), 'utf8')\n await fs.rename(tmp, outPath)\n}\n\nexport async function loadGraphFromDisk(graph: NeatGraph, outPath: string): Promise<void> {\n let raw: string\n try {\n raw = await fs.readFile(outPath, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return\n throw err\n }\n let payload = JSON.parse(raw) as PersistedGraph\n if (payload.schemaVersion === 1) {\n payload = migrateV1ToV2(payload)\n }\n if (payload.schemaVersion === 2) {\n payload = migrateV2ToV3(payload)\n }\n if (payload.schemaVersion === 3) {\n payload = migrateV3ToV4(payload)\n }\n if (payload.schemaVersion === 4) {\n payload = migrateV4ToV5(payload)\n }\n if (payload.schemaVersion === 5) {\n payload = migrateV5ToV6(payload)\n }\n if (payload.schemaVersion === 6) {\n payload = migrateV6ToV7(payload)\n }\n if (payload.schemaVersion !== SCHEMA_VERSION) {\n throw new Error(\n `persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`,\n )\n }\n graph.clear()\n graph.import(payload.graph)\n}\n\nexport interface PersistLoopOptions {\n // How often the periodic background save fires. Defaults to 60s.\n intervalMs?: number\n // Whether a SIGTERM/SIGINT should flush the graph and then exit the process.\n // Defaults to true, which is what the standalone owners (`neat serve`,\n // `neat watch`) want: the persist loop is the only thing holding the process\n // open, so it owns the shutdown — flush, then exit.\n //\n // The daemon owns its own orderly shutdown (it closes its listeners, flushes\n // every slot, clears its `daemon.json` + the machine-wide discovery copy, and\n // removes its pid file before exiting). It runs many persist loops, one per\n // project, and an exiting signal handler inside any of them would end the\n // process out from under that teardown — leaving the discovery copy and pid\n // file behind. So the daemon passes `false`: each loop still saves on its\n // interval and unhooks cleanly on teardown, but the daemon decides when the\n // process exits.\n exitOnSignal?: boolean\n}\n\n// Periodic save + (optionally) a best-effort save-and-exit on SIGTERM/SIGINT.\n// Returns a cleanup that clears the interval and unhooks any signal handlers —\n// important for tests so they don't keep the process alive, and for the daemon\n// so a torn-down slot's loop stops reacting to signals.\nexport function startPersistLoop(\n graph: NeatGraph,\n outPath: string,\n opts: PersistLoopOptions = {},\n): () => void {\n const intervalMs = opts.intervalMs ?? 60_000\n const exitOnSignal = opts.exitOnSignal ?? true\n let stopped = false\n\n const tick = async (): Promise<void> => {\n if (stopped) return\n try {\n await saveGraphToDisk(graph, outPath)\n } catch (err) {\n console.error('persist: periodic save failed', err)\n }\n }\n\n const interval = setInterval(() => {\n void tick()\n }, intervalMs)\n\n const onSignal = exitOnSignal\n ? (signal: NodeJS.Signals): void => {\n void (async () => {\n try {\n await saveGraphToDisk(graph, outPath)\n } catch (err) {\n console.error(`persist: ${signal} save failed`, err)\n } finally {\n process.exit(0)\n }\n })()\n }\n : null\n\n if (onSignal) {\n process.on('SIGTERM', onSignal)\n process.on('SIGINT', onSignal)\n }\n\n return () => {\n stopped = true\n clearInterval(interval)\n if (onSignal) {\n process.off('SIGTERM', onSignal)\n process.off('SIGINT', onSignal)\n }\n }\n}\n\n// Snapshot merge (ADR-074 §1) lives in ingest.ts — that's the mutation-\n// authority boundary per the lifecycle contract (Rule 3 / ADR-030). The\n// merge is a form of ingestion: an external snapshot lands on the live graph\n// the same way an OTel span does, preserving the EXTRACTED + OBSERVED\n// coexistence contract along the way.\n","import { promises as fs } from 'node:fs'\nimport type { GraphEdge, GraphNode } from '@neat.is/types'\nimport type { NeatGraph } from './graph.js'\n\n// Diff a snapshot on disk (or fetched over HTTP) against the live in-memory\n// graph. The \"base\" is the snapshot you supplied; the \"current\" is whatever\n// the server has loaded right now. Symmetric in shape, but consumers usually\n// want to read it as \"what changed since base.\"\n//\n// The shape mirrors the issue's spec (#77):\n// { added: { nodes, edges }, removed: { nodes, edges },\n// changed: { nodes: [{id, before, after}], edges: [{id, before, after}] } }\n//\n// Both timestamps are echoed back so the caller doesn't have to track them\n// separately.\n\ninterface PersistedNodeEntry {\n key?: string\n attributes?: Record<string, unknown>\n}\n\ninterface PersistedEdgeEntry {\n key?: string\n source?: string\n target?: string\n attributes?: Record<string, unknown>\n}\n\nexport interface PersistedSnapshot {\n schemaVersion?: number\n exportedAt?: string\n graph?: {\n nodes?: PersistedNodeEntry[]\n edges?: PersistedEdgeEntry[]\n }\n}\n\nexport interface GraphDiff {\n base: { exportedAt?: string }\n current: { exportedAt: string }\n added: { nodes: GraphNode[]; edges: GraphEdge[] }\n removed: { nodes: GraphNode[]; edges: GraphEdge[] }\n changed: {\n nodes: { id: string; before: GraphNode; after: GraphNode }[]\n edges: { id: string; before: GraphEdge; after: GraphEdge }[]\n }\n}\n\nexport async function loadSnapshotForDiff(target: string): Promise<PersistedSnapshot> {\n if (/^https?:\\/\\//i.test(target)) {\n const res = await fetch(target)\n if (!res.ok) {\n throw new Error(`fetch ${target} failed: ${res.status} ${res.statusText}`)\n }\n return (await res.json()) as PersistedSnapshot\n }\n const raw = await fs.readFile(target, 'utf8')\n return JSON.parse(raw) as PersistedSnapshot\n}\n\nfunction indexEntries<T>(\n entries: { key?: string; attributes?: Record<string, unknown> }[] | undefined,\n): Map<string, T> {\n const m = new Map<string, T>()\n if (!entries) return m\n for (const entry of entries) {\n const id = (entry.attributes?.id as string | undefined) ?? entry.key\n if (!id) continue\n m.set(id, entry.attributes as T)\n }\n return m\n}\n\nexport function computeGraphDiff(\n liveGraph: NeatGraph,\n baseSnapshot: PersistedSnapshot,\n currentExportedAt: string = new Date().toISOString(),\n): GraphDiff {\n const baseNodes = indexEntries<GraphNode>(baseSnapshot.graph?.nodes)\n const baseEdges = indexEntries<GraphEdge>(baseSnapshot.graph?.edges)\n\n const liveNodes = new Map<string, GraphNode>()\n liveGraph.forEachNode((id, attrs) => liveNodes.set(id, attrs as GraphNode))\n const liveEdges = new Map<string, GraphEdge>()\n liveGraph.forEachEdge((id, attrs) => liveEdges.set(id, attrs as GraphEdge))\n\n const result: GraphDiff = {\n base: { exportedAt: baseSnapshot.exportedAt },\n current: { exportedAt: currentExportedAt },\n added: { nodes: [], edges: [] },\n removed: { nodes: [], edges: [] },\n changed: { nodes: [], edges: [] },\n }\n\n for (const [id, after] of liveNodes) {\n const before = baseNodes.get(id)\n if (!before) {\n result.added.nodes.push(after)\n } else if (!shallowEqual(before, after)) {\n result.changed.nodes.push({ id, before, after })\n }\n }\n for (const [id, before] of baseNodes) {\n if (!liveNodes.has(id)) result.removed.nodes.push(before)\n }\n for (const [id, after] of liveEdges) {\n const before = baseEdges.get(id)\n if (!before) {\n result.added.edges.push(after)\n } else if (!shallowEqual(before, after)) {\n result.changed.edges.push({ id, before, after })\n }\n }\n for (const [id, before] of baseEdges) {\n if (!liveEdges.has(id)) result.removed.edges.push(before)\n }\n\n return result\n}\n\n// Stable JSON comparison. Snapshot order isn't guaranteed, so canonicalising\n// keys before stringify keeps the comparison robust against re-ordered fields.\nfunction shallowEqual(a: unknown, b: unknown): boolean {\n return canonicalJson(a) === canonicalJson(b)\n}\n\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(value, (_key, v) => {\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n return Object.keys(v as Record<string, unknown>)\n .sort()\n .reduce<Record<string, unknown>>((acc, k) => {\n acc[k] = (v as Record<string, unknown>)[k]\n return acc\n }, {})\n }\n return v\n })\n}\n","// Project registry — owns the per-project state that lives alongside the\n// graph map: snapshot/error/stale paths, scan path, optional search index.\n//\n// Routes use it via `resolve(project)`. Server / watch construct it once at\n// boot and pass it to buildApi. Tests can mock it with a small literal.\n\nimport path from 'node:path'\nimport type { NeatGraph } from './graph.js'\nimport { DEFAULT_PROJECT, getGraph } from './graph.js'\nimport type { SearchIndex } from './search.js'\n\nexport interface ProjectPaths {\n snapshotPath: string\n errorsPath: string\n staleEventsPath: string\n embeddingsCachePath: string\n // Policy-violations log per ADR-041 § Append-only ndjson sidecars. Lives\n // in the same neat-out directory as the other ndjson sidecars; daemons\n // wire PolicyViolationsLog to it.\n policyViolationsPath: string\n}\n\n// Default project keeps the legacy filenames so existing M5 / β / γ users\n// see no behaviour change. Named projects fan out by name (ADR-026).\nexport function pathsForProject(project: string, baseDir: string): ProjectPaths {\n if (project === DEFAULT_PROJECT) {\n return {\n snapshotPath: path.join(baseDir, 'graph.json'),\n errorsPath: path.join(baseDir, 'errors.ndjson'),\n staleEventsPath: path.join(baseDir, 'stale-events.ndjson'),\n embeddingsCachePath: path.join(baseDir, 'embeddings.json'),\n policyViolationsPath: path.join(baseDir, 'policy-violations.ndjson'),\n }\n }\n return {\n snapshotPath: path.join(baseDir, `${project}.json`),\n errorsPath: path.join(baseDir, `errors.${project}.ndjson`),\n staleEventsPath: path.join(baseDir, `stale-events.${project}.ndjson`),\n embeddingsCachePath: path.join(baseDir, `embeddings.${project}.json`),\n policyViolationsPath: path.join(baseDir, `policy-violations.${project}.ndjson`),\n }\n}\n\nexport interface ProjectContext {\n name: string\n graph: NeatGraph\n scanPath?: string\n paths: ProjectPaths\n searchIndex?: SearchIndex\n}\n\nexport class Projects {\n private contexts = new Map<string, ProjectContext>()\n\n upsert(ctx: ProjectContext): void {\n this.contexts.set(ctx.name, ctx)\n }\n\n set(\n name: string,\n init: Omit<ProjectContext, 'name' | 'graph'> & { graph?: NeatGraph },\n ): ProjectContext {\n const ctx: ProjectContext = {\n name,\n graph: init.graph ?? getGraph(name),\n scanPath: init.scanPath,\n paths: init.paths,\n searchIndex: init.searchIndex,\n }\n this.contexts.set(name, ctx)\n return ctx\n }\n\n get(name: string): ProjectContext | undefined {\n return this.contexts.get(name)\n }\n\n has(name: string): boolean {\n return this.contexts.has(name)\n }\n\n list(): string[] {\n return [...this.contexts.keys()].sort()\n }\n\n attachSearchIndex(name: string, index: SearchIndex | undefined): void {\n const ctx = this.contexts.get(name)\n if (ctx) ctx.searchIndex = index\n }\n}\n\n// Parses NEAT_PROJECTS=a,b,c; trims whitespace, drops empty entries.\n// `default` is implicit (always loaded), so callers usually filter it out\n// before iterating extra projects.\nexport function parseExtraProjects(raw: string | undefined): string[] {\n if (!raw) return []\n return raw\n .split(',')\n .map((p) => p.trim())\n .filter((p) => p.length > 0 && p !== DEFAULT_PROJECT)\n}\n","/**\n * Machine-level project registry + machine-wide daemon discovery.\n *\n * This module owns two surfaces under `~/.neat/`:\n *\n * 1. The legacy project registry at `~/.neat/projects.json` (ADR-048). One\n * file, per-user, machine-local, not synced. Under the project-daemon\n * contract (ADR-096) it is no longer the coordination point — it is read\n * once for migration and otherwise left to the additive writes the daemon\n * and orchestrator still perform. The read-modify-write helpers below keep\n * their atomic-write + exclusive-lock machinery for that legacy surface.\n *\n * 2. The machine-wide daemon discovery directory at `~/.neat/daemons/`\n * (ADR-096 §6). One file per running daemon — `<project>.json` — each owned\n * solely by the daemon that wrote it (on start) and removes it (on graceful\n * stop). Discovery is **append-only and lock-free**: a reader scans the\n * directory and reconciles liveness; it never acquires a shared lock, so it\n * can never deadlock against a daemon (#506). Losing or rebuilding the\n * directory costs discovery convenience, not correctness — each project's\n * own `neat-out/daemon.json` stays authoritative.\n *\n * `neat ps` / `neat list` and the per-daemon `pause` / `resume` / `uninstall`\n * verbs read discovery, falling back to the legacy registry where no daemon\n * file is present yet (the migration window before every daemon self-describes).\n *\n * The legacy lock is a file we exclusively-create (`O_EXCL`), hold while we\n * mutate, and unlink on the way out. Crude but cross-platform; matches what\n * `proper-lockfile` does internally without pulling the dep in. It never sits\n * on the discovery path.\n */\n\nimport { promises as fs } from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport {\n RegistryFileSchema,\n type RegistryEntry,\n type RegistryFile,\n type RegistryStatus,\n} from '@neat.is/types'\n\nconst LOCK_TIMEOUT_MS = 5_000\nconst LOCK_RETRY_MS = 50\n\n// Resolve `~/.neat/` per call so tests can override `HOME` / `NEAT_HOME`\n// before each run without module-load order mattering.\nfunction neatHome(): string {\n const override = process.env.NEAT_HOME\n if (override && override.length > 0) return path.resolve(override)\n return path.join(os.homedir(), '.neat')\n}\n\nexport function registryPath(): string {\n return path.join(neatHome(), 'projects.json')\n}\n\nexport function registryLockPath(): string {\n return path.join(neatHome(), 'projects.json.lock')\n}\n\n// The daemon writes its PID here on startup (daemon.ts). It's the authoritative\n// \"is a neat daemon running\" signal — more reliable than matching the lock's\n// own PID, since the daemon holds the registry lock only for brief read-modify-\n// write windows and an init that contends usually catches an orphaned lock\n// rather than the daemon mid-write.\nfunction daemonPidPath(): string {\n return path.join(neatHome(), 'neatd.pid')\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// #366 / ADR-096 §6 — machine-wide daemon discovery, lock-free.\n//\n// Each running daemon drops a `<project>.json` file into `~/.neat/daemons/`\n// on start and removes it on graceful stop. The file is a copy of that\n// project's authoritative `neat-out/daemon.json` record. A daemon owns only\n// its own file — there is no shared file and no shared lock. Discovery reads\n// the directory and reconciles liveness; it never acquires a lock, so it can\n// never deadlock against a daemon mid-write the way the registry lock did\n// (#506). The shape mirrors what the daemon writes (keystone #508); we read it\n// as plain JSON rather than importing the daemon's writer so the two stay\n// decoupled, and validate defensively so a malformed file is skipped, not\n// fatal.\n// ─────────────────────────────────────────────────────────────────────────\n\nexport interface DaemonPorts {\n rest: number\n otlp: number\n web: number\n}\n\nexport interface DaemonRecord {\n project: string\n // Project root whose `neat-out/` holds the authoritative record.\n projectPath: string\n pid: number\n status: 'running' | 'stopped'\n ports: DaemonPorts\n // ISO8601.\n startedAt: string\n neatVersion: string\n}\n\n// A discovered daemon plus the liveness verdict the reader reconciled. `live`\n// is true only when the record claims `running` AND its pid is actually alive\n// — a daemon that crashed without clearing its file reads as `running` but\n// `live: false`, so `neat ps` never reports a ghost as up.\nexport interface DiscoveredDaemon {\n record: DaemonRecord\n live: boolean\n // Where the discovery copy was read from. Useful for diagnostics and for the\n // per-daemon verbs that clean a stale file.\n source: string\n}\n\nexport function daemonsDir(): string {\n return path.join(neatHome(), 'daemons')\n}\n\nfunction isFiniteInt(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v)\n}\n\n// Parse one discovery file's contents into a DaemonRecord, or undefined when\n// the shape doesn't hold. Defensive on purpose: discovery is convenience, so a\n// half-written or hand-edited file is skipped rather than crashing `neat ps`.\nfunction parseDaemonRecord(raw: string): DaemonRecord | undefined {\n let obj: unknown\n try {\n obj = JSON.parse(raw)\n } catch {\n return undefined\n }\n if (typeof obj !== 'object' || obj === null) return undefined\n const r = obj as Record<string, unknown>\n const ports = r.ports as Record<string, unknown> | undefined\n if (\n typeof r.project !== 'string' ||\n typeof r.projectPath !== 'string' ||\n !isFiniteInt(r.pid) ||\n (r.status !== 'running' && r.status !== 'stopped') ||\n typeof r.startedAt !== 'string' ||\n typeof r.neatVersion !== 'string' ||\n !ports ||\n !isFiniteInt(ports.rest) ||\n !isFiniteInt(ports.otlp) ||\n !isFiniteInt(ports.web)\n ) {\n return undefined\n }\n return {\n project: r.project,\n projectPath: r.projectPath,\n pid: r.pid,\n status: r.status,\n ports: { rest: ports.rest, otlp: ports.otlp, web: ports.web },\n startedAt: r.startedAt,\n neatVersion: r.neatVersion,\n }\n}\n\n// Liveness for a discovered daemon. Exported (via the default probe) so the\n// verbs and tests reconcile the same way: `running` claim AND pid alive.\nexport function isPidAlive(pid: number): boolean {\n return isPidAliveDefault(pid)\n}\n\nexport interface DiscoveryProbe {\n isPidAlive(pid: number): boolean\n}\n\nconst defaultDiscoveryProbe: DiscoveryProbe = { isPidAlive: isPidAliveDefault }\n\n/**\n * Scan `~/.neat/daemons/` and return every well-formed discovery record with\n * its reconciled liveness. Lock-free: a plain directory read, no rendezvous.\n *\n * A missing directory (no daemon has ever started under this model) yields an\n * empty list — first run, nothing to discover. Malformed or unreadable files\n * are skipped silently; discovery degrades to \"fewer entries,\" never an error.\n */\nexport async function discoverDaemons(\n probe: DiscoveryProbe = defaultDiscoveryProbe,\n): Promise<DiscoveredDaemon[]> {\n const dir = daemonsDir()\n let names: string[]\n try {\n names = await fs.readdir(dir)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n const out: DiscoveredDaemon[] = []\n for (const name of names) {\n if (!name.endsWith('.json')) continue\n const file = path.join(dir, name)\n let raw: string\n try {\n raw = await fs.readFile(file, 'utf8')\n } catch {\n continue\n }\n const record = parseDaemonRecord(raw)\n if (!record) continue\n const live = record.status === 'running' && probe.isPidAlive(record.pid)\n out.push({ record, live, source: file })\n }\n out.sort((a, b) => a.record.project.localeCompare(b.record.project))\n return out\n}\n\n/**\n * Remove a daemon's discovery file. A daemon owns its own file, so this is for\n * the per-daemon verbs reconciling a record whose daemon is gone (a crashed\n * daemon that never cleared its own file) — never a rendezvous another process\n * coordinates through. Best-effort: an already-absent file is success.\n */\nexport async function removeDaemonRecord(source: string): Promise<void> {\n await fs.unlink(source).catch(() => {})\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// #366 / ADR-096 §8 — migration off the global registry.\n//\n// Installs carrying `~/.neat/projects.json` map their registered projects onto\n// per-project daemons on first run under this model. The discovery directory is\n// authoritative for \"what's running\"; the registry is read once to surface\n// projects that were registered before any daemon self-described, so the\n// machine-wide verbs still see them during the migration window. The registry\n// is no longer the coordination surface — nothing here writes it back.\n// ─────────────────────────────────────────────────────────────────────────\n\n// A unified view for the machine-wide verbs: a discovered daemon when one is\n// present, otherwise a legacy registry entry projected into the same shape so\n// `neat ps` / `neat list` render one table regardless of which surface the\n// project lives on yet.\nexport interface MachineProject {\n project: string\n projectPath: string\n // 'running' / 'stopped' come from a live discovery record; 'registered'\n // marks a legacy registry entry with no daemon file yet (migration window).\n state: 'running' | 'stopped' | 'registered'\n // Present only when a discovery record backs this row.\n ports?: DaemonPorts\n // Legacy registry status when the row came from the registry; undefined for\n // a discovery-backed row.\n registryStatus?: RegistryStatus\n pid?: number\n}\n\n/**\n * The machine-wide project view the CLI verbs render. Discovery wins: every\n * `~/.neat/daemons/` record becomes a row (running or stopped per liveness).\n * Legacy registry entries whose path isn't already covered by a discovery\n * record are folded in as `registered` rows so a pre-#508 install still lists\n * its projects. Keyed on resolved path so a registry entry and its daemon\n * record collapse to one row.\n *\n * Read-only on both surfaces. The registry is read once for migration; nothing\n * here writes it back, in keeping with ADR-096 §8.\n */\nexport async function listMachineProjects(\n probe: DiscoveryProbe = defaultDiscoveryProbe,\n): Promise<MachineProject[]> {\n const discovered = await discoverDaemons(probe)\n const byPath = new Map<string, MachineProject>()\n for (const d of discovered) {\n const key = await normalizeProjectPath(d.record.projectPath)\n byPath.set(key, {\n project: d.record.project,\n projectPath: d.record.projectPath,\n state: d.live ? 'running' : 'stopped',\n ports: d.record.ports,\n pid: d.record.pid,\n })\n }\n\n // Migration read: legacy registry entries not already covered by a daemon\n // record. Read once, never written back.\n let legacy: RegistryEntry[] = []\n try {\n legacy = (await readRegistry()).projects\n } catch {\n // A corrupt legacy file shouldn't sink discovery — the daemons we found are\n // still valid. Skip the legacy fold.\n legacy = []\n }\n for (const entry of legacy) {\n const key = await normalizeProjectPath(entry.path)\n if (byPath.has(key)) continue\n byPath.set(key, {\n project: entry.name,\n projectPath: entry.path,\n state: 'registered',\n registryStatus: entry.status,\n })\n }\n\n return [...byPath.values()].sort((a, b) => a.project.localeCompare(b.project))\n}\n\n// Find a discovery record by project name, with its reconciled liveness. The\n// per-daemon verbs key on the project name the operator types; discovery is the\n// source of truth for whether a daemon is running and which pid to signal.\nexport async function findDaemonByProject(\n name: string,\n probe: DiscoveryProbe = defaultDiscoveryProbe,\n): Promise<DiscoveredDaemon | undefined> {\n const discovered = await discoverDaemons(probe)\n return discovered.find((d) => d.record.project === name)\n}\n\n// Signal a running daemon to shut down via its discovery-recorded pid. SIGTERM\n// is the graceful-stop signal every daemon already wires (neatd.ts) — the\n// daemon clears its own discovery file on the way out. Returns true when the\n// signal was delivered, false when the pid was already gone (nothing to stop).\nexport function signalDaemonStop(pid: number): boolean {\n try {\n process.kill(pid, 'SIGTERM')\n return true\n } catch (err) {\n // ESRCH → already gone; treat as a no-op success of intent. EPERM and the\n // rest surface as \"couldn't signal\" so the verb can report honestly.\n if ((err as NodeJS.ErrnoException).code === 'ESRCH') return false\n throw err\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// #432 — distinguish a live daemon from a genuinely stale lock.\n//\n// `neat init` used to hang the full timeout on any contended lock and then\n// print one remediation: \"remove the file by hand.\" That advice is actively\n// dangerous when a neat daemon is alive — the daemon grabs the lock for its\n// own registry writes, so hand-removing it races the daemon and corrupts the\n// registry for the live process. The lock now carries the holder PID, and the\n// timeout (or first contention with a live daemon) resolves to a message that\n// names who holds it and what's safe to do.\n// ─────────────────────────────────────────────────────────────────────────\n\nexport type LockHolder =\n | { kind: 'daemon'; pid: number }\n | { kind: 'command'; pid: number }\n | { kind: 'stale' }\n\n// Probes the holder-resolution logic depends on, broken out so tests can drive\n// each branch without a real daemon or live PIDs.\nexport interface LockHolderProbe {\n // POSIX liveness via `process.kill(pid, 0)`: true if the process exists\n // (including EPERM — alive but owned by another user), false if it's gone.\n isPidAlive(pid: number): boolean\n // PID recorded in `~/.neat/neatd.pid`, or undefined if there's no pidfile.\n daemonPidFromFile(): Promise<number | undefined>\n // Whether the daemon's always-open `/health` endpoint answers. Confirms a\n // live neatd.pid is actually our daemon and not a reused PID.\n daemonResponds(): Promise<boolean>\n}\n\nfunction isPidAliveDefault(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n } catch (err) {\n // ESRCH → no such process (dead). EPERM → exists but owned by another user\n // (alive). Treat anything else as not-alive: we'd rather under-claim a live\n // holder than wrongly block on a lock we can't verify.\n return (err as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\nasync function readPidFile(file: string): Promise<number | undefined> {\n try {\n const raw = await fs.readFile(file, 'utf8')\n const pid = Number.parseInt(raw.trim(), 10)\n return Number.isInteger(pid) && pid > 0 ? pid : undefined\n } catch {\n return undefined\n }\n}\n\nconst defaultLockHolderProbe: LockHolderProbe = {\n isPidAlive: isPidAliveDefault,\n daemonPidFromFile: () => readPidFile(daemonPidPath()),\n async daemonResponds(): Promise<boolean> {\n const base = process.env.NEAT_API_URL ?? 'http://localhost:8080'\n try {\n // `/health` stays unauthenticated in every mode (auth.ts), so a reachable\n // daemon answers it even with a bearer token set. Any HTTP response — not\n // just 2xx — means a daemon owns the port. A short timeout keeps the\n // fail-fast path well under a second.\n await fetch(`${base}/health`, { signal: AbortSignal.timeout(750) })\n return true\n } catch {\n return false\n }\n },\n}\n\n// Read the PID a lock holder recorded when it created the lock. Undefined for a\n// legacy empty lock, an unreadable file, or a lock unlinked out from under us.\nasync function readLockPid(lockPath: string): Promise<number | undefined> {\n return readPidFile(lockPath)\n}\n\n// Decide who holds (or orphaned) the lock. A live daemon dominates: while neatd\n// is alive, hand-removing the lock is never safe, so we surface the daemon\n// message even when the lock itself is an empty orphan. We never classify our\n// own process as the blocking daemon — a daemon serializing two of its own\n// registry writes contends with itself briefly and should just retry.\nexport async function classifyLockHolder(\n lockPath: string,\n probe: LockHolderProbe = defaultLockHolderProbe,\n): Promise<LockHolder> {\n const lockPid = await readLockPid(lockPath)\n const daemonPid = await probe.daemonPidFromFile()\n if (\n daemonPid !== undefined &&\n daemonPid !== process.pid &&\n probe.isPidAlive(daemonPid) &&\n // The lock already names the daemon, or the daemon answers on its port.\n // Either confirms a live daemon is in the picture (the second guards\n // against a stale pidfile whose PID got reused).\n (daemonPid === lockPid || (await probe.daemonResponds()))\n ) {\n return { kind: 'daemon', pid: daemonPid }\n }\n if (lockPid !== undefined && lockPid !== process.pid && probe.isPidAlive(lockPid)) {\n return { kind: 'command', pid: lockPid }\n }\n return { kind: 'stale' }\n}\n\nexport function lockHolderMessage(holder: LockHolder, lockPath: string, timeoutMs: number): string {\n switch (holder.kind) {\n case 'daemon':\n return (\n `The neat daemon (pid ${holder.pid}) is holding the registry lock. ` +\n 'Register this project through the daemon, or stop neatd and re-run `neat init`.'\n )\n case 'command':\n return (\n `Another neat command (pid ${holder.pid}) is holding the registry lock. ` +\n \"Wait for it to finish, or check `ps` if you're not sure what's running.\"\n )\n case 'stale':\n return (\n `neat registry: timed out after ${timeoutMs}ms waiting for ${lockPath}. ` +\n 'Another neat process is holding the lock; if no such process exists, remove the file by hand.'\n )\n }\n}\n\n/**\n * Path normalisation per ADR-048 #7. Two `init` calls from different relative\n * paths to the same dir must collapse to one entry. `path.resolve` handles\n * relative-to-cwd; we pass it through `fs.realpath` when the dir exists so\n * symlinked paths land on the same canonical entry too.\n */\nexport async function normalizeProjectPath(input: string): Promise<string> {\n const resolved = path.resolve(input)\n try {\n return await fs.realpath(resolved)\n } catch {\n return resolved\n }\n}\n\n/**\n * tmp + fsync + rename. The fsync on the data fd guarantees the bytes are on\n * disk before rename swaps the inode; rename itself is atomic on POSIX.\n *\n * Exported so the init flow and test harnesses can use the same helper.\n */\nexport async function writeAtomically(target: string, contents: string): Promise<void> {\n await fs.mkdir(path.dirname(target), { recursive: true })\n const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`\n const fd = await fs.open(tmp, 'w')\n try {\n await fd.writeFile(contents, 'utf8')\n await fd.sync()\n } finally {\n await fd.close()\n }\n await fs.rename(tmp, target)\n}\n\nasync function acquireLock(\n lockPath: string,\n timeoutMs: number = LOCK_TIMEOUT_MS,\n probe: LockHolderProbe = defaultLockHolderProbe,\n): Promise<void> {\n const deadline = Date.now() + timeoutMs\n await fs.mkdir(path.dirname(lockPath), { recursive: true })\n let probedHolder = false\n while (true) {\n try {\n const fd = await fs.open(lockPath, 'wx')\n try {\n // Stamp the lock with our PID so a contender can name who holds it and\n // tell a live holder apart from a stale file. Best-effort: an empty\n // lock still excludes correctly, it just can't be diagnosed.\n await fd.writeFile(`${process.pid}\\n`, 'utf8')\n } finally {\n await fd.close()\n }\n return\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code !== 'EEXIST') throw err\n // A live daemon holds the registry continuously enough that spinning the\n // full timeout is pointless — surface the routed remediation on the first\n // contention. Peer commands and stale locks fall through to the retry:\n // peers clear on their own, and a stale lock wants the timeout's guidance.\n if (!probedHolder) {\n probedHolder = true\n const holder = await classifyLockHolder(lockPath, probe)\n if (holder.kind === 'daemon') throw new Error(lockHolderMessage(holder, lockPath, timeoutMs))\n }\n if (Date.now() >= deadline) {\n const holder = await classifyLockHolder(lockPath, probe)\n throw new Error(lockHolderMessage(holder, lockPath, timeoutMs))\n }\n await new Promise((r) => setTimeout(r, LOCK_RETRY_MS))\n }\n }\n}\n\nasync function releaseLock(lockPath: string): Promise<void> {\n await fs.unlink(lockPath).catch(() => {})\n}\n\nasync function withLock<T>(fn: () => Promise<T>): Promise<T> {\n const lock = registryLockPath()\n await acquireLock(lock)\n try {\n return await fn()\n } finally {\n await releaseLock(lock)\n }\n}\n\n/**\n * Read the registry from disk. Returns an empty registry if the file does\n * not exist yet — first run, never registered anything.\n *\n * Throws on parse / schema errors. The contract is single-source-of-truth;\n * a corrupt file is louder than a silent reset.\n */\nexport async function readRegistry(): Promise<RegistryFile> {\n const file = registryPath()\n let raw: string\n try {\n raw = await fs.readFile(file, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { version: 1, projects: [] }\n }\n throw err\n }\n const parsed = JSON.parse(raw)\n return RegistryFileSchema.parse(parsed)\n}\n\nasync function writeRegistry(reg: RegistryFile): Promise<void> {\n // Re-parse before writing to surface schema drift introduced by callers\n // mutating the in-memory object directly.\n const validated = RegistryFileSchema.parse(reg)\n await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + '\\n')\n}\n\nexport interface AddProjectOptions {\n name: string\n path: string\n languages?: string[]\n status?: RegistryStatus\n}\n\nexport class ProjectNameCollisionError extends Error {\n readonly projectName: string\n constructor(name: string) {\n super(`neat registry: a project named \"${name}\" is already registered`)\n this.name = 'ProjectNameCollisionError'\n this.projectName = name\n }\n}\n\n/**\n * Register a project, or update its `lastSeenAt` if the same path is already\n * registered under the same name (idempotent re-init).\n *\n * Hard error on name collision against a different path — ADR-046 #7. The\n * caller can recover by passing `--project <new-name>`.\n */\nexport async function addProject(opts: AddProjectOptions): Promise<RegistryEntry> {\n const resolvedPath = await normalizeProjectPath(opts.path)\n return withLock(async () => {\n const reg = await readRegistry()\n const byName = reg.projects.find((p) => p.name === opts.name)\n const byPath = reg.projects.find((p) => p.path === resolvedPath)\n\n if (byName && byName.path !== resolvedPath) {\n throw new ProjectNameCollisionError(opts.name)\n }\n\n const now = new Date().toISOString()\n\n if (byName && byName.path === resolvedPath) {\n // Idempotent re-register: same name, same path. Refresh languages /\n // status if the caller passed new ones.\n byName.lastSeenAt = now\n if (opts.languages) byName.languages = opts.languages\n if (opts.status) byName.status = opts.status\n await writeRegistry(reg)\n return byName\n }\n\n if (byPath && byPath.name !== opts.name) {\n // Same dir already registered under a different name. Treat as a\n // collision so the user is forced to decide which name wins.\n throw new ProjectNameCollisionError(byPath.name)\n }\n\n const entry: RegistryEntry = {\n name: opts.name,\n path: resolvedPath,\n registeredAt: now,\n languages: opts.languages ?? [],\n status: opts.status ?? 'active',\n }\n reg.projects.push(entry)\n await writeRegistry(reg)\n return entry\n })\n}\n\nexport async function getProject(name: string): Promise<RegistryEntry | undefined> {\n const reg = await readRegistry()\n return reg.projects.find((p) => p.name === name)\n}\n\nexport async function listProjects(): Promise<RegistryEntry[]> {\n const reg = await readRegistry()\n return reg.projects\n}\n\nexport async function setStatus(name: string, status: RegistryStatus): Promise<RegistryEntry> {\n return withLock(async () => {\n const reg = await readRegistry()\n const entry = reg.projects.find((p) => p.name === name)\n if (!entry) throw new Error(`neat registry: no project named \"${name}\"`)\n entry.status = status\n await writeRegistry(reg)\n return entry\n })\n}\n\nexport async function touchLastSeen(name: string, at: string = new Date().toISOString()): Promise<void> {\n await withLock(async () => {\n const reg = await readRegistry()\n const entry = reg.projects.find((p) => p.name === name)\n if (!entry) return\n entry.lastSeenAt = at\n await writeRegistry(reg)\n })\n}\n\n/**\n * Remove the registry entry for `name`. Per ADR-048 #6: this only removes the\n * registry row. It does **not** touch `neat-out/`, `policy.json`, or any user\n * file in the project directory. SDK-install rollback is a separate flow\n * (`neat-rollback.patch`) that the caller opts in to.\n */\nexport async function removeProject(name: string): Promise<RegistryEntry | undefined> {\n return withLock(async () => {\n const reg = await readRegistry()\n const idx = reg.projects.findIndex((p) => p.name === name)\n if (idx < 0) return undefined\n const [removed] = reg.projects.splice(idx, 1)\n await writeRegistry(reg)\n return removed\n })\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// #463 — prune dead-path entries so the registry doesn't accumulate zombies.\n//\n// Ephemeral tmp-dir projects (smoke runs, throwaway demos) leave entries whose\n// `path` is gone from disk. Without pruning the daemon logs an ENOENT warning\n// for each on every startup forever and `/api/health` lists them as permanent\n// `broken` rows. Pruning removes user-facing state, so it's deliberately\n// conservative: we only ever drop an entry on a *definite* ENOENT (the path is\n// genuinely gone). A transient stat failure — EACCES, EBUSY, an unmounted\n// drive — leaves the entry untouched; it isn't dead, just unreachable.\n//\n// Two modes:\n// - Auto-prune (daemon bootstrap / reload) gates removal on a staleness TTL.\n// An ENOENT entry is only dropped if its `lastSeenAt` is older than the TTL,\n// so a recently-active project whose path is temporarily unavailable stays.\n// - `neat prune` is explicit user intent, so it drops any ENOENT entry\n// immediately regardless of age.\n// ─────────────────────────────────────────────────────────────────────────\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n// Default 7 days, mirroring NEAT_STALE_THRESHOLDS' env-override shape.\nexport const DEFAULT_PRUNE_TTL_MS = 7 * DAY_MS\n\nexport function pruneTtlMs(): number {\n const raw = process.env.NEAT_REGISTRY_PRUNE_TTL_MS\n if (!raw) return DEFAULT_PRUNE_TTL_MS\n const n = Number.parseInt(raw, 10)\n if (Number.isFinite(n) && n >= 0) return n\n console.warn(\n `[neat] NEAT_REGISTRY_PRUNE_TTL_MS could not be parsed (${raw}); using default ${DEFAULT_PRUNE_TTL_MS}ms`,\n )\n return DEFAULT_PRUNE_TTL_MS\n}\n\n// Classify a single path: 'gone' (definite ENOENT), 'present' (stat succeeded,\n// directory exists), or 'unknown' (a transient/ambiguous error — never prune).\n// Exported so the daemon and tests can probe the same logic.\nexport type PathStatus = 'gone' | 'present' | 'unknown'\n\nasync function statPathStatus(p: string): Promise<PathStatus> {\n try {\n const stat = await fs.stat(p)\n return stat.isDirectory() ? 'present' : 'unknown'\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === 'ENOENT' ? 'gone' : 'unknown'\n }\n}\n\nexport interface PruneOptions {\n // Override the staleness gate. Auto-prune passes a TTL; `neat prune` passes\n // `ttlMs: 0` to drop any ENOENT entry immediately. Default is the env-driven\n // TTL.\n ttlMs?: number\n // Override the path probe (tests drive ENOENT / EACCES / present branches\n // without a real filesystem). Defaults to a real stat.\n statPath?: (p: string) => Promise<PathStatus>\n // Clock injection for the staleness comparison; defaults to Date.now().\n now?: () => number\n}\n\n/**\n * Remove registry entries whose `path` is definitely gone (ENOENT). Goes\n * through the same lock + atomic-write path as every other mutation — never a\n * raw rewrite — so the contract's atomicity invariant holds.\n *\n * Staleness gate: an ENOENT entry is dropped only when `ttlMs` is 0 (explicit\n * `neat prune`) or its `lastSeenAt` (falling back to `registeredAt`) is older\n * than `ttlMs`. A fresh ENOENT entry under a non-zero TTL is left in place — the\n * daemon still marks it `broken`, the TTL is the safety margin before removal.\n *\n * Returns the entries that were removed.\n */\nexport async function pruneRegistry(opts: PruneOptions = {}): Promise<RegistryEntry[]> {\n const ttlMs = opts.ttlMs ?? pruneTtlMs()\n const statPath = opts.statPath ?? statPathStatus\n const now = opts.now ?? Date.now\n\n return withLock(async () => {\n const reg = await readRegistry()\n const removed: RegistryEntry[] = []\n const kept: RegistryEntry[] = []\n for (const entry of reg.projects) {\n const status = await statPath(entry.path)\n if (status !== 'gone') {\n // Present, or a transient/ambiguous error — keep it. We only prune on a\n // definite ENOENT.\n kept.push(entry)\n continue\n }\n // Path is gone. Drop it immediately when there's no TTL gate, otherwise\n // only once it's been quiet longer than the TTL.\n if (ttlMs <= 0) {\n removed.push(entry)\n continue\n }\n const lastSeen = Date.parse(entry.lastSeenAt ?? entry.registeredAt)\n const age = now() - (Number.isFinite(lastSeen) ? lastSeen : 0)\n if (age > ttlMs) {\n removed.push(entry)\n } else {\n kept.push(entry)\n }\n }\n if (removed.length === 0) return []\n reg.projects = kept\n await writeRegistry(reg)\n return removed\n })\n}\n\n","import Fastify, {\n type FastifyInstance,\n type FastifyReply,\n type FastifyRequest,\n} from 'fastify'\nimport cors from '@fastify/cors'\nimport type {\n ErrorEvent,\n GraphEdge,\n GraphNode,\n Policy,\n PolicyViolation,\n ProjectListEntry,\n} from '@neat.is/types'\nimport { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from '@neat.is/types'\nimport type { DivergenceType } from '@neat.is/types'\nimport {\n applyExtension,\n describeProjectInstrumentation,\n dryRunExtension,\n listUninstrumented,\n lookupInstrumentation,\n rollbackExtension,\n} from './extend/index.js'\nimport { computeDivergences } from './divergences.js'\nimport {\n LOGS_QUERY_DEFAULT_LIMIT,\n LOGS_QUERY_MAX_LIMIT,\n queryLogEntries,\n} from './logs-store.js'\nimport {\n evaluateAllPolicies,\n loadPolicyFile,\n PolicyViolationsLog,\n selectApplicablePolicies,\n} from './policy.js'\nimport type { NeatGraph } from './graph.js'\nimport { DEFAULT_PROJECT } from './graph.js'\nimport { extractFromDirectory } from './extract.js'\nimport { readExtractionHealth, extractionHealthPathFor } from './extract/errors.js'\nimport { readErrorEvents, readStaleEvents } from './ingest.js'\nimport {\n getBlastRadius,\n getObservedDependencies,\n getRootCause,\n getTransitiveDependencies,\n TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH,\n TRANSITIVE_DEPENDENCIES_MAX_DEPTH,\n} from './traverse.js'\nimport { computeGraphDiff, loadSnapshotForDiff } from './diff.js'\nimport { mergeSnapshot, SnapshotValidationError } from './ingest.js'\nimport { SCHEMA_VERSION, type PersistedGraph } from './persist.js'\nimport type { SearchIndex } from './search.js'\nimport type { Projects, ProjectContext } from './projects.js'\nimport { Projects as ProjectsClass, pathsForProject } from './projects.js'\nimport {\n findDaemonByProject,\n getProject as getRegistryProject,\n listProjects as listRegistryProjects,\n} from './registry.js'\nimport { handleSse } from './streaming.js'\nimport { mountBearerAuth, readAuthEnv } from './auth.js'\nimport {\n connectorMatchesProject,\n readConnectorsConfig,\n redactCredentialRef,\n} from './connectors-config.js'\nimport { getConnectorStatus, recordConnectorPoll, sanitizePollError } from './connectors/status.js'\nimport { runConnectorPoll } from './connectors/index.js'\nimport { buildRegistration } from './connectors/registry.js'\n\nexport interface BuildApiOptions {\n // Multi-project shape. Optional — when absent we synthesise a single-\n // project registry from the legacy fields below so existing callers\n // (mainly tests) keep working unchanged.\n projects?: Projects\n startedAt?: number\n\n // Legacy single-project shape. Mapped to project=`default` if `projects`\n // isn't provided.\n graph?: NeatGraph\n scanPath?: string\n errorsPath?: string\n staleEventsPath?: string\n searchIndex?: SearchIndex\n\n // ADR-073 §3 — bearer token required on `/api/*` and `/events`. Undefined\n // leaves the middleware off (loopback-only callers; the bind-authority\n // gate in startDaemon refuses to bind publicly without one).\n authToken?: string\n // ADR-073 §3 — when the operator runs behind a reverse proxy that already\n // authenticates the request, the daemon-side check is bypassed.\n trustProxy?: boolean\n // ADR-073 §3 amendment — public-read mode. When `true`, GET / HEAD / OPTIONS\n // bypass the bearer check; writes still require it. OTLP ingest is gated\n // independently and is unaffected by this flag.\n publicRead?: boolean\n // Issue #340 — per-project bootstrap status. When provided, project-scoped\n // routes for projects still extracting return 503 with `{ready: false}`\n // instead of 404.\n bootstrap?: {\n status: (name: string) => 'bootstrapping' | 'active' | 'broken' | undefined\n list: () => Array<{ name: string; status: 'bootstrapping' | 'active' | 'broken'; elapsedMs: number }>\n }\n // ADR-096 §4/§5 — the per-project daemon's identity. When set, this daemon\n // serves exactly this one project: `GET /projects` reports only it (the\n // dashboard pins to \"the project this daemon serves,\" not a machine-wide\n // list), and the daemon-wide `/health` carries it at the top level for the\n // spawn-reuse identity check. Absent → the legacy multi-project daemon, whose\n // `/projects` is the machine-wide registry passthrough.\n singleProject?: { name: string; path: string }\n\n // ADR-136 — the resolved NEAT_HOME the connector-status endpoint\n // (`GET /:project/connectors`) reads `~/.neat/connectors.json` from. The\n // daemon passes the same home its slot bootstrap read the file from. Absent\n // falls back to the env-based resolution in connectors-config.ts (the CLI /\n // test path), so callers that never touch connectors need not set it.\n connectorsHome?: string\n // #871 — test seam for the manual poll trigger (POST /connectors/:id/poll).\n // Defaults to runConnectorPoll; tests inject a stub so the trigger's wiring\n // is verifiable without a live provider round-trip.\n runPoll?: typeof runConnectorPoll\n}\n\ninterface SerializedGraph {\n nodes: GraphNode[]\n edges: GraphEdge[]\n}\n\nfunction serializeGraph(graph: NeatGraph): SerializedGraph {\n const nodes: GraphNode[] = []\n graph.forEachNode((_id, attrs) => {\n nodes.push(attrs)\n })\n const edges: GraphEdge[] = []\n graph.forEachEdge((_id, attrs) => {\n edges.push(attrs)\n })\n return { nodes, edges }\n}\n\nfunction projectFromReq(req: FastifyRequest, singleProject?: string): string {\n // `:project` is optional in the URL — the request hits either\n // /projects/:project/X or the unprefixed /X.\n const params = req.params as { project?: string }\n const named = params.project\n // ADR-096 §4 — \"the daemon is the project.\" On a per-project daemon a bare\n // /X needs no project name to disambiguate: it means this daemon's one\n // project. The legacy `default` alias maps to it too, so an agent wired with\n // only NEAT_CORE_URL (no project arg, no NEAT_DEFAULT_PROJECT) reaches the\n // real project without naming it. An explicit real name still routes as\n // given. A non-single-project daemon keeps coercing a missing param to the\n // `default` project.\n if (singleProject) {\n return named === undefined || named === DEFAULT_PROJECT ? singleProject : named\n }\n return named ?? DEFAULT_PROJECT\n}\n\nfunction resolveProject(\n registry: Projects,\n req: FastifyRequest,\n reply: FastifyReply,\n bootstrap?: BuildApiOptions['bootstrap'],\n singleProject?: string,\n): ProjectContext | null {\n const name = projectFromReq(req, singleProject)\n const ctx = registry.get(name)\n if (!ctx) {\n // Issue #340 — registered but still bootstrapping: surface 503 so the\n // probe consumer knows the project is real and incoming, not missing.\n const phase = bootstrap?.status(name)\n if (phase === 'bootstrapping') {\n void reply.code(503).send({ ready: false, project: name, status: 'bootstrapping' })\n return null\n }\n if (phase === 'broken') {\n void reply.code(503).send({ ready: false, project: name, status: 'broken' })\n return null\n }\n // The project isn't in this daemon's hosted set. The shared machine-wide\n // registry may still know it because another daemon serves it — so a bare\n // \"project not found\" reads as \"no such project\" when the truth is \"not\n // here.\" GET /projects marks which projects this core hosts (`hostedHere`);\n // point the caller there rather than let it trust the wrong daemon (#884).\n void reply.code(404).send({\n error: 'project not found',\n project: name,\n hint: 'This daemon does not host that project. GET /projects lists what it serves (see hostedHere).',\n })\n return null\n }\n return ctx\n}\n\nfunction buildLegacyRegistry(opts: BuildApiOptions): Projects {\n if (opts.projects) return opts.projects\n if (!opts.graph) {\n throw new Error('buildApi: either `projects` or `graph` must be provided')\n }\n const registry = new ProjectsClass()\n // pathsForProject only matters here for the snapshot/embeddings paths\n // routes never read; the ingest paths come from explicit options below.\n const paths = pathsForProject(DEFAULT_PROJECT, '')\n registry.set(DEFAULT_PROJECT, {\n graph: opts.graph,\n scanPath: opts.scanPath,\n paths: {\n snapshotPath: paths.snapshotPath,\n errorsPath: opts.errorsPath ?? paths.errorsPath,\n staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,\n embeddingsCachePath: paths.embeddingsCachePath,\n policyViolationsPath: paths.policyViolationsPath,\n },\n searchIndex: opts.searchIndex,\n })\n return registry\n}\n\ninterface RouteContext {\n registry: Projects\n startedAt: number\n // Where the routes are getting mounted. `'root'` is the legacy unprefixed\n // mount that historically resolved every request to the `default` project;\n // `'project'` is the `/projects/:project` plugin scope where the project\n // segment is always present. The /health handler branches on this so the\n // root mount can answer daemon-wide while the scoped mount stays\n // per-project (issue #343).\n scope: 'root' | 'project'\n // Legacy callers passed `errorsPath`/`staleEventsPath` explicitly and\n // expected absent values to disable the read. Track that intent so the\n // /incidents handlers don't accidentally read a phantom file.\n errorsPathFor: (ctx: ProjectContext) => string | undefined\n staleEventsPathFor: (ctx: ProjectContext) => string | undefined\n // policy.json lives at the project root (per ADR-042 §File location), not\n // under neat-out/. Routes that read it map a project context to the path.\n policyFilePathFor: (ctx: ProjectContext) => string | undefined\n // Issue #340 — flips per-project routes from 404 to 503 while a slot is\n // still extracting.\n bootstrap?: BuildApiOptions['bootstrap']\n // ADR-096 §4 — the per-project daemon's one project. When set, unprefixed\n // (and `default`-named) requests resolve to it instead of the `default`\n // project. Absent for the legacy multi-project daemon.\n singleProject?: string\n // ADR-136 — where the connector-status route reads connectors.json from.\n // Undefined uses connectors-config.ts's env-based home resolution.\n connectorsHome?: string\n // #871 — the poll executor the manual trigger runs; defaults to\n // runConnectorPoll, overridable for tests.\n runPoll: typeof runConnectorPoll\n}\n\n// Registers every project-scoped route on `scope`. Called twice from\n// buildApi: once on the root app (so /graph etc. land at default), once\n// inside a `register(_, { prefix: '/projects/:project' })` plugin so the\n// same handlers run when the URL names a project explicitly.\nfunction registerRoutes(scope: FastifyInstance, ctx: RouteContext): void {\n const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx\n\n // SSE event stream (ADR-051 #1). Dual-mounted: hits /events for default\n // project and /projects/:project/events when scoped. The handler keeps\n // the connection open and writes one frame per bus envelope whose\n // `project` matches.\n scope.get<{ Params: { project?: string } }>('/events', (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n handleSse(req, reply, { project: proj.name })\n })\n\n // Per-project /health stays scoped. The unscoped `/health` at the root\n // mount is handled by the daemon-wide handler below (issue #343) —\n // daemon-wide readiness mustn't pivot on whether the `default` project\n // exists. The scoped variant carries the bootstrap-aware shape from\n // issue #340.\n if (ctx.scope === 'project') {\n scope.get<{ Params: { project?: string } }>('/health', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const uptimeMs = Date.now() - startedAt\n // #883 — surface the most recent extraction pass's coverage so a caller\n // can tell the graph is partial. Best-effort: absent sidecar → no field,\n // never an error.\n const coverage = await readExtractionHealth(\n extractionHealthPathFor(proj.paths.errorsPath),\n )\n return {\n ok: true,\n project: proj.name,\n uptimeMs,\n // Legacy fields kept additively. The web shell's StatusBar reads\n // nodeCount / edgeCount; ADR-061's HealthResponseSchema validates\n // the canonical triple and lets the extras pass through.\n uptime: Math.floor(uptimeMs / 1000),\n nodeCount: proj.graph.order,\n edgeCount: proj.graph.size,\n lastUpdated: new Date().toISOString(),\n ...(coverage ? { coverage } : {}),\n }\n })\n }\n\n scope.get<{ Params: { project?: string } }>('/graph', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n return serializeGraph(proj.graph)\n })\n\n scope.get<{ Params: { project?: string; id: string } }>(\n '/graph/node/:id',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { id } = req.params\n if (!proj.graph.hasNode(id)) {\n return reply.code(404).send({ error: 'node not found', id })\n }\n return { node: proj.graph.getNodeAttributes(id) as GraphNode }\n },\n )\n\n scope.get<{ Params: { project?: string; id: string } }>(\n '/graph/edges/:id',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { id } = req.params\n if (!proj.graph.hasNode(id)) {\n return reply.code(404).send({ error: 'node not found', id })\n }\n const inbound = proj.graph\n .inboundEdges(id)\n .map((e) => proj.graph.getEdgeAttributes(e) as GraphEdge)\n const outbound = proj.graph\n .outboundEdges(id)\n .map((e) => proj.graph.getEdgeAttributes(e) as GraphEdge)\n return { inbound, outbound }\n },\n )\n\n // Transitive dependencies (issue #144). BFS outbound to depth N, returning\n // a flat list with distance + edgeType + provenance per dependency.\n // Default depth 3, max 10. The MCP get_dependencies tool calls this.\n scope.get<{\n Params: { project?: string; nodeId: string }\n Querystring: { depth?: string }\n }>('/graph/dependencies/:nodeId', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n const depth = req.query.depth ? Number(req.query.depth) : TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH\n if (!Number.isFinite(depth) || depth < 1 || depth > TRANSITIVE_DEPENDENCIES_MAX_DEPTH) {\n return reply.code(400).send({\n error: `depth must be an integer in [1, ${TRANSITIVE_DEPENDENCIES_MAX_DEPTH}]`,\n })\n }\n return getTransitiveDependencies(proj.graph, nodeId, depth)\n })\n\n // Observed-only dependencies (issue #578). The runtime CALLS a node makes,\n // file-grained — and, for a ServiceNode, the OBSERVED edges of the files it\n // owns, since the call-site processor lands them on files, not the service\n // root. REST parity for the MCP get_observed_dependencies tool (issue #593);\n // the CLI observed-dependencies verb reads it too.\n scope.get<{ Params: { project?: string; nodeId: string } }>(\n '/graph/observed-dependencies/:nodeId',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n return getObservedDependencies(proj.graph, nodeId)\n },\n )\n\n // Divergence query — the thesis surface (ADR-060). Read-only, derived,\n // dual-mounted via registerRoutes. Query params filter the result set;\n // body shape is DivergenceResult.\n scope.get<{\n Params: { project?: string }\n Querystring: { type?: string; minConfidence?: string; node?: string }\n }>('/graph/divergences', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n let typeFilter: Set<DivergenceType> | undefined\n if (req.query.type) {\n const candidates = req.query.type\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n const parsed: DivergenceType[] = []\n for (const c of candidates) {\n const r = DivergenceTypeSchema.safeParse(c)\n if (!r.success) {\n return reply.code(400).send({\n error: `unknown divergence type \"${c}\"`,\n allowed: DivergenceTypeSchema.options,\n })\n }\n parsed.push(r.data)\n }\n typeFilter = new Set(parsed)\n }\n let minConfidence: number | undefined\n if (req.query.minConfidence !== undefined) {\n const n = Number(req.query.minConfidence)\n if (!Number.isFinite(n) || n < 0 || n > 1) {\n return reply.code(400).send({\n error: 'minConfidence must be a number in [0, 1]',\n })\n }\n minConfidence = n\n }\n return computeDivergences(proj.graph, {\n ...(typeFilter ? { type: typeFilter } : {}),\n ...(minConfidence !== undefined ? { minConfidence } : {}),\n ...(req.query.node ? { node: req.query.node } : {}),\n })\n })\n\n // ADR-061 envelope rule: list endpoints return { count, total, events }.\n // `total` is the size of the underlying collection; `count` is the size of\n // the slice we're handing back. The web shell counts on both.\n scope.get<{\n Params: { project?: string }\n Querystring: { limit?: string }\n }>('/incidents', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const epath = errorsPathFor(proj)\n if (!epath) return { count: 0, total: 0, events: [] }\n const events = await readErrorEvents(epath)\n const total = events.length\n const limit = req.query.limit ? Number(req.query.limit) : 50\n const safeLimit =\n Number.isFinite(limit) && limit > 0 ? Math.min(limit, 200) : 50\n const sliced = events.slice(0, safeLimit)\n return { count: sliced.length, total, events: sliced }\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { limit?: string; edgeType?: string }\n }>('/stale-events', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const spath = staleEventsPathFor(proj)\n if (!spath) return { count: 0, total: 0, events: [] }\n const events = await readStaleEvents(spath)\n const filtered = req.query.edgeType\n ? events.filter((e) => e.edgeType === req.query.edgeType)\n : events\n const ordered = [...filtered].reverse()\n const total = ordered.length\n const limit = req.query.limit ? Number(req.query.limit) : 50\n const sliced = ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50)\n return { count: sliced.length, total, events: sliced }\n })\n\n // Logs surface (docs/contracts/logs.md, ADR-132). Reads the bounded\n // per-(project, source) ring buffer in logs-store.ts — the only REST\n // surface allowed to; MCP's get_logs and the CLI's `neat logs` are meant\n // to call this endpoint rather than the store directly. `source` is\n // repeatable (?source=supabase&source=railway); `service`/`since` filter\n // before the `limit` cap, so `total` reflects the filtered-but-unlimited\n // size per the ADR-061 envelope rule.\n scope.get<{\n Params: { project?: string }\n Querystring: { source?: string | string[]; service?: string; limit?: string; since?: string }\n }>('/logs', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const rawSource = req.query.source\n const sources =\n rawSource === undefined ? undefined : Array.isArray(rawSource) ? rawSource : [rawSource]\n const filtered = queryLogEntries({\n projectName: proj.name,\n ...(sources && sources.length > 0 ? { source: sources } : {}),\n ...(req.query.service ? { service: req.query.service } : {}),\n ...(req.query.since ? { since: req.query.since } : {}),\n })\n const total = filtered.length\n const limit = req.query.limit ? Number(req.query.limit) : LOGS_QUERY_DEFAULT_LIMIT\n const safeLimit =\n Number.isFinite(limit) && limit > 0\n ? Math.min(limit, LOGS_QUERY_MAX_LIMIT)\n : LOGS_QUERY_DEFAULT_LIMIT\n const sliced = filtered.slice(0, safeLimit)\n return { count: sliced.length, total, logs: sliced }\n })\n\n // Connector status (docs/contracts/rest-api.md, connectors.md §8, ADR-136).\n // Read-only view backing the web GUI's connector view: the connectors\n // configured for this project in ~/.neat/connectors.json, each with its\n // credential redacted to the env-ref pointer (never a resolved secret) and\n // the live poll health the in-process status tracker recorded. Reads the live\n // config file, never the graph; dual-mounted per ADR-026 like every route.\n scope.get<{ Params: { project?: string } }>('/connectors', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n let entries\n try {\n entries = (await readConnectorsConfig(ctx.connectorsHome)).connectors\n } catch (err) {\n return reply.code(500).send({\n error: 'connectors.json failed to read',\n details: (err as Error).message,\n })\n }\n const connectors = entries\n .filter((entry) => connectorMatchesProject(entry, proj.name))\n .map((entry) => ({\n id: entry.id,\n provider: entry.provider,\n credentialRef: redactCredentialRef(entry.credential),\n status: getConnectorStatus(entry.id),\n }))\n return { connectors }\n })\n\n // #871 — trigger one poll of a connector on demand. An operator verifying a\n // freshly-added connector shouldn't have to wait a full interval (and, before\n // the poll loop wired up under `neat watch`, waited forever with no way to\n // force or diagnose a tick). Reuses the exact buildRegistration + runConnectorPoll\n // the background loop uses, against this project's live graph; the outcome lands\n // in the same status tracker GET /connectors reads, so the two surfaces agree.\n scope.post<{ Params: { project?: string; id: string } }>(\n '/connectors/:id/poll',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { id } = req.params\n let entries\n try {\n entries = (await readConnectorsConfig(ctx.connectorsHome)).connectors\n } catch (err) {\n return reply\n .code(500)\n .send({ error: 'connectors.json failed to read', details: (err as Error).message })\n }\n const entry = entries.find((e) => e.id === id && connectorMatchesProject(e, proj.name))\n if (!entry) {\n return reply.code(404).send({ error: 'connector not found', id, project: proj.name })\n }\n const built = buildRegistration(entry, proj.graph, process.env)\n if (!built.ok) {\n // A push provider (Vercel) has nothing to poll — its data arrives via\n // the OTLP receiver — so that's a 409, distinct from a genuinely broken\n // entry (unknown provider / missing field / unresolved credential) → 400.\n return reply\n .code(built.push ? 409 : 400)\n .send({ error: built.reason, id, push: built.push ?? false })\n }\n const reg = built.registration\n const at = new Date().toISOString()\n try {\n const result = await ctx.runPoll(\n reg.connector,\n { projectDir: proj.scanPath ?? '', credentials: reg.credentials },\n proj.graph,\n reg.resolveTarget,\n )\n recordConnectorPoll(id, { outcome: 'ok', at, signalsLastPoll: result.signalCount })\n return { id, outcome: 'ok', signalsLastPoll: result.signalCount, status: getConnectorStatus(id) }\n } catch (err) {\n const error = sanitizePollError(err)\n recordConnectorPoll(id, { outcome: 'error', at, error })\n return reply.code(502).send({ id, outcome: 'error', error, status: getConnectorStatus(id) })\n }\n },\n )\n\n // One handler, two paths: `/incidents/:nodeId` is the original REST name and\n // `/graph/incident-history/:nodeId` mirrors the MCP get_incident_history tool\n // so the two surfaces answer under matching names (issue #593).\n const incidentHistoryHandler = async (\n req: FastifyRequest<{ Params: { project?: string; nodeId: string } }>,\n reply: FastifyReply,\n ): Promise<{ count: number; total: number; events: ErrorEvent[] } | undefined> => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n reply.code(404).send({ error: 'node not found', id: nodeId })\n return\n }\n const epath = errorsPathFor(proj)\n if (!epath) return { count: 0, total: 0, events: [] }\n const events = await readErrorEvents(epath)\n const filtered = events.filter(\n (e) =>\n e.affectedNode === nodeId || e.service === nodeId.replace(/^service:/, ''),\n )\n return { count: filtered.length, total: filtered.length, events: filtered }\n }\n scope.get<{ Params: { project?: string; nodeId: string } }>(\n '/incidents/:nodeId',\n incidentHistoryHandler,\n )\n scope.get<{ Params: { project?: string; nodeId: string } }>(\n '/graph/incident-history/:nodeId',\n incidentHistoryHandler,\n )\n\n scope.get<{\n Params: { project?: string; nodeId: string }\n Querystring: { errorId?: string }\n }>('/graph/root-cause/:nodeId', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n // Load the incident store so root-cause can localize an in-process failure\n // that never crossed a graph edge (#584). When errorId is supplied the\n // named incident colours the reason; the full set is the fallback the graph\n // walk consults when no edge carried an incompatibility.\n const epath = errorsPathFor(proj)\n const incidents = epath ? await readErrorEvents(epath) : []\n let errorEvent: ErrorEvent | undefined\n if (req.query.errorId) {\n errorEvent = incidents.find((e) => e.id === req.query.errorId)\n if (!errorEvent) {\n return reply\n .code(404)\n .send({ error: 'error event not found', id: req.query.errorId })\n }\n }\n const result = getRootCause(proj.graph, nodeId, errorEvent, incidents)\n if (!result) return reply.code(404).send({ error: 'no root cause found', id: nodeId })\n return result\n })\n\n scope.get<{\n Params: { project?: string; nodeId: string }\n Querystring: { depth?: string }\n }>('/graph/blast-radius/:nodeId', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { nodeId } = req.params\n if (!proj.graph.hasNode(nodeId)) {\n return reply.code(404).send({ error: 'node not found', id: nodeId })\n }\n const depth = req.query.depth ? Number(req.query.depth) : undefined\n if (depth !== undefined && (!Number.isFinite(depth) || depth < 0)) {\n return reply.code(400).send({ error: 'depth must be a non-negative number' })\n }\n return getBlastRadius(proj.graph, nodeId, depth)\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { q?: string; limit?: string }\n }>('/search', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const raw = (req.query.q ?? '').trim()\n if (!raw) return reply.code(400).send({ error: 'query parameter `q` is required' })\n const limit = req.query.limit ? Number(req.query.limit) : undefined\n const safeLimit =\n limit !== undefined && Number.isFinite(limit) && limit > 0 ? limit : undefined\n if (proj.searchIndex) {\n const result = await proj.searchIndex.search(raw, safeLimit)\n return {\n query: result.query,\n provider: result.provider,\n matches: result.matches.map((m) => ({ ...m.node, score: m.score })),\n }\n }\n const q = raw.toLowerCase()\n const matches: (GraphNode & { score: number })[] = []\n proj.graph.forEachNode((id, attrs) => {\n const name = (attrs as { name?: string }).name ?? ''\n if (id.toLowerCase().includes(q) || name.toLowerCase().includes(q)) {\n matches.push({ ...(attrs as GraphNode), score: 1 })\n }\n })\n return {\n query: q,\n provider: 'substring' as const,\n matches: matches.slice(0, safeLimit),\n }\n })\n\n scope.get<{ Params: { project?: string }; Querystring: { against?: string } }>(\n '/graph/diff',\n async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const against = req.query.against\n if (!against) {\n return reply.code(400).send({ error: 'query parameter `against` is required' })\n }\n // Security (#693): `against` used to be handed straight to\n // `loadSnapshotForDiff`, which `fetch()`es any `http(s)` URL and\n // otherwise reads whatever filesystem path it's given — a live\n // SSRF/LFI vector once this route is reachable without auth (public-read\n // mode). `against` now only ever resolves to a snapshot the server\n // already manages: `self` (this project's own on-disk snapshot, written\n // by the persist loop / `neat sync`) or the name of another project\n // this same daemon is tracking. Either way the string is looked up in\n // the registry map, never concatenated into a path or handed to\n // `fetch()` — anything that doesn't match a known project is a 400.\n const targetProjectName = against === 'self' ? proj.name : against\n const targetProject = registry.get(targetProjectName)\n if (!targetProject) {\n return reply.code(400).send({\n error:\n 'unknown snapshot id — `against` must be `self` or the name of a project this server has a snapshot for',\n against,\n })\n }\n try {\n const snapshot = await loadSnapshotForDiff(targetProject.paths.snapshotPath)\n return computeGraphDiff(proj.graph, snapshot)\n } catch (err) {\n return reply\n .code(400)\n .send({ error: 'failed to load snapshot', against, detail: (err as Error).message })\n }\n },\n )\n\n // Snapshot push (ADR-074 §1). `neat sync` POSTs a snapshot here; we merge\n // it into the live graph through `mergeSnapshot`, which preserves any\n // accumulated OBSERVED edges per the Rule 2 coexistence contract. Body\n // shape is the same JSON `persist.ts` writes to disk. Dual-mounted at\n // `/snapshot` and `/projects/:project/snapshot` via registerRoutes.\n scope.post<{\n Params: { project?: string }\n Body: { snapshot?: PersistedGraph }\n }>('/snapshot', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const body = req.body\n if (!body || typeof body !== 'object' || !body.snapshot) {\n return reply\n .code(400)\n .send({ error: 'request body must be { snapshot: <persisted-graph> }' })\n }\n const snap = body.snapshot\n if (typeof snap.schemaVersion !== 'number' || snap.schemaVersion !== SCHEMA_VERSION) {\n return reply.code(400).send({\n error: `unsupported snapshot schemaVersion ${snap.schemaVersion} (expected ${SCHEMA_VERSION})`,\n })\n }\n try {\n const result = mergeSnapshot(proj.graph, snap)\n return {\n project: proj.name,\n nodesAdded: result.nodesAdded,\n edgesAdded: result.edgesAdded,\n nodeCount: proj.graph.order,\n edgeCount: proj.graph.size,\n }\n } catch (err) {\n // A malformed node/edge (wrong `type` literal, missing required field,\n // ...) fails GraphNodeSchema/GraphEdgeSchema validation inside\n // mergeSnapshot and rejects the whole snapshot rather than merging the\n // valid entries and silently dropping the rest. `issues` carries one\n // entry per invalid node/edge so the caller can see exactly what was\n // wrong instead of guessing from a single summary string.\n if (err instanceof SnapshotValidationError) {\n return reply.code(400).send({\n error: 'snapshot merge failed',\n details: err.message,\n issues: err.issues,\n })\n }\n return reply.code(400).send({\n error: 'snapshot merge failed',\n details: (err as Error).message,\n })\n }\n })\n\n scope.post<{ Params: { project?: string } }>('/graph/scan', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply\n .code(409)\n .send({ error: 'scan path not configured for this project', project: proj.name })\n }\n const result = await extractFromDirectory(proj.graph, proj.scanPath)\n return {\n project: proj.name,\n scanned: proj.scanPath,\n nodesAdded: result.nodesAdded,\n edgesAdded: result.edgesAdded,\n nodeCount: proj.graph.order,\n edgeCount: proj.graph.size,\n }\n })\n\n // Policy surface (ADR-045 / contract #18). /policies returns the parsed\n // policy.json; /policies/violations is the persistent log; /policies/check\n // is dry-run evaluation. All dual-mounted via registerRoutes.\n scope.get<{ Params: { project?: string } }>('/policies', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const policyPath = ctx.policyFilePathFor(proj)\n if (!policyPath) {\n // No policy file configured for this project — return the empty file\n // shape so consumers don't have to special-case \"no policies yet.\"\n return { version: 1, policies: [] }\n }\n try {\n const policies = await loadPolicyFile(policyPath)\n return { version: 1, policies }\n } catch (err) {\n return reply.code(400).send({\n error: 'policy.json failed to parse',\n details: (err as Error).message,\n })\n }\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { severity?: string; policyId?: string }\n }>('/policies/violations', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const log = new PolicyViolationsLog(proj.paths.policyViolationsPath)\n let violations = await log.readAll()\n if (req.query.severity) {\n const sev = PolicySeveritySchema.safeParse(req.query.severity)\n if (!sev.success) {\n return reply.code(400).send({\n error: 'invalid severity',\n details: sev.error.format(),\n })\n }\n violations = violations.filter((v) => v.severity === sev.data)\n }\n if (req.query.policyId) {\n violations = violations.filter((v) => v.policyId === req.query.policyId)\n }\n return { violations }\n })\n\n // Soft guardrail read path (ADR-108 / policies-soft-guardrail.md). Returns\n // the policies APPLICABLE to a node — the rules an agent should be aware of\n // while working there. This INFORMS; it never blocks and carries no verdict.\n // Matching is a direct subject/region match (see selectApplicablePolicies),\n // not the full overlay traversal (ADR-105 §5), which is still unbuilt.\n scope.get<{\n Params: { project?: string }\n Querystring: { node?: string }\n }>('/policies/applicable', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const nodeId = req.query.node\n if (!nodeId) {\n return reply.code(400).send({ error: 'missing required query param \"node\"' })\n }\n const policyPath = ctx.policyFilePathFor(proj)\n let policies: Policy[] = []\n if (policyPath) {\n try {\n policies = await loadPolicyFile(policyPath)\n } catch (err) {\n return reply.code(400).send({\n error: 'policy.json failed to parse',\n details: (err as Error).message,\n })\n }\n }\n const applicable = selectApplicablePolicies(proj.graph, policies, nodeId)\n return { node: nodeId, applicable }\n })\n\n scope.post<{\n Params: { project?: string }\n Body: { hypotheticalAction?: unknown }\n }>('/policies/check', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const parsed = PoliciesCheckBodySchema.safeParse(req.body ?? {})\n if (!parsed.success) {\n return reply.code(400).send({\n error: 'invalid /policies/check body',\n details: parsed.error.format(),\n })\n }\n\n const policyPath = ctx.policyFilePathFor(proj)\n let policies: Policy[] = []\n if (policyPath) {\n try {\n policies = await loadPolicyFile(policyPath)\n } catch (err) {\n return reply.code(400).send({\n error: 'policy.json failed to parse',\n details: (err as Error).message,\n })\n }\n }\n\n // No hypothetical → return current violations against the live graph.\n // With a hypothetical → simulate the action against a deep-copy graph\n // (avoids mutation authority concerns), evaluate, return the delta.\n const evalCtx = { now: () => Date.now() }\n if (!parsed.data.hypotheticalAction) {\n const violations = evaluateAllPolicies(proj.graph, policies, evalCtx)\n const blocking = violations.filter((v) => v.onViolation === 'block')\n return { allowed: blocking.length === 0, violations }\n }\n\n // For now the dry-run simulation re-uses evaluateAllPolicies on the\n // current graph. Full hypothetical simulation (e.g. \"what if I added\n // this OBSERVED edge?\") is the v0.2.4-δ scope; #117 ships the surface,\n // #118 fills in the action shapes' simulation logic.\n const violations = evaluateAllPolicies(proj.graph, policies, evalCtx)\n const blocking = violations.filter((v) => v.onViolation === 'block')\n return {\n allowed: blocking.length === 0,\n hypotheticalAction: parsed.data.hypotheticalAction,\n violations,\n } as { allowed: boolean; hypotheticalAction: unknown; violations: PolicyViolation[] }\n })\n\n // ── /extend — surgical instrumentation tools (ADR-081, ADR-086) ─────────\n // Six routes. Three read-only (list-uninstrumented, lookup, describe),\n // three operative (apply, dry-run, rollback). File-scope restricted per\n // the extend-skill contract: only instrumentation/otel-init files and\n // package.json. Dual-mounted via registerRoutes.\n\n scope.get<{ Params: { project?: string } }>('/extend/list-uninstrumented', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply.code(409).send({ error: 'scan path not configured for this project', project: proj.name })\n }\n try {\n const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath })\n return { libraries: results }\n } catch (err) {\n return reply.code(500).send({ error: (err as Error).message })\n }\n })\n\n scope.get<{\n Params: { project?: string }\n Querystring: { library?: string; version?: string }\n }>('/extend/lookup', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n const { library, version } = req.query\n if (!library) {\n return reply.code(400).send({ error: 'query parameter `library` is required' })\n }\n const result = lookupInstrumentation(library, version)\n if (!result) {\n return reply.code(404).send({ error: 'library not found in registry', library })\n }\n return result\n })\n\n scope.get<{ Params: { project?: string } }>('/extend/describe', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply.code(409).send({ error: 'scan path not configured for this project', project: proj.name })\n }\n try {\n const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath })\n return state\n } catch (err) {\n return reply.code(500).send({ error: (err as Error).message })\n }\n })\n\n // GET /instrumentation — the web ObservedOverlay's honesty probe (#823).\n //\n // The overlay (packages/web/app/components/ObservedOverlay.tsx), through its\n // proxy (packages/web/app/api/instrumentation/route.ts), reads the daemon\n // ROOT `/instrumentation` to pick Mode A (instrumentation wired, idle — run\n // your app) vs Mode B (didn't engage — here's the one fix) and to pull the\n // specific Mode B diagnosis. This fuses the static hook state\n // (describeProjectInstrumentation: hook files + installed OTel deps) with the\n // registry-driven coverage gaps (listUninstrumented) and answers the overlay's\n // contract shape: `{ engaged, diagnosis? }`.\n //\n // Honesty (web-completeness #26): never a fabricated cause. Wired with nothing\n // diagnosably missing → `engaged: true` (Mode A). Can't tell — no scan path or\n // an unreadable package.json → `engaged: null` (neutral; the overlay stays\n // Mode A rather than wrongly accusing a healthy idle setup). Only a real, named\n // gap yields `engaged: false` with a diagnosis. Dual-mounted via registerRoutes\n // like every route (ADR-026), so the web proxy's root-path hit resolves.\n scope.get<{ Params: { project?: string } }>('/instrumentation', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n // No scan path configured — we genuinely can't diagnose. Neutral.\n return { engaged: null }\n }\n try {\n const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath })\n const uninstrumented = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath })\n\n // Not wired at all: NEAT never wrote an OTel init hook for this project.\n if (state.hookFiles.length === 0) {\n return {\n engaged: false,\n diagnosis: {\n reason:\n \"No instrumented entry point found — NEAT hasn't written an OTel init hook for this project.\",\n fixCommand: 'neat init',\n detail:\n 'Run `neat init` so NEAT writes the instrumentation hook next to your entry point, then run your app.',\n },\n }\n }\n\n // Wired, but a library on the hot path isn't in the auto-instrumentation\n // set — its spans won't reach the graph until it's extended.\n if (uninstrumented.length > 0) {\n const names = uninstrumented.map((u) => u.library)\n const more = names.length > 1 ? ` (and ${names.length - 1} more)` : ''\n return {\n engaged: false,\n diagnosis: {\n reason: `\\`${names[0]}\\`${more} isn't in the auto-instrumentation set, so spans from it won't reach the graph.`,\n fixCommand: 'neat extend',\n detail: `Uninstrumented on your hot path: ${names.join(', ')}. Run \\`neat extend\\` to wire the missing instrumentation.`,\n },\n }\n }\n\n // Hook files present and no known coverage gaps: the static picture is\n // healthy. Whether traffic has actually flowed is an OBSERVED question the\n // graph answers — from here the honest read is \"wired, waiting to engage.\"\n return { engaged: true }\n } catch {\n // Unreadable package.json / other read error — we can't tell. Neutral,\n // never a fabricated cause.\n return { engaged: null }\n }\n })\n\n scope.post<{\n Params: { project?: string }\n Body: { library?: string; instrumentation_package?: string; version?: string; registration_snippet?: string }\n }>('/extend/apply', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply.code(409).send({ error: 'scan path not configured for this project', project: proj.name })\n }\n const { library, instrumentation_package, version, registration_snippet } = req.body ?? {}\n if (!library || !instrumentation_package || !version || !registration_snippet) {\n return reply.code(400).send({ error: 'body must include library, instrumentation_package, version, registration_snippet' })\n }\n try {\n const result = await applyExtension(\n { project: proj.name, scanPath: proj.scanPath },\n { library, instrumentation_package, version, registration_snippet },\n )\n return result\n } catch (err) {\n return reply.code(500).send({ error: (err as Error).message })\n }\n })\n\n scope.post<{\n Params: { project?: string }\n Body: { library?: string; instrumentation_package?: string; version?: string; registration_snippet?: string }\n }>('/extend/dry-run', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply.code(409).send({ error: 'scan path not configured for this project', project: proj.name })\n }\n const { library, instrumentation_package, version, registration_snippet } = req.body ?? {}\n if (!library || !instrumentation_package || !version || !registration_snippet) {\n return reply.code(400).send({ error: 'body must include library, instrumentation_package, version, registration_snippet' })\n }\n try {\n const result = await dryRunExtension(\n { project: proj.name, scanPath: proj.scanPath },\n { library, instrumentation_package, version, registration_snippet },\n )\n return result\n } catch (err) {\n return reply.code(500).send({ error: (err as Error).message })\n }\n })\n\n scope.post<{\n Params: { project?: string }\n Body: { library?: string }\n }>('/extend/rollback', async (req, reply) => {\n const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject)\n if (!proj) return\n if (!proj.scanPath) {\n return reply.code(409).send({ error: 'scan path not configured for this project', project: proj.name })\n }\n const { library } = req.body ?? {}\n if (!library) {\n return reply.code(400).send({ error: 'body must include library' })\n }\n try {\n const result = await rollbackExtension(\n { project: proj.name, scanPath: proj.scanPath },\n { library },\n )\n return result\n } catch (err) {\n return reply.code(500).send({ error: (err as Error).message })\n }\n })\n}\n\nexport async function buildApi(opts: BuildApiOptions): Promise<FastifyInstance> {\n // Node ids are how the graph is addressed over REST, and under file-awareness\n // (ADR-087) an id is `code:<filepath>:<symbol>` — which the CLI and MCP\n // clients URL-encode (every `/` becomes `%2F`) before it lands in a `:id` /\n // `:nodeId` path param. Fastify's default maxParamLength of 100 then rejects\n // any realistic file-grained id at the router, before the handler runs, with\n // a generic \"Route not found\" 404. To an agent querying blast-radius /\n // dependencies / root-cause for a real node, that reads as a broken endpoint\n // rather than \"that node isn't in the graph\" — the handler's actionable\n // `{ error: 'node not found', id }` never gets a chance to answer. Raise the\n // cap so a long-but-real id reaches the handler; 1024 clears any realistic\n // path + symbol while still bounding a pathological multi-kilobyte URL.\n const app = Fastify({ logger: false, routerOptions: { maxParamLength: 1024 } })\n await app.register(cors, { origin: true })\n\n // ADR-073 §3/§4 — `buildApi` owns auth enforcement so every listener that\n // serves the REST surface gets the same gate, whoever builds it. When a\n // caller doesn't pass auth options explicitly, they resolve from the\n // environment (`NEAT_AUTH_TOKEN` / `NEAT_AUTH_PROXY` / `NEAT_PUBLIC_READ`)\n // through the single `readAuthEnv` reader. A caller that does pass them\n // wins, so the daemon and `serve` keep their explicit wiring; a caller that\n // omits them — `neat watch` — inherits the same token rather than serving\n // open by omission. The bind-host loopback refusal stays with the binding\n // caller via `assertBindAuthority`, which reads the same env.\n const env = readAuthEnv()\n const authToken = opts.authToken ?? env.authToken\n const trustProxy = opts.trustProxy ?? env.trustProxy\n const publicRead = opts.publicRead ?? env.publicRead\n\n // ADR-073 §3 — bearer middleware sits ahead of every route handler. No-op\n // when the resolved token is undefined; loopback-only callers (the laptop\n // dev path) hit that branch. `publicRead` opens GET / HEAD / OPTIONS to\n // anonymous callers while keeping writes gated.\n mountBearerAuth(app, {\n token: authToken,\n trustProxy,\n publicRead,\n })\n\n // ADR-073 §3a / ADR-139 — `/api/config` is always unauthenticated. The web\n // shell hits it before any bearer-carrying request to learn which mode the\n // daemon is in. Exposes exactly three booleans — `publicRead`, `authProxy`,\n // and `requiresAuth` — and nothing else; no project list, no version, no env.\n //\n // `requiresAuth` is true iff this daemon actually mounts a bearer hook, which\n // is exactly `mountBearerAuth`'s own condition: a token is set and we're not\n // trusting an upstream proxy. A tokenless daemon (the loopback dev path) and\n // a proxy-terminated one both serve every request anonymously — there is no\n // bearer to log in with — so the web gate must not bounce them to /login.\n // This is distinct from `publicRead`: a public-read reference deployment\n // still requires auth for writes and renders read-only, while a tokenless\n // local daemon requires no auth at all and stays fully writable.\n const requiresAuth = authToken !== undefined && authToken.length > 0 && trustProxy !== true\n app.get('/api/config', async () => ({\n publicRead: publicRead === true,\n authProxy: trustProxy === true,\n requiresAuth,\n }))\n\n const startedAt = opts.startedAt ?? Date.now()\n const registry = buildLegacyRegistry(opts)\n\n const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== undefined\n const legacyStaleExplicit = !opts.projects && opts.staleEventsPath !== undefined\n\n const errorsPathFor = (proj: ProjectContext): string | undefined => {\n if (proj.name === DEFAULT_PROJECT && !opts.projects) {\n return legacyErrorsExplicit ? opts.errorsPath : undefined\n }\n return proj.paths.errorsPath\n }\n const staleEventsPathFor = (proj: ProjectContext): string | undefined => {\n if (proj.name === DEFAULT_PROJECT && !opts.projects) {\n return legacyStaleExplicit ? opts.staleEventsPath : undefined\n }\n return proj.paths.staleEventsPath\n }\n\n // policy.json lives at the project's scanPath root per ADR-042. Without a\n // scanPath we have nowhere to read it from — those projects show as\n // \"no policies configured\" via the empty-file response.\n const policyFilePathFor = (proj: ProjectContext): string | undefined => {\n if (!proj.scanPath) return undefined\n return `${proj.scanPath}/policy.json`\n }\n\n const routeCtx: RouteContext = {\n registry,\n startedAt,\n scope: 'root',\n errorsPathFor,\n staleEventsPathFor,\n policyFilePathFor,\n bootstrap: opts.bootstrap,\n singleProject: opts.singleProject?.name,\n connectorsHome: opts.connectorsHome,\n runPoll: opts.runPoll ?? runConnectorPoll,\n }\n\n // Daemon-wide /health (issue #343). Per ADR-049 the daemon is the unit of\n // readiness; the response answers \"is this process up and what's it\n // currently serving?\" without depending on a `default` project. Probes —\n // orchestrator, kubelet readiness, systemd, supervisors — read this.\n //\n // The `projects` array surfaces every slot the daemon knows about. When\n // the bootstrap tracker is wired (issue #340), the per-entry `status`\n // and `elapsedMs` come from there so the orchestrator's wait loop can\n // tell `bootstrapping` apart from `active` without per-project probes.\n app.get('/health', async () => {\n const uptimeMs = Date.now() - startedAt\n const bootstrapList = opts.bootstrap?.list() ?? []\n const byName = new Map(bootstrapList.map((p) => [p.name, p]))\n const names = new Set<string>([\n ...registry.list(),\n ...bootstrapList.map((p) => p.name),\n ])\n const projects = [...names].sort().map((name) => {\n const proj = registry.get(name)\n const tracked = byName.get(name)\n return {\n name,\n nodeCount: proj?.graph.order ?? 0,\n edgeCount: proj?.graph.size ?? 0,\n ...(tracked ? { status: tracked.status, elapsedMs: tracked.elapsedMs } : {}),\n }\n })\n return {\n ok: true,\n uptimeMs,\n // ADR-096 §7 — a per-project daemon carries its project at the top level\n // so a spawn can confirm a daemon found on a reused port is serving THIS\n // project before reusing it. The legacy multi-project daemon omits it and\n // is matched against the `projects` array instead.\n ...(opts.singleProject ? { project: opts.singleProject.name } : {}),\n projects,\n }\n })\n\n // ADR-096 §4 — \"the daemon is the project.\" A per-project daemon reports only\n // its own project here, so the dashboard pins to the project this daemon\n // serves rather than to whichever machine-registered project happens to be\n // active. The legacy multi-project daemon hands back the machine-level\n // registry (ADR-048) — the one documented bare-array GET. Either way the\n // response is `Array<ProjectListEntry>` — a RegistryEntry plus `hostedHere`\n // (#884) — which the dashboard and the CLI's bare-verb resolver treat as a\n // list primitive.\n app.get('/projects', async (_req, reply) => {\n if (opts.singleProject) {\n const phase = opts.bootstrap?.status(opts.singleProject.name)\n const entry: ProjectListEntry = {\n name: opts.singleProject.name,\n path: opts.singleProject.path,\n registeredAt: new Date(startedAt).toISOString(),\n languages: [],\n // The daemon is serving this project, so it's active unless its\n // bootstrap broke. ('bootstrapping' still reads as active — the project\n // is the one to land on; its graph route answers 503 until ready.)\n status: phase === 'broken' ? 'broken' : 'active',\n hostedHere: true,\n }\n return [entry]\n }\n try {\n // The registry is shared machine-wide, so it lists projects this daemon\n // doesn't serve (another daemon owns them). Mark which ones this core\n // actually hosts so a caller doesn't trust per-project answers from the\n // wrong daemon (#884). `registry` is this daemon's in-memory hosted set.\n const entries = await listRegistryProjects()\n return entries.map<ProjectListEntry>((entry) => ({\n ...entry,\n hostedHere: registry.has(entry.name),\n }))\n } catch (err) {\n return reply.code(500).send({\n error: 'failed to read project registry',\n details: (err as Error).message,\n })\n }\n })\n\n // Singular project lookup (ADR-061 #7). Distinct route from the\n // `/projects/:project/...` dual-mount prefix because Fastify matches on\n // the full path; the trailing-segment-less request lands here.\n app.get<{ Params: { project: string } }>('/projects/:project', async (req, reply) => {\n try {\n const entry = await getRegistryProject(req.params.project)\n if (!entry) {\n return reply\n .code(404)\n .send({ error: 'project not found', project: req.params.project })\n }\n // The registry knows the project, but this daemon may not host it. Say so,\n // and when another daemon owns it, name where it lives so the caller can\n // repoint rather than read a 200 as \"this core serves it\" (#884).\n const hostedHere = registry.has(entry.name)\n if (!hostedHere) {\n const owner = await findDaemonByProject(entry.name).catch(() => undefined)\n if (owner) {\n return {\n project: { ...entry, hostedHere },\n servedBy: {\n path: owner.record.projectPath,\n restPort: owner.record.ports.rest,\n live: owner.live,\n },\n }\n }\n }\n return { project: { ...entry, hostedHere } }\n } catch (err) {\n return reply.code(500).send({\n error: 'failed to read project registry',\n details: (err as Error).message,\n })\n }\n })\n\n // Default mount: /graph, /incidents, etc. resolve to project=default.\n // `/health` at this scope is the daemon-wide handler registered above,\n // not the per-project one (issue #343).\n registerRoutes(app, routeCtx)\n\n // Project-scoped mount: same handlers, URL params include `:project`,\n // and `/health` answers per project.\n await app.register(\n async (scope) => {\n registerRoutes(scope, { ...routeCtx, scope: 'project' })\n },\n { prefix: '/projects/:project' },\n )\n\n return app\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport os from 'node:os'\nimport { resolve as registryResolve, list as registryList } from '@neat.is/instrumentation-registry'\nimport {\n detectPackageManager,\n runPackageManagerInstall,\n} from '../installers/package-manager.js'\n\nexport interface ExtendContext {\n project: string\n scanPath: string\n}\n\nexport interface LibraryCoverageResult {\n library: string\n coverage: 'bundled' | 'first-party' | 'third-party' | 'http-only' | 'gap'\n installedVersion?: string\n instrumentation_package?: string\n package_version?: string\n registration?: string\n notes?: string\n}\n\nexport interface ProjectInstrumentationState {\n hookFiles: string[]\n envNeat: boolean\n installedDeps: Record<string, string>\n}\n\nexport interface ExtensionApplyResult {\n library: string\n filesTouched: string[]\n depsAdded: string[]\n installOutput: string\n alreadyApplied: boolean\n}\n\nexport interface ExtensionDiff {\n library: string\n filesTouched: string[]\n depsToAdd: string[]\n packageJsonPatch: object\n templatePatch: string\n}\n\ninterface PackageJson {\n name?: string\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n [key: string]: unknown\n}\n\ninterface ExtendLogEntry {\n timestamp: string\n project: string\n library: string\n instrumentation_package: string\n version: string\n registration_snippet: string\n filesTouched: string[]\n depsAdded: string[]\n installOutput: string\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await fs.access(p)\n return true\n } catch {\n return false\n }\n}\n\nasync function readPackageJson(scanPath: string): Promise<PackageJson> {\n const pkgPath = path.join(scanPath, 'package.json')\n const raw = await fs.readFile(pkgPath, 'utf8')\n return JSON.parse(raw) as PackageJson\n}\n\n// Directories the hook-file walk never descends into: dependency trees, VCS\n// metadata, build output, and NEAT's own output dir. None can hold a user's\n// instrumentation hook, and node_modules especially would make the walk crawl.\n// Every dot-prefixed directory (`.git`, `.next`, `.turbo`, `.vercel`, …) is\n// skipped too — a generated hook never lands in one.\nconst HOOK_WALK_SKIP_DIRS = new Set([\n 'node_modules',\n 'dist',\n 'build',\n 'out',\n 'coverage',\n 'neat-out',\n])\n\n// Locate every OTel init hook in the project: a file whose basename starts with\n// `instrumentation` or `otel-init` in any JS/TS module flavor (js/ts/cjs/mjs).\n// The orchestrator writes the hook adjacent to the resolved entry — commonly a\n// subdirectory like `src/otel-init.cjs` for an app whose entry is\n// `src/index.js` — and the Next branch writes `instrumentation.node.{ts,js}`\n// under `src/` for --src-dir layouts, so the search walks the whole tree rather\n// than reading the root alone (#823). Paths come back relative to scanPath;\n// every caller re-joins them with `path.join(scanPath, file)`.\nasync function findHookFiles(scanPath: string): Promise<string[]> {\n const found: string[] = []\n const walk = async (dir: string): Promise<void> => {\n const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => [])\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (entry.name.startsWith('.') || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue\n await walk(path.join(dir, entry.name))\n } else if (entry.isFile()) {\n if (\n (entry.name.startsWith('instrumentation') || entry.name.startsWith('otel-init')) &&\n /\\.(ts|js|cjs|mjs)$/.test(entry.name)\n ) {\n const rel = path.relative(scanPath, path.join(dir, entry.name))\n // Normalise to forward slashes so the returned path is stable across\n // platforms and re-joins cleanly (path.join accepts `/` everywhere).\n found.push(rel.split(path.sep).join('/'))\n }\n }\n }\n }\n await walk(scanPath)\n return found.sort()\n}\n\n// Choose which hook file the registration snippet splices into. A project can\n// carry several (Next writes instrumentation.{,node,edge}.{ts,js}; a monorepo\n// may keep one per package), but only the file that constructs the SDK has an\n// insertion point — the edge file registers via `@vercel/otel` and sorts first\n// alphabetically, so `hookFiles[0]` would be the wrong target. Prefer the first\n// hook file that splices cleanly and hand back its patched contents; fall back\n// to the first hook file so the caller still emits a precise \"no insertion\n// point\" error naming a real file.\nasync function pickPrimaryHookFile(\n scanPath: string,\n hookFiles: string[],\n snippet: string,\n): Promise<{ file: string; content: string; patched: string | null }> {\n let fallback: { file: string; content: string } | null = null\n for (const file of hookFiles) {\n const content = await fs.readFile(path.join(scanPath, file), 'utf8')\n const patched = splicedContent(content, snippet)\n if (patched !== null) return { file, content, patched }\n if (fallback === null) fallback = { file, content }\n }\n return { file: fallback!.file, content: fallback!.content, patched: null }\n}\n\nfunction extendLogPath(): string {\n return process.env.NEAT_EXTEND_LOG ?? path.join(os.homedir(), '.neat', 'extend-log.ndjson')\n}\n\nasync function appendExtendLog(entry: ExtendLogEntry): Promise<void> {\n const logPath = extendLogPath()\n await fs.mkdir(path.dirname(logPath), { recursive: true })\n await fs.appendFile(logPath, JSON.stringify(entry) + '\\n', 'utf8')\n}\n\n// Insert `snippet` into `fileContent` at the instrumentation array.\n// Tries __INSTRUMENTATION_BLOCK__ first, then last instrumentations.push(,\n// then before new NodeSDK(. Returns null if no insertion point is found.\nfunction splicedContent(fileContent: string, snippet: string): string | null {\n if (fileContent.includes('__INSTRUMENTATION_BLOCK__')) {\n return fileContent.replace('__INSTRUMENTATION_BLOCK__', `${snippet}\\n__INSTRUMENTATION_BLOCK__`)\n }\n\n const lines = fileContent.split('\\n')\n\n let lastPushIdx = -1\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].includes('instrumentations.push(')) lastPushIdx = i\n }\n if (lastPushIdx >= 0) {\n lines.splice(lastPushIdx + 1, 0, snippet)\n return lines.join('\\n')\n }\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].includes('new NodeSDK(')) {\n lines.splice(i, 0, snippet)\n return lines.join('\\n')\n }\n }\n\n return null\n}\n\nexport async function listUninstrumented(ctx: ExtendContext): Promise<LibraryCoverageResult[]> {\n const pkg = await readPackageJson(ctx.scanPath)\n const allDeps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }\n\n const results: LibraryCoverageResult[] = []\n for (const [library, installedVersion] of Object.entries(allDeps)) {\n const entry = registryResolve(library, installedVersion)\n if (!entry) continue\n if (entry.coverage === 'bundled' || entry.coverage === 'http-only') continue\n results.push({\n library,\n coverage: entry.coverage,\n installedVersion,\n instrumentation_package: entry.instrumentation_package,\n package_version: entry.package_version,\n registration: entry.registration,\n notes: entry.notes,\n })\n }\n return results\n}\n\nexport function lookupInstrumentation(\n library: string,\n installedVersion?: string,\n): LibraryCoverageResult | null {\n const entry = registryResolve(library, installedVersion)\n if (!entry) return null\n return {\n library: entry.library,\n coverage: entry.coverage,\n instrumentation_package: entry.instrumentation_package,\n package_version: entry.package_version,\n registration: entry.registration,\n notes: entry.notes,\n }\n}\n\nexport async function describeProjectInstrumentation(\n ctx: ExtendContext,\n): Promise<ProjectInstrumentationState> {\n const hookFiles = await findHookFiles(ctx.scanPath)\n const envNeat = await fileExists(path.join(ctx.scanPath, '.env.neat'))\n\n const registryInstrPackages = new Set<string>(\n registryList()\n .map((e) => e.instrumentation_package)\n .filter((p): p is string => !!p),\n )\n\n const pkg = await readPackageJson(ctx.scanPath)\n const allDeps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }\n\n const installedDeps: Record<string, string> = {}\n for (const [key, version] of Object.entries(allDeps)) {\n if (key.startsWith('@opentelemetry/') || registryInstrPackages.has(key)) {\n installedDeps[key] = version\n }\n }\n\n return { hookFiles, envNeat, installedDeps }\n}\n\nexport async function applyExtension(\n ctx: ExtendContext,\n args: {\n library: string\n instrumentation_package: string\n version: string\n registration_snippet: string\n },\n options?: {\n runInstall?: typeof runPackageManagerInstall\n },\n): Promise<ExtensionApplyResult> {\n const hookFiles = await findHookFiles(ctx.scanPath)\n\n if (hookFiles.length === 0) {\n throw new Error(\n `No instrumentation hook files found in ${ctx.scanPath}. Run \\`neat init\\` first.`,\n )\n }\n\n // Idempotency check: scan all hook files for the snippet\n for (const file of hookFiles) {\n const content = await fs.readFile(path.join(ctx.scanPath, file), 'utf8')\n if (content.includes(args.registration_snippet)) {\n return { library: args.library, filesTouched: [], depsAdded: [], installOutput: '', alreadyApplied: true }\n }\n }\n\n // Resolve the hook file to splice into, and confirm it has an insertion point\n // BEFORE touching package.json — otherwise a snippet that can't splice would\n // leave a dep added with no matching registration.\n const primary = await pickPrimaryHookFile(ctx.scanPath, hookFiles, args.registration_snippet)\n if (primary.patched === null) {\n throw new Error(\n `Could not find instrumentation insertion point in ${hookFiles.join(', ')}. ` +\n 'Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.',\n )\n }\n const primaryFile = primary.file\n const primaryPath = path.join(ctx.scanPath, primaryFile)\n const filesTouched: string[] = []\n const depsAdded: string[] = []\n\n // 1. Add dep to package.json if not already present\n const pkgPath = path.join(ctx.scanPath, 'package.json')\n const pkg = await readPackageJson(ctx.scanPath)\n if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {\n pkg.dependencies = { ...(pkg.dependencies ?? {}), [args.instrumentation_package]: args.version }\n await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\\n', 'utf8')\n filesTouched.push('package.json')\n depsAdded.push(`${args.instrumentation_package}@${args.version}`)\n }\n\n // 2. Splice registration snippet into the resolved hook file\n await fs.writeFile(primaryPath, primary.patched, 'utf8')\n filesTouched.push(primaryFile)\n\n // 3. Run package manager install\n const cmd = await detectPackageManager(ctx.scanPath)\n const installer = options?.runInstall ?? runPackageManagerInstall\n const install = await installer(cmd)\n const installOutput =\n install.exitCode === 0\n ? `${cmd.pm} install succeeded`\n : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`\n\n // 4. Log the apply\n await appendExtendLog({\n timestamp: new Date().toISOString(),\n project: ctx.project,\n library: args.library,\n instrumentation_package: args.instrumentation_package,\n version: args.version,\n registration_snippet: args.registration_snippet,\n filesTouched,\n depsAdded,\n installOutput,\n })\n\n return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false }\n}\n\nexport async function dryRunExtension(\n ctx: ExtendContext,\n args: {\n library: string\n instrumentation_package: string\n version: string\n registration_snippet: string\n },\n): Promise<ExtensionDiff> {\n const hookFiles = await findHookFiles(ctx.scanPath)\n\n if (hookFiles.length === 0) {\n return {\n library: args.library,\n filesTouched: [],\n depsToAdd: [],\n packageJsonPatch: {},\n templatePatch: \"No hook files found. Run 'neat init' first.\",\n }\n }\n\n for (const file of hookFiles) {\n const content = await fs.readFile(path.join(ctx.scanPath, file), 'utf8')\n if (content.includes(args.registration_snippet)) {\n return {\n library: args.library,\n filesTouched: [],\n depsToAdd: [],\n packageJsonPatch: {},\n templatePatch: 'Already applied — no changes would be made.',\n }\n }\n }\n\n const primary = await pickPrimaryHookFile(ctx.scanPath, hookFiles, args.registration_snippet)\n const filesTouched: string[] = []\n const depsToAdd: string[] = []\n let packageJsonPatch: object = {}\n let templatePatch = ''\n\n const pkg = await readPackageJson(ctx.scanPath)\n if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {\n packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } }\n depsToAdd.push(`${args.instrumentation_package}@${args.version}`)\n filesTouched.push('package.json')\n }\n\n if (primary.patched !== null) {\n filesTouched.push(primary.file)\n templatePatch = `+ ${args.registration_snippet}`\n } else {\n templatePatch = 'Could not find insertion point in hook file.'\n }\n\n return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch }\n}\n\nexport async function rollbackExtension(\n ctx: ExtendContext,\n args: { library: string },\n): Promise<{ undone: boolean; message: string }> {\n const logPath = extendLogPath()\n\n if (!(await fileExists(logPath))) {\n return { undone: false, message: 'no apply found for library' }\n }\n\n const raw = await fs.readFile(logPath, 'utf8')\n const entries: ExtendLogEntry[] = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => JSON.parse(line) as ExtendLogEntry)\n\n const match = [...entries]\n .reverse()\n .find((e) => e.project === ctx.project && e.library === args.library)\n\n if (!match) {\n return { undone: false, message: 'no apply found for library' }\n }\n\n // Remove dep from package.json\n const pkgPath = path.join(ctx.scanPath, 'package.json')\n if (await fileExists(pkgPath)) {\n const pkg = await readPackageJson(ctx.scanPath)\n if (pkg.dependencies?.[match.instrumentation_package]) {\n const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies\n pkg.dependencies = rest\n await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\\n', 'utf8')\n }\n }\n\n // Remove the registration snippet from hook files (no re-install — user owns the lockfile)\n const hookFiles = await findHookFiles(ctx.scanPath)\n for (const file of hookFiles) {\n const filePath = path.join(ctx.scanPath, file)\n const content = await fs.readFile(filePath, 'utf8')\n if (content.includes(match.registration_snippet)) {\n const filtered = content\n .split('\\n')\n .filter((line) => !line.includes(match.registration_snippet))\n .join('\\n')\n await fs.writeFile(filePath, filtered, 'utf8')\n break\n }\n }\n\n return {\n undone: true,\n message: `rolled back ${match.library} (${match.instrumentation_package})`,\n }\n}\n","/**\n * Package-manager detection + install invocation.\n *\n * Issue #381 — the apply phase adds dependencies to package.json but\n * relies on the operator to run `npm install` afterwards. The v0.4.5\n * smoke surfaced this as a hard regression on Brief: the installer\n * adds `@opentelemetry/sdk-node` (and `@prisma/instrumentation` when\n * Prisma is in deps) and the next `npm run dev` fails with\n * `Cannot find module '@opentelemetry/sdk-node'` before the OTel SDK\n * even gets a chance to load.\n *\n * ADR-046's \"lockfiles never touched\" rule is about NEAT not directly\n * editing lockfile contents; letting the user's own package manager\n * update them as a side effect of `<pm> install` is consistent with\n * that contract. The clarification is documented in\n * docs/contracts/sdk-install.md.\n */\n\nimport { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport { spawn } from 'node:child_process'\n\nexport type PackageManager = 'bun' | 'pnpm' | 'yarn' | 'npm'\n\nexport interface PackageManagerCommand {\n pm: PackageManager\n // The directory the install command runs in. Monorepo workspaces install\n // from the lockfile root, not the per-package directory, so detection\n // walks up from `serviceDir` and reports the lockfile-owning parent.\n cwd: string\n // The argv passed to spawn(). Each entry is one positional token. Selected\n // to keep install output quiet on the happy path; failures still print\n // because stderr is streamed verbatim.\n args: string[]\n}\n\n// Lockfile basename → package manager + the install args we run for it.\n// Priority order — first match wins when multiple lockfiles coexist (rare,\n// but `package-lock.json` alongside `pnpm-lock.yaml` happens during a\n// migration). Bun leads because a project that opted into Bun deliberately\n// rejects npm; pnpm and yarn follow for similar reasons; npm is the default\n// when nothing else applies.\nconst LOCKFILE_PRIORITY: ReadonlyArray<{\n lockfile: string\n pm: PackageManager\n args: string[]\n}> = [\n { lockfile: 'bun.lockb', pm: 'bun', args: ['install', '--no-summary'] },\n { lockfile: 'pnpm-lock.yaml', pm: 'pnpm', args: ['install', '--no-summary'] },\n { lockfile: 'yarn.lock', pm: 'yarn', args: ['install', '--silent'] },\n {\n lockfile: 'package-lock.json',\n pm: 'npm',\n args: ['install', '--no-audit', '--no-fund', '--prefer-offline'],\n },\n]\n\n// Default when no lockfile is present anywhere in the ancestor chain — a\n// fresh project the operator hasn't installed yet. npm is the safe pick:\n// it's available on every Node install and produces a `package-lock.json`\n// the user can later swap out for pnpm/yarn/bun without losing fidelity.\nconst NPM_FALLBACK_ARGS = ['install', '--no-audit', '--no-fund', '--prefer-offline']\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p)\n return true\n } catch {\n return false\n }\n}\n\n// Resolve the package-manager command for a service directory. Walks up from\n// `serviceDir` looking for a lockfile — the first ancestor that has one\n// owns the install. Monorepos (npm/pnpm/yarn workspaces, Bun workspaces)\n// land their lockfile at the workspace root, so this returns the root cwd\n// even when `serviceDir` is a per-package subdir.\n//\n// No lockfile anywhere → npm at the service dir. A fresh `create-next-app`\n// run sits in this bucket (no install yet, no lockfile to read).\nexport async function detectPackageManager(\n serviceDir: string,\n): Promise<PackageManagerCommand> {\n let dir = path.resolve(serviceDir)\n const stops = new Set<string>()\n for (let i = 0; i < 64; i++) {\n if (stops.has(dir)) break\n stops.add(dir)\n for (const candidate of LOCKFILE_PRIORITY) {\n const lockPath = path.join(dir, candidate.lockfile)\n if (await exists(lockPath)) {\n return { pm: candidate.pm, cwd: dir, args: [...candidate.args] }\n }\n }\n const parent = path.dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n return { pm: 'npm', cwd: path.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] }\n}\n\nexport interface PackageManagerInvocation {\n pm: PackageManager\n cwd: string\n args: string[]\n // 0 → success; non-zero → install failed. Reported in the orchestrator\n // summary so the operator can act before the daemon goes hunting for\n // spans that never arrive.\n exitCode: number\n // Stderr captured from the child process, trimmed. Empty on success.\n // Surfaces the underlying failure cause without dumping the entire\n // install log into the summary.\n stderr: string\n}\n\n// Run the install command and resolve with the outcome. Stdout is dropped\n// (`<pm> install --silent`-style flags already keep it short); stderr is\n// captured so a failure can be relayed up to the orchestrator's summary.\nexport async function runPackageManagerInstall(\n cmd: PackageManagerCommand,\n): Promise<PackageManagerInvocation> {\n return new Promise((resolve) => {\n const child = spawn(cmd.pm, cmd.args, {\n cwd: cmd.cwd,\n // Inherit PATH + HOME so the user's installed managers resolve.\n env: process.env,\n // `false` keeps the parent in control of cleanup if the orchestrator\n // exits before install finishes. Cross-platform-safe.\n shell: false,\n stdio: ['ignore', 'ignore', 'pipe'],\n })\n let stderr = ''\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8')\n })\n child.on('error', (err) => {\n resolve({\n pm: cmd.pm,\n cwd: cmd.cwd,\n args: cmd.args,\n exitCode: 127,\n stderr: stderr + `\\n${err.message}`,\n })\n })\n child.on('close', (code) => {\n resolve({\n pm: cmd.pm,\n cwd: cmd.cwd,\n args: cmd.args,\n exitCode: code ?? 1,\n stderr: stderr.trim(),\n })\n })\n })\n}\n","import type { LogEntry } from '@neat.is/types'\n\n// Bounded per-(projectName, source) ring buffer for LogEntry records\n// (docs/contracts/logs.md Rule 4, ADR-132). Fully in-memory, no\n// persistence — a daemon restart loses the buffer, the same trade-off NEAT\n// already accepts for the live graph between snapshots. Every pair owns its\n// own array, so a burst from one source can never evict another pair's\n// entries.\n\n// Default cap: the last 1,000 entries, and nothing older than 24h — both\n// bounds apply independently (see `prune` below), whichever trips first for\n// a given entry.\nexport const LOGS_STORE_MAX_ENTRIES = 1000\nexport const LOGS_STORE_MAX_AGE_MS = 24 * 60 * 60 * 1000\n\n// GET /logs query-param defaults. Exported so the REST route (api.ts) and\n// any future direct caller (MCP's get_logs) share one definition of \"sane\n// max\" rather than each picking their own number.\nexport const LOGS_QUERY_DEFAULT_LIMIT = 100\nexport const LOGS_QUERY_MAX_LIMIT = 1000\n\nconst buffers = new Map<string, LogEntry[]>()\n\n// Separator chosen so an ordinary project name or source string can't\n// collide two distinct (projectName, source) pairs onto the same key —\n// neither a NEAT project name nor a `source` value (logs.md Rule 1's fixed\n// set, extended one string per future connector) is expected to contain it.\nconst KEY_SEP = '::'\n\nfunction bufferKey(projectName: string, source: string): string {\n return `${projectName}${KEY_SEP}${source}`\n}\n\nfunction sourcesForProject(projectName: string): string[] {\n const prefix = `${projectName}${KEY_SEP}`\n const sources: string[] = []\n for (const key of buffers.keys()) {\n if (key.startsWith(prefix)) sources.push(key.slice(prefix.length))\n }\n return sources\n}\n\n// Drops entries older than the age cap, then trims to the count cap. Both\n// run every append so a buffer never grows past 1,000 elements even\n// mid-burst, and a slow, quiet source still ages its old entries out even\n// though it never fills the count cap.\nfunction prune(entries: LogEntry[], now: number): LogEntry[] {\n const cutoff = now - LOGS_STORE_MAX_AGE_MS\n let pruned = entries.filter((e) => {\n const t = Date.parse(e.timestamp)\n // A timestamp that fails to parse can't be judged stale — fail open\n // rather than silently dropping a malformed-but-real entry.\n return Number.isNaN(t) || t >= cutoff\n })\n if (pruned.length > LOGS_STORE_MAX_ENTRIES) {\n pruned = pruned.slice(pruned.length - LOGS_STORE_MAX_ENTRIES)\n }\n return pruned\n}\n\nexport function appendLogEntry(entry: LogEntry): void {\n const key = bufferKey(entry.projectName, entry.source)\n const existing = buffers.get(key) ?? []\n existing.push(entry)\n buffers.set(key, prune(existing, Date.now()))\n}\n\nexport interface QueryLogEntriesOptions {\n projectName: string\n // Repeatable per logs.md §5 — when omitted, every source this project has\n // ever written an entry under is included.\n source?: string[]\n service?: string\n limit?: number\n // ISO8601 lower bound, inclusive, matched against each entry's own\n // `timestamp` (not append time).\n since?: string\n}\n\n// Newest-first, merged across every matching source bucket for the\n// project, then service/since filtered. `limit` is applied last so a\n// direct caller (a future MCP get_logs, or a test) can ask for a bounded\n// slice without redoing the REST layer's separate total/count bookkeeping;\n// the REST route itself calls this once unlimited (for `total`) and slices\n// the result itself so `total` reflects the filtered-but-unlimited size.\nexport function queryLogEntries(opts: QueryLogEntriesOptions): LogEntry[] {\n const { projectName, source, service, since, limit } = opts\n const sources = source && source.length > 0 ? source : sourcesForProject(projectName)\n\n let merged: LogEntry[] = []\n for (const src of sources) {\n const buf = buffers.get(bufferKey(projectName, src))\n if (buf) merged = merged.concat(buf)\n }\n\n if (service) {\n merged = merged.filter((e) => e.serviceName === service)\n }\n\n if (since) {\n const sinceMs = Date.parse(since)\n if (!Number.isNaN(sinceMs)) {\n merged = merged.filter((e) => {\n const t = Date.parse(e.timestamp)\n return Number.isNaN(t) || t >= sinceMs\n })\n }\n }\n\n merged.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp))\n\n if (typeof limit === 'number' && Number.isFinite(limit) && limit >= 0) {\n return merged.slice(0, limit)\n }\n return merged\n}\n\n// Test seam. The store is a module-level singleton with no persistence —\n// tests that append fixture entries need a way back to empty between\n// cases, the same shape `resetGraph()` gives the in-memory graph.\nexport function resetLogsStore(): void {\n buffers.clear()\n}\n","// SSE handler for the frontend-facing event stream (ADR-051 #1).\n// Subscribes to the bus in events.ts, filters by project, writes\n// `event: <type>\\ndata: <json>\\n\\n` frames to the client. An initial\n// `:open\\n\\n` comment goes out at the handshake so EventSource opens\n// right away instead of waiting on the first event or heartbeat.\n//\n// Backpressure: per-connection queue cap of 1000 outstanding writes; once\n// hit, the connection is dropped with `event: error data: { reason:\n// 'backpressure' }` per ADR-051 #8. Heartbeat: comment line every 30s\n// keeps proxies from idle-timing out (ADR-051 #3).\n\nimport type { FastifyReply, FastifyRequest } from 'fastify'\nimport {\n EVENT_BUS_CHANNEL,\n eventBus,\n type NeatEventEnvelope,\n} from './events.js'\n\nexport const SSE_HEARTBEAT_MS = 30_000\nexport const SSE_BACKPRESSURE_CAP = 1000\n\nexport interface HandleSseOptions {\n project: string\n heartbeatMs?: number\n backpressureCap?: number\n}\n\nexport function handleSse(\n req: FastifyRequest,\n reply: FastifyReply,\n opts: HandleSseOptions,\n): void {\n const heartbeatMs = opts.heartbeatMs ?? SSE_HEARTBEAT_MS\n const backpressureCap = opts.backpressureCap ?? SSE_BACKPRESSURE_CAP\n\n reply.raw.setHeader('Content-Type', 'text/event-stream')\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform')\n reply.raw.setHeader('Connection', 'keep-alive')\n reply.raw.setHeader('X-Accel-Buffering', 'no')\n reply.raw.flushHeaders?.()\n\n // Flushing headers leaves the response body empty, so the browser's\n // EventSource stays in CONNECTING (readyState 0) until the first body byte\n // lands — which, on a quiet graph, is the first real event or the 30s\n // heartbeat, whichever comes first. Write a comment line right away so the\n // stream opens at the handshake and EventSource fires onopen immediately.\n // A colon-prefixed comment is a no-op per the SSE spec (clients ignore it,\n // same as the heartbeat), so it sits outside the locked ADR-051 taxonomy.\n // Written raw, bypassing the backpressure accounting below, exactly like\n // the heartbeat does.\n reply.raw.write(':open\\n\\n')\n\n let pending = 0\n let dropped = false\n\n const closeConnection = (): void => {\n if (dropped) return\n dropped = true\n eventBus.off(EVENT_BUS_CHANNEL, listener)\n clearInterval(heartbeat)\n if (!reply.raw.writableEnded) reply.raw.end()\n }\n\n const writeFrame = (frame: string): void => {\n if (dropped) return\n if (pending >= backpressureCap) {\n // Past the cap — emit one final error frame and drop. Don't try to\n // gracefully drain; a slow consumer that's already 1000 frames behind\n // is not going to catch up.\n const errFrame = `event: error\\ndata: ${JSON.stringify({ reason: 'backpressure' })}\\n\\n`\n reply.raw.write(errFrame)\n closeConnection()\n return\n }\n pending++\n reply.raw.write(frame, () => {\n pending = Math.max(0, pending - 1)\n })\n }\n\n const listener = (envelope: NeatEventEnvelope): void => {\n if (envelope.project !== opts.project) return\n writeFrame(`event: ${envelope.type}\\ndata: ${JSON.stringify(envelope.payload)}\\n\\n`)\n }\n\n eventBus.on(EVENT_BUS_CHANNEL, listener)\n\n const heartbeat = setInterval(() => {\n if (dropped) return\n reply.raw.write(':heartbeat\\n\\n')\n }, heartbeatMs)\n if (typeof heartbeat.unref === 'function') heartbeat.unref()\n\n req.raw.on('close', closeConnection)\n reply.raw.on('close', closeConnection)\n reply.raw.on('error', closeConnection)\n}\n","// Connector configuration — `~/.neat/connectors.json` read + env-ref\n// resolution (docs/contracts/connector-config.md, ADR-130).\n//\n// This is the file family sibling to the machine-level project registry\n// (registry.ts): per-user, machine-local, never version-controlled. Where the\n// project registry says which projects exist, `connectors.json` says which\n// built connectors are turned on for them and where each one's credential\n// comes from. It is\n// the one file in the family that references real secrets, so it is mode\n// `0600` and — by default — holds a *pointer* to a secret (an env-var\n// reference resolved only at daemon-read time), never the secret itself\n// (contract §1, §2).\n//\n// This module owns two of the contract's three seams: reading/validating the\n// file (`readConnectorsConfig`) and resolving a `credential` env-ref to a\n// value in memory (`resolveCredential`). The provider dispatch table\n// (connectors/registry.ts) turns a resolved entry into a `ConnectorRegistration`;\n// the daemon (daemon.ts) triggers the read at slot bootstrap and hands the\n// registrations to `startConnectorPollLoop`. This file never writes the\n// snapshot and never lets a resolved secret reach disk (contract §6,\n// connectors.md §6, contracts.md Rule 13).\n\nimport os from 'node:os'\nimport path from 'node:path'\nimport { promises as fs } from 'node:fs'\n\n// Only bump 1 → 2 when the on-disk shape changes in a way an old reader\n// couldn't ignore. A file with no `version` reads as 1 (the shape below).\nexport const CONNECTORS_CONFIG_VERSION = 1\n\n/**\n * A credential, before resolution. Either a single env-ref/plaintext string\n * (single-field providers — `\"$RAILWAY_TOKEN\"`), or a per-field map of them\n * (multi-field providers — `{ managementToken: \"$SUPABASE_MGMT_TOKEN\",\n * postgresConnectionString: \"$SUPABASE_DB_URL\" }`). A leading `$` marks the\n * string as the name of an environment variable; anything else is a literal\n * (contract §2).\n */\nexport type CredentialRef = string | Record<string, string>\n\n/**\n * One entry in `~/.neat/connectors.json`. Mirrors the contract §1 schema\n * exactly — `credentials`/`config`/`enabled`/`addedAt` are deliberately\n * absent; the shape converged on a single `credential` ref plus opaque\n * `options`, and every listed entry is active (there is no `enabled` gate).\n */\nexport interface ConnectorEntry {\n // Addressable handle, auto-slugged from provider (disambiguated by project\n // when a provider repeats). The CLI's `remove <id>` / `test <id>` use it.\n id: string\n // 'supabase' | 'railway' | 'firebase' | 'cloudflare' | ... — validated\n // against the dispatch table (registry.ts), not hardcoded here.\n provider: string\n // Matches a registered project's `name`; omitted binds to whichever project\n // the daemon is bootstrapping (one daemon per project, ADR-096).\n project?: string\n // Env-ref by default (see resolveCredential).\n credential: CredentialRef\n // Provider-shaped, non-secret config (project ref, service-id mappings,\n // poll cadence). Opaque here; the dispatch table maps it onto each\n // provider's own config type.\n options?: Record<string, unknown>\n}\n\nexport interface ConnectorsConfig {\n version: number\n connectors: ConnectorEntry[]\n}\n\n/**\n * A credential after env-ref resolution — values in memory only. `single`\n * for a one-string credential (the dispatch table knows which record key it\n * lands under); `fields` for a multi-field credential (the keys are already\n * the provider's own).\n */\nexport type ResolvedCredential =\n | { kind: 'single'; value: string }\n | { kind: 'fields'; fields: Record<string, string> }\n\n/**\n * Thrown when a `$VAR` credential reference names an environment variable\n * that isn't set. Kept distinct from every other failure so a caller can\n * tell \"you forgot to `export`\" apart from \"your token is wrong\" (contract\n * §4). The message names the variable *with* its `$` — `\"$SUPABASE_KEY is\n * unset\"` — so a log line points straight at what to set.\n */\nexport class EnvRefUnsetError extends Error {\n readonly ref: string\n readonly varName: string\n constructor(ref: string, varName: string) {\n super(`${ref} is unset`)\n this.name = 'EnvRefUnsetError'\n this.ref = ref\n this.varName = varName\n }\n}\n\n// Resolve `~/.neat/` the same way registry.ts does, per call, so a test\n// overriding NEAT_HOME (or HOME) before a run lands here too — module-load\n// order never matters.\nfunction neatHome(): string {\n const override = process.env.NEAT_HOME\n if (override && override.length > 0) return path.resolve(override)\n return path.join(os.homedir(), '.neat')\n}\n\nexport function connectorsConfigPath(home: string = neatHome()): string {\n return path.join(home, 'connectors.json')\n}\n\n// Owner-read/write only. Anything with group or other bits set is looser than\n// the contract's `0600` guarantee and gets a warning (not a refusal — a\n// mis-permissioned file is a hygiene problem, not a reason to leave a\n// connector dark).\nconst MODE_MASK_LOOSER_THAN_0600 = 0o077\n\nasync function warnIfModeLooserThan0600(file: string): Promise<void> {\n // The POSIX bits are meaningless on Windows; skip the check there rather\n // than warn on every read.\n if (process.platform === 'win32') return\n try {\n const stat = await fs.stat(file)\n if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {\n const mode = (stat.mode & 0o777).toString(8).padStart(3, '0')\n console.warn(\n `[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for — run \\`chmod 600 ${file}\\``,\n )\n }\n } catch {\n // Racing a delete between read and stat is harmless — the read already\n // succeeded; skip the permission warning rather than crash on it.\n }\n}\n\n/**\n * Read and validate `~/.neat/connectors.json`.\n *\n * - A missing file is the common, un-configured case → an empty connector\n * list, never an error (the daemon just starts no poll loops).\n * - A malformed file throws a clear error naming what's wrong. The daemon's\n * read path catches it and leaves the slot's connectors empty rather than\n * crashing the whole daemon (contract §6, `project-registry.md`'s\n * graceful-skip discipline).\n * - A file looser than `0600` is read but warns.\n */\nexport async function readConnectorsConfig(\n home: string = neatHome(),\n): Promise<ConnectorsConfig> {\n const file = connectorsConfigPath(home)\n let raw: string\n try {\n raw = await fs.readFile(file, 'utf8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { version: CONNECTORS_CONFIG_VERSION, connectors: [] }\n }\n throw err\n }\n await warnIfModeLooserThan0600(file)\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (err) {\n throw new Error(`${file} is not valid JSON: ${(err as Error).message}`)\n }\n return validateConfig(parsed, file)\n}\n\nfunction validateConfig(parsed: unknown, file: string): ConnectorsConfig {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error(`${file} must be a JSON object with a \"connectors\" array`)\n }\n const obj = parsed as Record<string, unknown>\n const version = obj.version === undefined ? CONNECTORS_CONFIG_VERSION : obj.version\n if (typeof version !== 'number' || !Number.isInteger(version)) {\n throw new Error(`${file}: \"version\" must be an integer`)\n }\n const rawConnectors = obj.connectors\n if (!Array.isArray(rawConnectors)) {\n throw new Error(`${file}: \"connectors\" must be an array`)\n }\n const connectors = rawConnectors.map((entry, i) => validateEntry(entry, i, file))\n return { version, connectors }\n}\n\nfunction validateEntry(entry: unknown, index: number, file: string): ConnectorEntry {\n const where = `${file}: connectors[${index}]`\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n throw new Error(`${where} must be an object`)\n }\n const e = entry as Record<string, unknown>\n const id = e.id\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(`${where}.id must be a non-empty string`)\n }\n const provider = e.provider\n if (typeof provider !== 'string' || provider.length === 0) {\n throw new Error(`${where}.provider must be a non-empty string`)\n }\n if (e.project !== undefined && typeof e.project !== 'string') {\n throw new Error(`${where}.project must be a string when present`)\n }\n const credential = validateCredentialRef(e.credential, `${where}.credential`)\n let options: Record<string, unknown> | undefined\n if (e.options !== undefined) {\n if (typeof e.options !== 'object' || e.options === null || Array.isArray(e.options)) {\n throw new Error(`${where}.options must be an object when present`)\n }\n options = e.options as Record<string, unknown>\n }\n return {\n id,\n provider,\n ...(typeof e.project === 'string' ? { project: e.project } : {}),\n credential,\n ...(options ? { options } : {}),\n }\n}\n\nfunction validateCredentialRef(raw: unknown, where: string): CredentialRef {\n if (typeof raw === 'string') {\n if (raw.length === 0) throw new Error(`${where} must be a non-empty string`)\n return raw\n }\n if (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n const fields = raw as Record<string, unknown>\n const keys = Object.keys(fields)\n if (keys.length === 0) throw new Error(`${where} object must have at least one field`)\n for (const key of keys) {\n const v = fields[key]\n if (typeof v !== 'string' || v.length === 0) {\n throw new Error(`${where}.${key} must be a non-empty string`)\n }\n }\n return fields as Record<string, string>\n }\n throw new Error(`${where} must be a string or an object of strings`)\n}\n\n/**\n * Resolve one credential ref to values held only in memory.\n *\n * A `$`-prefixed string is an environment-variable reference — resolved\n * against `env` (default `process.env`) at the moment the daemon builds a\n * registration; an unset variable throws `EnvRefUnsetError`. Any other string\n * is an explicit plaintext literal (the opt-in at-rest fallback, contract\n * §2), passed through unchanged. A field map resolves each value the same\n * way.\n */\nexport function resolveCredential(\n ref: CredentialRef,\n env: NodeJS.ProcessEnv = process.env,\n): ResolvedCredential {\n if (typeof ref === 'string') {\n return { kind: 'single', value: resolveRef(ref, env) }\n }\n const fields: Record<string, string> = {}\n for (const [key, value] of Object.entries(ref)) {\n fields[key] = resolveRef(value, env)\n }\n return { kind: 'fields', fields }\n}\n\n// A leading `$` (with at least one char after it) marks an env-ref; the value\n// must be set and non-empty. Everything else — including a bare `\"$\"` — is a\n// plaintext literal.\nfunction resolveRef(value: string, env: NodeJS.ProcessEnv): string {\n if (value.length > 1 && value.startsWith('$')) {\n const varName = value.slice(1)\n const resolved = env[varName]\n if (resolved === undefined || resolved.length === 0) {\n throw new EnvRefUnsetError(value, varName)\n }\n return resolved\n }\n return value\n}\n\n/**\n * Whether a connector entry belongs to the project a daemon slot is\n * bootstrapping. An entry with no `project` binds to whatever project is\n * being bootstrapped (the single-daemon-per-project common case); an entry\n * naming a project matches only that one. A non-match is skipped, never\n * errored (contract §6).\n */\nexport function connectorMatchesProject(entry: ConnectorEntry, project: string): boolean {\n return entry.project === undefined || entry.project === project\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Write side — `neat connector add/remove` mutate this file (contract §1, §3).\n//\n// The read side above never touches disk beyond a read; this is the half the\n// CLI drives. Two invariants the contract calls non-negotiable ride here: the\n// file lands mode `0600` on every write (owner-only — it points at real\n// secrets), and every write is atomic (tmp + fsync + rename) under an\n// exclusive lock, so a concurrent write can't leave a half-file behind. The\n// same discipline the sibling machine-level registry file already holds\n// (registry.ts), plus the explicit `0600` this file's secrets add.\n// ─────────────────────────────────────────────────────────────────────────\n\nconst CONNECTORS_LOCK_TIMEOUT_MS = 5_000\nconst CONNECTORS_LOCK_RETRY_MS = 50\n\nexport function connectorsConfigLockPath(home: string = neatHome()): string {\n return path.join(home, 'connectors.json.lock')\n}\n\n// A leading `$` (with at least one char after it) marks a value as an env-ref,\n// the exact rule `resolveRef` above resolves by. Kept as one predicate so the\n// writer, the reader, and the `list` display can never disagree on what counts\n// as a pointer vs a literal.\nexport function isEnvRef(value: string): boolean {\n return value.length > 1 && value.startsWith('$')\n}\n\n/**\n * Redact a credential ref to its env-ref pointer for a read surface — the\n * connector-status endpoint (`GET /:project/connectors`, ADR-136) and any other\n * caller that must show a connector's credential without ever resolving it. A\n * single-field credential redacts to one string; a multi-field one to a\n * field→pointer map. An env-ref (`$VAR`) is safe to show verbatim — it names the\n * variable, not the secret; a plaintext literal shows `****`. Reuses `isEnvRef`,\n * the same predicate the daemon resolver and `describeCredential` rely on, so no\n * read surface can drift from what actually counts as a pointer. This never\n * touches the environment — the secret value is never read here (connectors.md\n * §6, connector-config.md §2).\n */\nexport function redactCredentialRef(ref: CredentialRef): string | Record<string, string> {\n const one = (value: string): string => (isEnvRef(value) ? value : '****')\n if (typeof ref === 'string') return one(ref)\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(ref)) out[key] = one(value)\n return out\n}\n\n/**\n * tmp + fsync + rename, with the tmp file created mode `0600` so the bytes are\n * never briefly readable by group/other before the rename swaps them in. An\n * explicit `chmod(0o600)` on the handle backs the open-mode up in case a umask\n * ever widened it (it can't for `0600`, but the belt-and-suspenders keeps the\n * guarantee legible to the contract's future permission audit). rename is\n * atomic on POSIX and carries the tmp inode's mode with it.\n */\nasync function writeConfigAtomically0600(file: string, contents: string): Promise<void> {\n await fs.mkdir(path.dirname(file), { recursive: true })\n const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`\n const fd = await fs.open(tmp, 'w', 0o600)\n try {\n await fd.writeFile(contents, 'utf8')\n await fd.chmod(0o600)\n await fd.sync()\n } finally {\n await fd.close()\n }\n await fs.rename(tmp, file)\n}\n\n// Exclusive-create lock, the same cross-platform flock stand-in the sibling\n// registry uses: `wx` fails if the file exists, so whoever creates it holds the\n// lock; a contender retries until the 5s timeout. Best-effort PID stamp so a\n// stuck lock names who to look for.\nasync function acquireConnectorsLock(\n lockPath: string,\n timeoutMs: number = CONNECTORS_LOCK_TIMEOUT_MS,\n): Promise<void> {\n const deadline = Date.now() + timeoutMs\n await fs.mkdir(path.dirname(lockPath), { recursive: true })\n for (;;) {\n try {\n const fd = await fs.open(lockPath, 'wx')\n try {\n await fd.writeFile(`${process.pid}\\n`, 'utf8')\n } finally {\n await fd.close()\n }\n return\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err\n if (Date.now() >= deadline) {\n throw new Error(\n `neat connector: timed out after ${timeoutMs}ms waiting for ${lockPath}. ` +\n 'Another neat process may be writing the connector config; if none is, remove that file by hand.',\n )\n }\n await new Promise((r) => setTimeout(r, CONNECTORS_LOCK_RETRY_MS))\n }\n }\n}\n\nasync function releaseConnectorsLock(lockPath: string): Promise<void> {\n await fs.unlink(lockPath).catch(() => {})\n}\n\nasync function withConnectorsLock<T>(home: string, fn: () => Promise<T>): Promise<T> {\n const lock = connectorsConfigLockPath(home)\n await acquireConnectorsLock(lock)\n try {\n return await fn()\n } finally {\n await releaseConnectorsLock(lock)\n }\n}\n\nfunction serializeConfig(config: ConnectorsConfig): string {\n return (\n JSON.stringify(\n { version: config.version ?? CONNECTORS_CONFIG_VERSION, connectors: config.connectors },\n null,\n 2,\n ) + '\\n'\n )\n}\n\n/**\n * Insert `entry`, or replace the existing entry with the same `id`. The whole\n * read-modify-write runs under one lock so two concurrent adds can't clobber\n * each other, and the entry is validated before it lands so a malformed write\n * fails loudly rather than corrupting the file. Returns whether an existing\n * entry was replaced (vs a fresh insert) so the CLI can report which happened.\n */\nexport async function upsertConnectorEntry(\n entry: ConnectorEntry,\n home: string = neatHome(),\n): Promise<{ replaced: boolean }> {\n const file = connectorsConfigPath(home)\n return withConnectorsLock(home, async () => {\n const config = await readConnectorsConfig(home)\n // Re-validate through the same reader-side check so a hand-built entry\n // can't slip a bad shape past the writer.\n validateEntry(entry, config.connectors.length, file)\n const idx = config.connectors.findIndex((c) => c.id === entry.id)\n const replaced = idx >= 0\n if (replaced) config.connectors[idx] = entry\n else config.connectors.push(entry)\n await writeConfigAtomically0600(file, serializeConfig(config))\n return { replaced }\n })\n}\n\n/**\n * Remove the entry with `id`. Returns the removed entry, or `undefined` when no\n * entry carried that id (the CLI turns that into a clear \"no such id\"). Atomic\n * `0600` rewrite under the same lock as `upsertConnectorEntry`.\n */\nexport async function removeConnectorEntry(\n id: string,\n home: string = neatHome(),\n): Promise<ConnectorEntry | undefined> {\n const file = connectorsConfigPath(home)\n return withConnectorsLock(home, async () => {\n const config = await readConnectorsConfig(home)\n const idx = config.connectors.findIndex((c) => c.id === id)\n if (idx < 0) return undefined\n const [removed] = config.connectors.splice(idx, 1)\n await writeConfigAtomically0600(file, serializeConfig(config))\n return removed\n })\n}\n\n// Lower-case, collapse any run of non-alphanumerics to a single hyphen, trim\n// stray hyphens. Turns a project name or provider into a stable id fragment.\nfunction slugFragment(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\n/**\n * Auto-slug an `id` from the provider, disambiguated by project when a provider\n * repeats (contract §1). Bare provider first (`supabase`); on collision, the\n * provider plus its project (`supabase-brief`); then a numeric suffix\n * (`supabase-2`) as a last resort so `add` never fails just to name an entry.\n */\nexport function autoSlugConnectorId(\n provider: string,\n project: string | undefined,\n existingIds: ReadonlySet<string>,\n): string {\n const base = slugFragment(provider) || 'connector'\n if (!existingIds.has(base)) return base\n const projectFrag = project ? slugFragment(project) : ''\n if (projectFrag) {\n const withProject = `${base}-${projectFrag}`\n if (!existingIds.has(withProject)) return withProject\n }\n const stem = projectFrag ? `${base}-${projectFrag}` : base\n for (let n = 2; ; n++) {\n const candidate = `${stem}-${n}`\n if (!existingIds.has(candidate)) return candidate\n }\n}\n\n/** One credential field rendered for `list`, never carrying a resolved secret. */\nexport interface CredentialFieldDisplay {\n // Present only for a multi-field credential; the single-string form has none.\n field?: string\n kind: 'env-ref' | 'plaintext'\n // The env-var name (with its `$`) for a ref — safe to print, it's a pointer,\n // not the secret — or a fixed `****` mask for a plaintext literal.\n display: string\n // env-ref only: whether the referenced variable is set in `env`.\n status?: 'set' | 'unset'\n}\n\n/**\n * Render a credential for `neat connector list` — the redacted view, never the\n * resolved secret (contract §2, and the never-log-a-secret rule connectors.md\n * §6 / contracts.md Rule 13 already hold). An env-ref shows its `$VAR` pointer\n * plus whether that variable is currently set; a plaintext literal shows only\n * `****`. Reuses `isEnvRef` so the display can't drift from what the daemon\n * actually resolves.\n */\nexport function describeCredential(\n ref: CredentialRef,\n env: NodeJS.ProcessEnv = process.env,\n): CredentialFieldDisplay[] {\n const one = (value: string, field?: string): CredentialFieldDisplay => {\n if (isEnvRef(value)) {\n const varName = value.slice(1)\n const resolved = env[varName]\n const set = typeof resolved === 'string' && resolved.length > 0\n return {\n ...(field ? { field } : {}),\n kind: 'env-ref',\n display: value,\n status: set ? 'set' : 'unset',\n }\n }\n return { ...(field ? { field } : {}), kind: 'plaintext', display: '****' }\n }\n if (typeof ref === 'string') return [one(ref)]\n return Object.entries(ref).map(([k, v]) => one(v, k))\n}\n","// Connector poll-status tracker — the in-process record of each connector's\n// most recent poll tick (docs/contracts/connectors.md §8, ADR-136).\n//\n// The connector poll loop (`startConnectorPollLoop`, index.ts) writes here on\n// EVERY tick, success and failure; the connector-status endpoint\n// (`GET /:project/connectors`, api.ts) reads here. A process-local module\n// singleton is the right shape for the same reason `logs-store.ts` is: this is\n// live runtime state, not graph or snapshot state — a daemon restart loses it\n// and re-derives it on the next poll, exactly the \"OBSERVED is a live signal,\n// not an archive\" trade the rest of the connectors plane already makes.\n//\n// Nothing here ever holds a credential. The tracker records an id, an outcome,\n// a short error string, a signal count, and timestamps — the resolved secret\n// stays inside the `ConnectorContext` that flows to `poll()` and never reaches\n// this module (connectors.md §6, connector-config.md §6).\n\nimport type { ConnectorPollState, ConnectorStatus } from '@neat.is/types'\n\n// A connector polls on a 60s default cadence (`DEFAULT_POLL_INTERVAL_MS`,\n// index.ts). Five intervals of silence is the point past which \"hasn't polled\n// recently\" is worth surfacing as `stale` rather than a healthy connector that\n// simply hasn't ticked yet. This is a connector-poll concept, deliberately\n// distinct from the per-edge-type OBSERVED→STALE thresholds (ingest.ts) — those\n// decay graph edges; this flags a poll loop that has gone quiet or wedged.\nexport const CONNECTOR_STALE_THRESHOLD_MS = 5 * 60_000\n\n// One connector's last recorded tick. `lastOkAt` is kept separately from\n// `lastPollAt` so a run of failing ticks can still tell how long it has been\n// since the connector last actually succeeded — that gap is what `stale` reads.\ninterface PollRecord {\n lastPollAt: string\n lastOutcome: 'ok' | 'error'\n lastError: string | null\n signalsLastPoll: number\n lastOkAt: string | null\n}\n\nconst records = new Map<string, PollRecord>()\n\nconst MAX_ERROR_LEN = 200\n\n/**\n * Reduce a poll error to a short, single-line string safe to expose on the\n * status endpoint. Collapses whitespace and truncates — a poll error carries a\n * provider name and HTTP status, never a credential (the junction's own error\n * strings are secret-free by construction, connectors/registry.ts §authProbe),\n * and the truncation caps any accidental verbosity.\n */\nexport function sanitizePollError(err: unknown): string {\n const msg = err instanceof Error ? err.message : String(err)\n const collapsed = msg.replace(/\\s+/g, ' ').trim()\n return collapsed.length > MAX_ERROR_LEN ? `${collapsed.slice(0, MAX_ERROR_LEN - 1)}…` : collapsed\n}\n\nexport interface PollTick {\n outcome: 'ok' | 'error'\n // The tick's own start time, ISO8601.\n at: string\n // Signals the tick returned — recorded on a successful tick, 0 otherwise.\n signalsLastPoll?: number\n // Short, secret-free message on a failing tick; ignored on a successful one.\n error?: string | null\n}\n\n/**\n * Record one poll tick for connector `id`. Called by the poll loop on every\n * tick. A successful tick advances `lastOkAt`; a failing tick leaves the prior\n * `lastOkAt` in place so `stale` still measures time-since-last-success.\n */\nexport function recordConnectorPoll(id: string, tick: PollTick): void {\n const prev = records.get(id)\n records.set(id, {\n lastPollAt: tick.at,\n lastOutcome: tick.outcome,\n lastError: tick.outcome === 'error' ? (tick.error ?? null) : null,\n signalsLastPoll: tick.signalsLastPoll ?? 0,\n lastOkAt: tick.outcome === 'ok' ? tick.at : (prev?.lastOkAt ?? null),\n })\n}\n\n// Derive the reported state from the raw record + wall clock. `error` wins over\n// `stale` because \"the last tick threw\" is the fresher, more actionable signal;\n// `stale` is for a connector whose last *success* has aged out of the window\n// (a loop that stopped ticking, so no fresh error either).\nfunction deriveState(rec: PollRecord, now: number, thresholdMs: number): ConnectorPollState {\n if (rec.lastOutcome === 'error') return 'error'\n const okAt = rec.lastOkAt ? Date.parse(rec.lastOkAt) : NaN\n if (!Number.isNaN(okAt) && now - okAt > thresholdMs) return 'stale'\n return 'healthy'\n}\n\n/**\n * The live status for connector `id`. An id with no recorded tick is `idle`\n * with null timestamps — the honest \"configured but not yet polled\" state, the\n * same shape the endpoint returns for a freshly added connector. `now` and\n * `thresholdMs` are injectable for deterministic tests.\n */\nexport function getConnectorStatus(\n id: string,\n now: number = Date.now(),\n thresholdMs: number = CONNECTOR_STALE_THRESHOLD_MS,\n): ConnectorStatus {\n const rec = records.get(id)\n if (!rec) {\n return {\n state: 'idle',\n lastPollAt: null,\n lastOutcome: null,\n lastError: null,\n signalsLastPoll: 0,\n }\n }\n return {\n state: deriveState(rec, now, thresholdMs),\n lastPollAt: rec.lastPollAt,\n lastOutcome: rec.lastOutcome,\n lastError: rec.lastError,\n signalsLastPoll: rec.signalsLastPoll,\n }\n}\n\n/** Drop every recorded status. Test-only — the daemon never clears mid-run. */\nexport function resetConnectorStatus(): void {\n records.clear()\n}\n","// Connectors plane — the provider-agnostic pull/map/fuse pipeline\n// (docs/contracts/connectors.md, docs/connectors/README.md, ADR-124).\n//\n// A provider module (packages/core/src/connectors/<provider>/, none shipped\n// yet) owns exactly two things: fetching `ObservedSignal[]` off its own API\n// (`ObservedConnector.poll`) and mapping a signal's targetKind/targetName to\n// a NEAT node id (the `resolveTarget` callback below). Everything after that\n// — resolving a static call site, minting the OBSERVED edge — is written\n// once, here, and is identical for every provider: a connector-sourced edge\n// and a span-sourced edge carry the same provenance and land through the\n// same mutation primitives OTel ingest uses (README.md's opening paragraph).\n//\n// Mutation authority (ADR-030 — see contracts.test.ts's \"Lifecycle contract\"\n// audit): only ingest.ts and extract/* may call a graph mutator directly.\n// This module never does — every node/edge write below goes through an\n// ingest.ts primitive (`ensureServiceNode`, `ensureObservedFileNode`,\n// `upsertObservedEdge`), exported for exactly this reuse. A future\n// provider's own target-node creation (a Supabase table InfraNode, say)\n// belongs in ingest.ts too, for the same reason — this module only ever\n// calls into it, never mutates the graph itself.\n\nimport { NodeType, parseFileId, Provenance, type EdgeTypeValue, type GraphEdge } from '@neat.is/types'\nimport type { NeatGraph } from '../graph.js'\nimport {\n ensureInfraNode,\n ensureObservedFileNode,\n ensureServiceNode,\n mergeObservedColumns,\n reconcileObservedRelPath,\n upsertObservedEdge,\n type CallSite,\n} from '../ingest.js'\nimport type { ConnectorContext, ObservedConnector, ObservedSignal } from './types.js'\nimport { recordConnectorPoll, sanitizePollError } from './status.js'\n\nexport type {\n ConnectorCallSite,\n ConnectorContext,\n ObservedConnector,\n ObservedSignal,\n} from './types.js'\n\n// env-unscoped, matching the sentinel identity.ts uses for \"no deployment\n// signal\" (ADR-074 §2) — a connector signal carries no\n// deployment.environment(.name) the way an OTel span might.\nconst NO_ENV = 'unknown'\n\n/**\n * What a provider's target-resolution step hands back for one signal. The\n * generic pipeline needs both endpoints of the edge it's about to mint:\n *\n * - `serviceName` is the NEAT manifest service whose code produced the\n * signal — the edge's source. The shared pipeline turns it into a plain\n * ServiceNode id, or a FileNode id once the fuse step below resolves the\n * signal's callSite against it.\n * - `targetNodeId` is the id the provider's own mapping already resolved —\n * an `infraId(...)` sub-resource, a RouteNode, a ServiceNode, whatever\n * (see each provider's docs/connectors/<provider>.md §Fusion).\n *\n * Returning `null` skips the signal honestly: an unresolvable target never\n * fabricates a node or edge (the same discipline file-awareness.md §6\n * states for OTel ingest).\n */\nexport interface ResolvedConnectorTarget {\n targetNodeId: string\n serviceName: string\n edgeType: EdgeTypeValue\n /**\n * Set when `targetNodeId` names an InfraNode no static extractor has (yet)\n * declared — the honest \"observed but undeclared\" fallback\n * (docs/contracts/connectors.md §4a, ADR-133). A provider's `resolveTarget`\n * has no mutation authority of its own (ADR-030), so it declares the need\n * here instead of creating the node itself; the generic pipeline below\n * calls `ensureInfraNode` before minting the edge. `targetNodeId` MUST equal\n * `infraId(kind, name)` when this is set.\n */\n ensureInfraNode?: { kind: string; name: string; provider: string }\n}\n\nexport type ResolveConnectorTarget = (\n signal: ObservedSignal,\n ctx: ConnectorContext,\n) => ResolvedConnectorTarget | null\n\nexport interface ConnectorPollResult {\n // Signals connector.poll() returned this tick.\n signalCount: number\n // Fresh OBSERVED edges minted.\n edgesCreated: number\n // Existing OBSERVED edges whose signal block advanced.\n edgesUpdated: number\n // Signals that resolved to no target (resolveTarget returned null, or the\n // resolved target/service node doesn't exist in the graph yet) — dropped\n // honestly rather than minting a fabricated edge.\n unresolved: number\n}\n\n// #803 — file-grain a connector observation by attribution. A pull-connector's\n// signal carries the target (table / route) it observed but no source location —\n// the provider telemetry never recorded which line called it. The static\n// extractor does: `<client>.from('table')` mints a `file → target` EXTRACTED\n// edge. So when a connector observes `service → target` with no call site of its\n// own, land the OBSERVED edge on the file that statically makes that call —\n// but only when exactly one file in the service does, so the attribution is a\n// fact, not a guess. Ambiguous (several call sites) or none → service-coarse,\n// honestly (connectors.md — never fabricate a source).\nfunction staticCallSiteFor(\n graph: NeatGraph,\n serviceName: string,\n targetNodeId: string,\n): CallSite | undefined {\n if (!graph.hasNode(targetNodeId)) return undefined\n const sites: CallSite[] = []\n for (const edgeId of graph.inboundEdges(targetNodeId)) {\n const edge = graph.getEdgeAttributes(edgeId) as GraphEdge\n if (edge.provenance !== Provenance.EXTRACTED) continue\n const parsed = parseFileId(edge.source)\n if (!parsed || parsed.service !== serviceName || !edge.evidence) continue\n const site: CallSite = { relPath: edge.evidence.file }\n if (edge.evidence.line !== undefined) site.line = edge.evidence.line\n sites.push(site)\n }\n return sites.length === 1 ? sites[0] : undefined\n}\n\n// #803 — file-grain an *ingress* observation whose target is a RouteNode.\n// `staticCallSiteFor` can't reach a route: routes.ts owns a route via\n// `service ──CONTAINS──▶ route`, not a `file → route` call-site edge, so there\n// is nothing inbound to attribute through. But the RouteNode already records\n// its own definition site — `path` (the service-relative source file routes.ts\n// parsed the route from) and `line`. So a connector that observes a route being\n// hit (Cloudflare Workers, Firebase Hosting) file-grains onto that recorded\n// site directly: the route's own source location, a fact the static pass\n// already established, never a guess. This generalizes what railway/index.ts\n// does per-connector — it reads `route.path`/`route.line` into the signal's own\n// `callSite` in its map layer — into the shared pipeline, so every\n// route-targeting connector file-grains the same way without duplicating the\n// lookup. (Railway keeps setting its own `callSite`, which still wins below;\n// this only covers connectors that resolve a route target but carry no\n// callSite of their own.)\nfunction routeCallSiteFor(graph: NeatGraph, targetNodeId: string): CallSite | undefined {\n if (!graph.hasNode(targetNodeId)) return undefined\n const attrs = graph.getNodeAttributes(targetNodeId) as { type?: string; path?: string; line?: number }\n if (attrs.type !== NodeType.RouteNode || !attrs.path) return undefined\n const site: CallSite = { relPath: attrs.path }\n if (attrs.line !== undefined) site.line = attrs.line\n return site\n}\n\n/**\n * One poll cycle: fetch, map, fuse, mint. Pure with respect to `ctx` — the\n * caller (`startConnectorPollLoop` below, or a one-shot `neat sync`) owns\n * advancing `ctx.since` between calls.\n */\nexport async function runConnectorPoll(\n connector: ObservedConnector,\n ctx: ConnectorContext,\n graph: NeatGraph,\n resolveTarget: ResolveConnectorTarget,\n): Promise<ConnectorPollResult> {\n const signals = await connector.poll(ctx)\n let edgesCreated = 0\n let edgesUpdated = 0\n let unresolved = 0\n\n for (const signal of signals) {\n const resolved = resolveTarget(signal, ctx)\n if (!resolved) {\n unresolved++\n continue\n }\n\n // Honest-fallback declaration (§ResolvedConnectorTarget doc) — ensure the\n // InfraNode exists before the upsert below needs it to. Idempotent no-op\n // once the node is created on a later poll.\n if (resolved.ensureInfraNode) {\n const { kind, name, provider } = resolved.ensureInfraNode\n ensureInfraNode(graph, kind, name, provider)\n }\n\n // ADR-157 — when the provider's signal parsed columns off its query text\n // (Neon / Supabase `pg_stat_statements`), merge them onto the resolved table\n // node as OBSERVED column attributes, through the same ingest primitive OTel\n // ingest uses. A no-op for a non-table target or a signal with no columns, so\n // a route / REST-path signal grows nothing.\n mergeObservedColumns(graph, resolved.targetNodeId, signal.columns)\n\n // Same shape ingest.ts's handleSpan uses for every span: auto-create a\n // minimal ServiceNode the first time this service is seen so the edge\n // upsert below always has a source endpoint, even for a service the\n // static extractor hasn't reached (or never will, in an OTel-less setup).\n const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV)\n\n // File-grain fusion when the signal carries a call site, through the\n // same reconcileObservedRelPath path OTel ingest uses (file-awareness.md\n // §4) — service-level, honestly, when it doesn't (§6, never fabricated).\n const callSite: CallSite | undefined = signal.callSite\n ? { relPath: signal.callSite.file, line: signal.callSite.line }\n : routeCallSiteFor(graph, resolved.targetNodeId) ??\n staticCallSiteFor(graph, resolved.serviceName, resolved.targetNodeId)\n const sourceId = callSite\n ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite)\n : serviceNodeId\n const evidence = callSite\n ? {\n file: reconcileObservedRelPath(graph, resolved.serviceName, callSite.relPath),\n line: callSite.line,\n }\n : undefined\n\n // upsertObservedEdge increments its signal block by exactly one call per\n // invocation — the right unit for a single span, but a connector signal\n // is already an aggregate over the whole poll window (`callCount` calls,\n // `errorCount` of them failing). Replay it that many times so the\n // edge's spanCount/errorCount — and the confidence grade derived from\n // them, ADR-066 — land the same as if `callCount` individual spans had\n // arrived, rather than undercounting a batched signal down to +1.\n const calls = Math.trunc(signal.callCount)\n // A non-finite count (NaN/Infinity from a provider's shape drift) is\n // neither an observation nor an honest miss — drop it rather than letting\n // a zero-iteration loop below tally a phantom edge update (gate #8).\n if (!Number.isFinite(calls) || calls < 1) continue\n const errors = Number.isFinite(signal.errorCount)\n ? Math.min(Math.max(Math.trunc(signal.errorCount), 0), calls)\n : 0\n\n let created = false\n let ok = true\n for (let i = 0; i < calls; i++) {\n const result = upsertObservedEdge(\n graph,\n resolved.edgeType,\n sourceId,\n resolved.targetNodeId,\n signal.lastObservedIso,\n i < errors,\n evidence,\n )\n if (!result) {\n // Target node doesn't exist yet — an extractor gap (supabase.md\n // §Static extractor gap documents exactly this case) or a provider\n // that hasn't minted it. Honest miss, not a crash.\n ok = false\n break\n }\n if (i === 0) created = result.created\n }\n if (!ok) {\n unresolved++\n continue\n }\n if (created) edgesCreated++\n else edgesUpdated++\n }\n\n return { signalCount: signals.length, edgesCreated, edgesUpdated, unresolved }\n}\n\nexport interface ConnectorPollLoopOptions {\n intervalMs?: number\n onError?: (err: unknown) => void\n // The connector's config-entry id (ADR-130). When set, every tick — success\n // and failure — is recorded to the in-process status tracker (status.ts) the\n // connector-status endpoint reads (ADR-136). A programmatic connector with no\n // id records nothing and never appears on that endpoint.\n connectorId?: string\n}\n\nconst DEFAULT_POLL_INTERVAL_MS = 60_000\n\n/**\n * Recurring wrapper around `runConnectorPoll` — the same setInterval +\n * unref + try/catch + stop-closure shape `startStalenessLoop` (ingest.ts)\n * already uses for the daemon's per-project background tasks, reused here\n * rather than reinvented. `daemon.ts` wires this in exactly where it wires\n * `startStalenessLoop`, tearing both down together per project slot.\n *\n * Advances `since` to the tick's own start time after every successful\n * poll — the next tick asks the provider for \"since last tick\" rather than\n * replaying. A tick that throws logs and leaves `since` where it was, so a\n * transient provider outage doesn't silently skip the gap once it recovers.\n */\nexport function startConnectorPollLoop(\n connector: ObservedConnector,\n ctx: ConnectorContext,\n graph: NeatGraph,\n resolveTarget: ResolveConnectorTarget,\n options: ConnectorPollLoopOptions = {},\n): () => void {\n let stopped = false\n let since = ctx.since\n const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS\n const connectorId = options.connectorId\n const onError =\n options.onError ??\n ((err: unknown) => console.error(`[neatd] connector poll failed (${connector.provider})`, err))\n\n const tick = (): void => {\n if (stopped) return\n void (async () => {\n const tickStartedAt = new Date().toISOString()\n try {\n const result = await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget)\n since = tickStartedAt\n // Record the successful tick for the status endpoint (ADR-136). This is\n // additive to the poll — it never changes what the tick mints or how\n // `since` advances.\n if (connectorId) {\n recordConnectorPoll(connectorId, {\n outcome: 'ok',\n at: tickStartedAt,\n signalsLastPoll: result.signalCount,\n })\n }\n } catch (err) {\n onError(err)\n // The failed tick becomes a queryable fact instead of only a log line.\n // `sanitizePollError` keeps the recorded message short and secret-free.\n if (connectorId) {\n recordConnectorPoll(connectorId, {\n outcome: 'error',\n at: tickStartedAt,\n error: sanitizePollError(err),\n })\n }\n }\n })()\n }\n\n // Poll once immediately, then on the interval — a freshly-added connector\n // produces OBSERVED data and a queryable status right away instead of sitting\n // idle until the first interval elapses (#871: an operator watched an idle\n // connector for minutes with no signal it was even scheduled).\n tick()\n const interval = setInterval(tick, intervalMs)\n if (typeof interval.unref === 'function') interval.unref()\n return () => {\n stopped = true\n clearInterval(interval)\n }\n}\n\n/**\n * One project's registered connector, ready for `daemon.ts` to poll on an\n * interval. Deliberately thin — no config-loading or credential-broker logic\n * lives here (that's provider- and profile-specific, later work per\n * docs/contracts/connectors.md §3); this is just the seam a daemon slot\n * wires a connector through.\n */\nexport interface ConnectorRegistration {\n // The config-entry id this registration was built from, when it came from\n // ~/.neat/connectors.json (ADR-130). The daemon threads it into the poll loop\n // so every tick is recorded per-id for the connector-status endpoint\n // (ADR-136). Absent for a programmatic registration a caller passes directly.\n id?: string\n connector: ObservedConnector\n credentials: Record<string, unknown>\n resolveTarget: ResolveConnectorTarget\n intervalMs?: number\n}\n","// The connector junction (ADR-131, docs/contracts/connectors.md). Every\n// connector's outbound call — `railway/client.ts`, `firebase/logging-api.ts`,\n// `cloudflare/client.ts`, `supabase/client.ts`, `supabase/postgres-client.ts`\n// — routes through `junctionFetch` (HTTP) or `dbJunction` (the Supabase\n// `pg` path) instead of its own bare `fetch()`/`pg` query. Before this\n// module, each connector reinvented the same thin, unprotected wrapper: no\n// timeout, no retry, no rate-limiting, no shared credential-header\n// convention, no shared outbound-health logging. None of that is a\n// per-provider concern — it's the same discipline every outbound call needs,\n// which is exactly why ADR-131 makes the outbound connection itself a\n// junction, the same way `connectors/index.ts` is already the one junction\n// every provider's inbound signal converges through on its way into the\n// graph.\n//\n// Passive/ambient discipline (connectors.md §2) still holds: this module\n// never issues a call a connector didn't already decide to make. What it\n// adds is what happens *around* a call a connector was always going to\n// make — bounding it in time, backing off instead of hammering a transient\n// failure, and self-throttling per customer account so a retry storm can\n// never look like the load-generation that principle forbids, even under\n// retry.\n\n// ── shared primitives: token-bucket rate limiting ──────────────────────────\n\n/**\n * A token bucket's shape: `capacity` tokens available as burst, refilling\n * one token every `refillMs`. Keyed on `(provider, accountKey)` — never\n * global — so one customer's aggressive polling can never throttle\n * another's (ADR-131 decision #1's rate-limiting clause).\n */\nexport interface TokenBucketConfig {\n /** Maximum tokens the bucket can hold — the burst allowance. */\n capacity: number\n /** Milliseconds to refill exactly one token. */\n refillMs: number\n}\n\ninterface TokenBucketState extends TokenBucketConfig {\n tokens: number\n updatedAt: number\n}\n\n// module-level — the one process-wide set of buckets every connector's call\n// shares, keyed per (provider, accountKey) exactly as ADR-131 specifies.\nconst buckets = new Map<string, TokenBucketState>()\n\nfunction bucketMapKey(provider: string, accountKey: string): string {\n return `${provider}\\x00${accountKey}`\n}\n\nfunction getBucket(provider: string, accountKey: string, config: TokenBucketConfig): TokenBucketState {\n const key = bucketMapKey(provider, accountKey)\n const existing = buckets.get(key)\n if (existing && existing.capacity === config.capacity && existing.refillMs === config.refillMs) {\n return existing\n }\n // First sighting of this (provider, accountKey) pair, or a call-site\n // override changed the bucket's shape — start a fresh, full bucket rather\n // than reinterpreting old token state under a new capacity/refill rate.\n const fresh: TokenBucketState = { ...config, tokens: config.capacity, updatedAt: Date.now() }\n buckets.set(key, fresh)\n return fresh\n}\n\nfunction refillBucket(bucket: TokenBucketState, now: number): void {\n if (now <= bucket.updatedAt) return\n const elapsed = now - bucket.updatedAt\n const grant = elapsed / bucket.refillMs\n if (grant <= 0) return\n bucket.tokens = Math.min(bucket.capacity, bucket.tokens + grant)\n bucket.updatedAt = now\n}\n\n/**\n * Thrown when a bucket is exhausted and waiting for the next token would\n * exceed the call's remaining wall-clock budget — the bounded-wait counterpart\n * to blocking indefinitely. A connector's own poll tick (60s by default,\n * connectors/index.ts's DEFAULT_POLL_INTERVAL_MS) always has slack for a\n * short, self-imposed throttle wait; this only fires when the bucket is so\n * depleted that waiting would itself become the unbounded hang ADR-131's\n * wall-clock budget exists to prevent.\n */\nexport class RateLimitExceededError extends Error {\n constructor(provider: string, accountKey: string) {\n super(\n `junction: rate limit exceeded for ${provider}:${accountKey} — waiting for the next token would exceed this call's wall-clock budget`,\n )\n this.name = 'RateLimitExceededError'\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms)\n if (typeof timer.unref === 'function') timer.unref()\n })\n}\n\n/**\n * Blocks until the (provider, accountKey) bucket holds a token, or throws\n * `RateLimitExceededError` when the wait would blow the remaining budget.\n * Every retry re-acquires a token here too (not just the first attempt) —\n * the per-account bucket has to hold under retry for the ambient/passive\n * principle to survive a transient-failure storm (ADR-131 consequences).\n */\nasync function acquireToken(\n provider: string,\n accountKey: string,\n config: TokenBucketConfig,\n remainingBudgetMs: number,\n): Promise<number> {\n const bucket = getBucket(provider, accountKey, config)\n refillBucket(bucket, Date.now())\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1\n return 0\n }\n const waitMs = Math.ceil((1 - bucket.tokens) * bucket.refillMs)\n if (waitMs > remainingBudgetMs) {\n throw new RateLimitExceededError(provider, accountKey)\n }\n await delay(waitMs)\n refillBucket(bucket, Date.now())\n bucket.tokens = Math.max(0, bucket.tokens - 1)\n return waitMs\n}\n\n/**\n * Test-only seam — production code never calls this. Mirrors ingest.ts's\n * `resetUnidentifiedSpanWarnings`/`resetNoSourceMapWarnings` pattern: clears\n * every provider/account bucket so each test starts from a fresh, full\n * bucket instead of bleeding state across cases sharing this module.\n */\nexport function resetJunctionRateLimiters(): void {\n buckets.clear()\n}\n\n// ── per-provider default bucket sizes ───────────────────────────────────────\n//\n// ADR-131's Phase 1 surveys found exactly one hard number: Cloudflare's\n// Telemetry Query API at ~300 requests / 5 minutes. Railway's GraphQL API,\n// Firebase's Cloud Logging `entries.list`, and Supabase's Management API\n// each have a real limit the docs surveyed couldn't pin down — every one of\n// docs/connectors/{railway,firebase,supabase}.md flags its own rate limit as\n// \"needs-endpoint-testing\" / \"unconfirmed\" rather than citing a number. Those\n// three therefore get the same conservative placeholder bucket, not three\n// independently-guessed numbers dressed up as if they were documented —\n// tightened once a live project confirms the real ceiling for each,\n// exactly the discipline those docs already ask of every other unconfirmed\n// surface. `supabase-postgres` is different again: pg_stat_statements is a\n// raw Postgres connection, not a rate-limited REST API, so its bucket is a\n// self-imposed ceiling against a retry storm hammering the customer's own\n// database, not a provider-documented cap.\nexport const JUNCTION_DEFAULT_RATE_LIMITS: Record<string, TokenBucketConfig> = {\n // ~300 requests / 5 minutes (ADR-131). Burst capacity holds a third of\n // that ceiling; steady-state refill (1 token / 3s = 20/min = 100/5min)\n // stays well clear of the documented limit even under sustained polling.\n cloudflare: { capacity: 100, refillMs: 3_000 },\n // Placeholder pending a live project confirming the real cap\n // (docs/connectors/railway.md: \"does not appear to publish one as of this\n // writing\").\n railway: { capacity: 30, refillMs: 10_000 },\n // Placeholder pending a live rate-limit check (docs/connectors/\n // firebase.md: \"needs-endpoint-testing against entries.list's live rate\n // limits\").\n firebase: { capacity: 30, refillMs: 10_000 },\n // Placeholder pending a live rate-limit check (docs/connectors/supabase.md:\n // \"the documented rate limit for this specific endpoint is unconfirmed\").\n supabase: { capacity: 30, refillMs: 10_000 },\n // Not a documented API limit at all — a self-imposed ceiling on the raw\n // pg_stat_statements connection (see module header above).\n 'supabase-postgres': { capacity: 20, refillMs: 3_000 },\n // A direct pg_stat_statements read. This is a self-imposed ceiling on the\n // customer's Neon compute, not a published management-API limit.\n neon: { capacity: 20, refillMs: 3_000 },\n // Push provider (ADR-146): the Drains REST API is touched only by `neat\n // connector add/remove/test` (provision/deprovision/validate), never a poll\n // loop, so this bucket is exercised a handful of times per command. Kept\n // conservative pending a documented Drains-API rate limit.\n vercel: { capacity: 20, refillMs: 5_000 },\n // Render's REST API (api-docs.render.com/reference/rate-limiting) isn't\n // pinned here to a single confirmed number — this is a conservative\n // placeholder pending a live project, matching the other pull providers.\n render: { capacity: 30, refillMs: 10_000 },\n}\n\n// Fallback for any provider not named above (a future connector that hasn't\n// had its own bucket sized yet) — conservative, not tuned to any specific\n// provider's documented limit.\nconst JUNCTION_GENERIC_RATE_LIMIT: TokenBucketConfig = { capacity: 20, refillMs: 5_000 }\n\nfunction defaultRateLimitFor(provider: string): TokenBucketConfig {\n return JUNCTION_DEFAULT_RATE_LIMITS[provider] ?? JUNCTION_GENERIC_RATE_LIMIT\n}\n\n// ── shared primitives: backoff + outbound-health logging ──────────────────\n\nexport const JUNCTION_DEFAULT_TIMEOUT_MS = 10_000\nexport const JUNCTION_DEFAULT_MAX_ATTEMPTS = 3\nexport const JUNCTION_DEFAULT_INITIAL_BACKOFF_MS = 200\nexport const JUNCTION_DEFAULT_BACKOFF_MULTIPLIER = 4\n// 30s — comfortably under connectors/index.ts's 60s DEFAULT_POLL_INTERVAL_MS,\n// so even a fully-retried call (3 attempts, each up to the 10s timeout, plus\n// backoff) can never make one poll tick pile up on the next.\nexport const JUNCTION_DEFAULT_MAX_ELAPSED_MS = 30_000\nexport const JUNCTION_DEFAULT_DB_TIMEOUT_MS = 10_000\n\nasync function backoff(\n attempt: number,\n initialBackoffMs: number,\n backoffMultiplier: number,\n remainingBudgetMs: number,\n): Promise<void> {\n // attempt 1 -> initialBackoffMs (200ms default), attempt 2 ->\n // initialBackoffMs * multiplier (800ms default), ... — exponential,\n // capped so a retry's own wait never overruns what's left of the call's\n // wall-clock budget.\n const raw = initialBackoffMs * backoffMultiplier ** (attempt - 1)\n const capped = Math.max(0, Math.min(raw, remainingBudgetMs))\n await delay(capped)\n}\n\n// The four outcomes ADR-131 names, plus `failed` for the single-attempt,\n// never-retried case (a 4xx, or a first attempt that used up the whole\n// budget) — a superset of the ADR's list, not a departure from it.\nexport type JunctionOutcome = 'success' | 'retried-then-succeeded' | 'retried-then-failed' | 'rate-limited' | 'failed'\n\n// Cloud Logging's / the Management API's own query strings carry real\n// content in the URL (Supabase's `sql=` query param, in particular) —\n// logging is stripped to origin + pathname so an outbound-health line never\n// echoes a credential or a query body back into stderr.\nfunction safeUrlLabel(url: string | URL): string {\n try {\n const u = typeof url === 'string' ? new URL(url) : url\n return `${u.origin}${u.pathname}`\n } catch {\n return String(url)\n }\n}\n\n// Structured outbound-health logging through the same mechanism the rest of\n// NEAT already uses for a connector's own diagnostics — console.log/warn/error\n// with a bracketed prefix (connectors/index.ts's `[neatd] connector poll\n// failed (...)`, ingest.ts's `[neat] ...` / `[neatd] ...` lines). Not a new\n// logging library — this is what a future `neat connector list --verbose`\n// reads stderr/stdout for.\nfunction logOutcome(\n provider: string,\n accountKey: string,\n outcome: JunctionOutcome,\n method: string,\n label: string,\n attempt: number,\n startedAt: number,\n): void {\n const elapsedMs = Date.now() - startedAt\n const line = `[neat connector] ${provider}:${accountKey} ${method} ${label} — ${outcome} (attempt ${attempt}, ${elapsedMs}ms)`\n if (outcome === 'success' || outcome === 'retried-then-succeeded') {\n console.log(line)\n } else if (outcome === 'rate-limited') {\n console.warn(line)\n } else {\n console.error(line)\n }\n}\n\n// ── credential injection ────────────────────────────────────────────────────\n\n/**\n * The one credential shape every connector so far shares (Railway, Firebase,\n * Cloudflare, and Supabase's Management API all carry `Authorization: Bearer\n * <token>` — see each provider's client.ts). Cuts the four near-identical\n * \"build the auth header\" blocks down to one (ADR-131 decision #1).\n */\nexport function bearerAuthHeader(token: string): { Authorization: string } {\n return { Authorization: `Bearer ${token}` }\n}\n\n// ── junctionFetch ───────────────────────────────────────────────────────────\n\nexport interface JunctionPolicy {\n /** 'railway' | 'firebase' | 'cloudflare' | 'supabase' | ... — half of the rate-limit bucket key and every outbound-health log line. */\n provider: string\n /**\n * Whatever identifies one customer's account to this provider — a Supabase\n * project ref, a Railway environment id, a Cloudflare account id, a GCP\n * project id for Firebase (ADR-131). An identifier, never a secret itself\n * — safe to log (contracts.md §6 governs the credential, not this).\n */\n accountKey: string\n /** AbortController timeout per attempt, ms. Default 10s. */\n timeoutMs?: number\n /** Total attempts including the first, before giving up. Default 3. */\n maxAttempts?: number\n /** Wall-clock budget for the whole call (all attempts + backoff + rate-limit waits), ms. Default 30s. */\n maxElapsedMs?: number\n /** First retry's backoff delay, ms. Default 200. */\n initialBackoffMs?: number\n /** Backoff growth factor per retry. Default 4 (200ms, 800ms, ...). */\n backoffMultiplier?: number\n /** Per-(provider, accountKey) token bucket. Defaults to this provider's entry in JUNCTION_DEFAULT_RATE_LIMITS. */\n rateLimit?: TokenBucketConfig\n /** Dependency-injection seam for tests — defaults to the platform global `fetch`, read at call time so tests can stub `globalThis.fetch` per call. */\n fetchImpl?: typeof fetch\n}\n\n/**\n * The one function every connector's outbound HTTP call goes through\n * (ADR-131). Times out and retries a transient failure with backoff, never\n * retries a 4xx, self-throttles per `(provider, accountKey)` even across\n * retries, and logs the call's outcome. Mirrors a bare `fetch()`'s contract\n * closely on purpose: on success *or* a non-retryable failure, this returns\n * the `Response` as-is — the caller's own `if (!res.ok) throw ...` still\n * constructs the same error it always did. This is a transport-layer\n * wrapper, not a second place that decides what a failed call means.\n */\nexport async function junctionFetch(url: string | URL, init: RequestInit = {}, policy: JunctionPolicy): Promise<Response> {\n const {\n provider,\n accountKey,\n timeoutMs = JUNCTION_DEFAULT_TIMEOUT_MS,\n maxAttempts = JUNCTION_DEFAULT_MAX_ATTEMPTS,\n maxElapsedMs = JUNCTION_DEFAULT_MAX_ELAPSED_MS,\n initialBackoffMs = JUNCTION_DEFAULT_INITIAL_BACKOFF_MS,\n backoffMultiplier = JUNCTION_DEFAULT_BACKOFF_MULTIPLIER,\n rateLimit = defaultRateLimitFor(provider),\n fetchImpl = fetch,\n } = policy\n\n const method = (init.method ?? 'GET').toUpperCase()\n const label = safeUrlLabel(url)\n const startedAt = Date.now()\n let attempt = 0\n let sawRetry = false\n\n for (;;) {\n attempt++\n const remainingBudget = maxElapsedMs - (Date.now() - startedAt)\n if (remainingBudget <= 0) {\n logOutcome(provider, accountKey, 'retried-then-failed', method, label, attempt - 1, startedAt)\n throw new Error(\n `junction: ${provider}:${accountKey} ${method} ${label} exceeded its wall-clock budget (${maxElapsedMs}ms) after ${attempt - 1} attempt(s)`,\n )\n }\n\n try {\n await acquireToken(provider, accountKey, rateLimit, remainingBudget)\n } catch (err) {\n if (err instanceof RateLimitExceededError) {\n logOutcome(provider, accountKey, 'rate-limited', method, label, attempt, startedAt)\n }\n throw err\n }\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n if (typeof timer.unref === 'function') timer.unref()\n\n try {\n const res = await fetchImpl(url, { ...init, signal: controller.signal })\n clearTimeout(timer)\n\n if (res.ok || res.status < 500) {\n // Success, or a non-retryable failure (4xx, or anything outside the\n // 5xx retry class) — never retried (ADR-131: \"a 4xx never retries\").\n logOutcome(provider, accountKey, sawRetry ? 'retried-then-succeeded' : 'success', method, label, attempt, startedAt)\n return res\n }\n\n // 5xx — transient by assumption, retry with backoff if attempts/budget remain.\n if (attempt >= maxAttempts) {\n logOutcome(provider, accountKey, sawRetry ? 'retried-then-failed' : 'failed', method, label, attempt, startedAt)\n return res\n }\n sawRetry = true\n await backoff(attempt, initialBackoffMs, backoffMultiplier, maxElapsedMs - (Date.now() - startedAt))\n } catch (err) {\n clearTimeout(timer)\n // Anything caught here is the fetch call itself failing — a network\n // error or this junction's own timeout abort — the same \"network\n // errors, and timeout\" retry class ADR-131 names alongside 5xx.\n if (attempt >= maxAttempts) {\n logOutcome(provider, accountKey, sawRetry ? 'retried-then-failed' : 'failed', method, label, attempt, startedAt)\n throw err\n }\n sawRetry = true\n await backoff(attempt, initialBackoffMs, backoffMultiplier, maxElapsedMs - (Date.now() - startedAt))\n }\n }\n}\n\n// ── dbJunction ───────────────────────────────────────────────────────────────\n\nexport interface DbJunctionPolicy {\n /** Provider name for the rate-limit bucket and log lines — e.g. 'supabase-postgres'. */\n provider: string\n /** Whatever identifies one customer's account/project — the pg-path analog of JunctionPolicy.accountKey. */\n accountKey: string\n /** Soft timeout, ms. Default 10s. See DbJunctionTimeoutError's doc comment for what \"soft\" means here. */\n timeoutMs?: number\n maxAttempts?: number\n maxElapsedMs?: number\n initialBackoffMs?: number\n backoffMultiplier?: number\n rateLimit?: TokenBucketConfig\n}\n\n/**\n * A `pg` query has no portable, version-safe cancel-on-timeout the way\n * `AbortController` gives `fetch` (node-postgres's own cancel path is a\n * separate wire message, not a drop-in `signal` option on `Client#query` for\n * the version range this workspace pins — see postgres-client.ts's header\n * comment on why `pg`'s default import is used at all). `dbJunction`'s\n * timeout is therefore \"stop waiting and let the caller retry with a fresh\n * connection,\" not \"cancel the in-flight query on the wire\" — stated\n * honestly here rather than implying a cancellation guarantee this\n * dependency doesn't give.\n */\nexport class DbJunctionTimeoutError extends Error {\n constructor(ms: number) {\n super(`junction: db query exceeded its ${ms}ms timeout`)\n this.name = 'DbJunctionTimeoutError'\n }\n}\n\nfunction withTimeout<T>(run: () => Promise<T>, timeoutMs: number): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new DbJunctionTimeoutError(timeoutMs)), timeoutMs)\n if (typeof timer.unref === 'function') timer.unref()\n run().then(\n (value) => {\n clearTimeout(timer)\n resolve(value)\n },\n (err) => {\n clearTimeout(timer)\n reject(err)\n },\n )\n })\n}\n\n// SQLSTATE classes/codes that mean \"transient — try again\": connection\n// exception (08xxx) and cannot_connect_now (57P03), plus Node's own\n// socket-level error codes for a connection that never got established at\n// all. Everything else — auth failure (28xxx), insufficient privilege\n// (42501), syntax error (42601) — is this surface's equivalent of a 4xx: a\n// bad credential or a malformed query is not a transient condition\n// (ADR-131), so it's never retried.\nconst RETRYABLE_NODE_ERROR_CODES = new Set(['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'EHOSTUNREACH', 'EAI_AGAIN', 'EPIPE'])\n\nfunction isRetryableDbError(err: unknown): boolean {\n if (err instanceof DbJunctionTimeoutError) return true\n const code = (err as { code?: unknown } | undefined)?.code\n if (typeof code !== 'string') return false\n if (code.startsWith('08') || code === '57P03') return true\n return RETRYABLE_NODE_ERROR_CODES.has(code)\n}\n\n/**\n * The `pg`-path counterpart to `junctionFetch` (ADR-131 decision #2) —\n * same retry/backoff and per-`(provider, accountKey)` rate-limit primitives,\n * adapted to a `run()` thunk (postgres-client.ts's connect/query/end\n * sequence) instead of a `fetch` call. Each retry re-opens its own\n * connection via `run()` — it never assumes the failed attempt's socket is\n * safely reusable.\n */\nexport async function dbJunction<T>(run: () => Promise<T>, policy: DbJunctionPolicy): Promise<T> {\n const {\n provider,\n accountKey,\n timeoutMs = JUNCTION_DEFAULT_DB_TIMEOUT_MS,\n maxAttempts = JUNCTION_DEFAULT_MAX_ATTEMPTS,\n maxElapsedMs = JUNCTION_DEFAULT_MAX_ELAPSED_MS,\n initialBackoffMs = JUNCTION_DEFAULT_INITIAL_BACKOFF_MS,\n backoffMultiplier = JUNCTION_DEFAULT_BACKOFF_MULTIPLIER,\n rateLimit = defaultRateLimitFor(provider),\n } = policy\n\n const startedAt = Date.now()\n let attempt = 0\n let sawRetry = false\n\n for (;;) {\n attempt++\n const remainingBudget = maxElapsedMs - (Date.now() - startedAt)\n if (remainingBudget <= 0) {\n logOutcome(provider, accountKey, 'retried-then-failed', 'QUERY', 'db', attempt - 1, startedAt)\n throw new Error(\n `junction: ${provider}:${accountKey} db query exceeded its wall-clock budget (${maxElapsedMs}ms) after ${attempt - 1} attempt(s)`,\n )\n }\n\n try {\n await acquireToken(provider, accountKey, rateLimit, remainingBudget)\n } catch (err) {\n if (err instanceof RateLimitExceededError) {\n logOutcome(provider, accountKey, 'rate-limited', 'QUERY', 'db', attempt, startedAt)\n }\n throw err\n }\n\n try {\n const result = await withTimeout(run, timeoutMs)\n logOutcome(provider, accountKey, sawRetry ? 'retried-then-succeeded' : 'success', 'QUERY', 'db', attempt, startedAt)\n return result\n } catch (err) {\n if (!isRetryableDbError(err) || attempt >= maxAttempts) {\n logOutcome(provider, accountKey, sawRetry ? 'retried-then-failed' : 'failed', 'QUERY', 'db', attempt, startedAt)\n throw err\n }\n sawRetry = true\n await backoff(attempt, initialBackoffMs, backoffMultiplier, maxElapsedMs - (Date.now() - startedAt))\n }\n }\n}\n","// Management API log-query client (docs/connectors/supabase.md §Surfaces\n// used #1). Read-only, ambient — this issues one query per poll tick against\n// telemetry Supabase already collects for every Cloud project (edge_logs),\n// never a synthetic request against the project itself (connectors.md §2).\n//\n// GET https://api.supabase.com/v1/projects/{ref}/analytics/endpoints/logs.all\n// — confirmed live against api.supabase.com/api/v1-json (operationId\n// `v1-get-project-logs-all`) during this connector's build: a GET request\n// with `sql` / `iso_timestamp_start` / `iso_timestamp_end` as query-string\n// parameters, not a POST with a JSON body. The endpoint is marked\n// `deprecated: true` in that spec but remains the documented, currently\n// reachable surface — see types.ts's header comment for the full citation\n// list and the open dialect question this connector's SQL still needs\n// live-project confirmation against.\n\nimport { bearerAuthHeader, junctionFetch } from '../junction.js'\nimport type {\n SupabaseConnectorConfig,\n SupabaseEdgeLogRow,\n SupabaseLogsAllResponse,\n} from './types.js'\n\nexport const DEFAULT_SUPABASE_MANAGEMENT_API_URL = 'https://api.supabase.com'\n\n// supabase.com/docs/guides/telemetry/logs's \"LIMIT and result row limitations\"\n// section: \"The Logs Explorer has a maximum of 1000 rows per run.\"\nexport const DEFAULT_LOG_LIMIT = 1000\n\n// The OpenAPI description for this endpoint: \"The timestamp range must be no\n// more than 24 hours and is rounded to the nearest minute. If the range is\n// more than 24 hours, a validation error will be thrown.\" A hard provider\n// ceiling, not merely a connector default — bounds `boundedSupabaseLogWindow`\n// below regardless of `config.maxLookbackMs`.\nexport const SUPABASE_LOG_QUERY_MAX_WINDOW_MS = 24 * 60 * 60 * 1000\n\n/**\n * `since` bounded by the provider's max lookback window (docs/contracts/\n * connectors.md §\"Poll cadence and backfill\") — a gap wider than the window\n * (a laptop off for a week) backfills from `now - window`, never an\n * unbounded full-history replay. `truncated` is true whenever the effective\n * start got clipped to the window floor rather than reflecting `since`\n * verbatim, so callers can log the gap being lossily capped rather than\n * silently swallowing it.\n */\nexport function boundedSupabaseLogWindow(\n since: string | undefined,\n now: Date,\n maxLookbackMs: number,\n): { startIso: string; endIso: string; truncated: boolean } {\n const window = Math.min(maxLookbackMs, SUPABASE_LOG_QUERY_MAX_WINDOW_MS)\n const floor = new Date(now.getTime() - window)\n const endIso = now.toISOString()\n if (!since) return { startIso: floor.toISOString(), endIso, truncated: false }\n const sinceMs = new Date(since).getTime()\n if (Number.isNaN(sinceMs)) return { startIso: floor.toISOString(), endIso, truncated: false }\n if (sinceMs < floor.getTime()) return { startIso: floor.toISOString(), endIso, truncated: true }\n return { startIso: new Date(sinceMs).toISOString(), endIso, truncated: false }\n}\n\n// The flat projection this connector needs out of edge_logs's nested\n// metadata.request/metadata.response arrays — field names confirmed against\n// worked examples on supabase.com/docs/guides/telemetry/logs,\n// .../advanced-log-filtering, and\n// .../troubleshooting/discovering-and-interpreting-api-errors-in-the-logs-7xREI9\n// (all fetched during this build). `regexp_contains(request.path, ...)`\n// narrows to the PostgREST surface (`/rest/v1/...`) this connector's fusion\n// targets — Auth/Storage/Realtime/Functions traffic on the same project is\n// out of scope (supabase.md §Out of scope) and excluded here rather than\n// fetched and dropped client-side. `FORMAT_TIMESTAMP` renders a strict\n// ISO8601 UTC string so map.ts never has to guess a timezone.\nfunction buildEdgeLogsQuery(limit: number): string {\n const safeLimit = Math.max(1, Math.trunc(limit) || DEFAULT_LOG_LIMIT)\n return [\n 'select',\n \" format_timestamp('%Y-%m-%dT%H:%M:%E6SZ', timestamp) as timestamp,\",\n ' request.method as method,',\n ' request.path as path,',\n ' response.status_code as status_code',\n 'from edge_logs',\n 'cross join unnest(metadata) as metadata',\n 'cross join unnest(metadata.request) as request',\n 'cross join unnest(metadata.response) as response',\n \"where regexp_contains(request.path, '^/rest/v1/')\",\n 'order by timestamp asc',\n `limit ${safeLimit}`,\n ].join('\\n')\n}\n\nfunction logsAllHttpFailureMessage(status: number): string {\n if (status === 401 || status === 403) {\n return (\n `supabase connector: logs.all request rejected (HTTP ${status}). ` +\n 'Check the Management API token, its analytics read scope, and --api-project-ref.'\n )\n }\n if (status === 404) {\n return 'supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.'\n }\n if (status === 429) {\n return (\n 'supabase connector: logs.all rate-limited (HTTP 429). ' +\n 'The connector will retry on the next poll; reduce poll cadence if this persists.'\n )\n }\n if (status === 400) {\n return (\n 'supabase connector: logs.all query rejected (HTTP 400). ' +\n 'Provider details redacted; confirm the live log-query dialect before shipping this connector.'\n )\n }\n return `supabase connector: logs.all request failed (HTTP ${status}); provider details redacted.`\n}\n\nfunction providerErrorDetails(error: SupabaseLogsAllResponse['error']): string {\n if (!error || typeof error === 'string') return ''\n const parts: string[] = []\n if (typeof error.code === 'number') parts.push(`code ${error.code}`)\n if (typeof error.status === 'string' && /^[A-Z0-9_.-]+$/.test(error.status)) {\n parts.push(`status ${error.status}`)\n }\n return parts.length > 0 ? ` (${parts.join(', ')})` : ''\n}\n\nexport async function fetchSupabaseEdgeLogs(\n config: SupabaseConnectorConfig,\n token: string,\n startIso: string,\n endIso: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<SupabaseEdgeLogRow[]> {\n const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL\n const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`)\n url.searchParams.set('sql', buildEdgeLogsQuery(config.logLimit ?? DEFAULT_LOG_LIMIT))\n url.searchParams.set('iso_timestamp_start', startIso)\n url.searchParams.set('iso_timestamp_end', endIso)\n\n const res = await junctionFetch(\n url,\n { method: 'GET', headers: bearerAuthHeader(token) },\n // accountKey: the Supabase project ref (ADR-131's own worked example) —\n // the Management API's rate limit is enforced per project.\n { provider: 'supabase', accountKey: config.apiProjectRef, fetchImpl },\n )\n if (!res.ok) {\n throw new Error(logsAllHttpFailureMessage(res.status))\n }\n let body: SupabaseLogsAllResponse\n try {\n body = (await res.json()) as SupabaseLogsAllResponse\n } catch {\n throw new Error('supabase connector: logs.all returned invalid JSON; provider details redacted.')\n }\n if (body.error) {\n throw new Error(\n `supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`,\n )\n }\n return body.result ?? []\n}\n","// Supabase connector — provider-specific types (docs/connectors/supabase.md,\n// ADR-124). First connectors-plane provider; the shared pull/map/fuse\n// scaffold (connectors/index.ts) and the three sibling providers (Railway,\n// Firebase, Cloudflare) already landed on main — this module follows their\n// fetch/map/resolve split.\n//\n// Scope per supabase.md: Supabase Cloud only, two surfaces —\n// 1. the Management API's log-query endpoint over edge_logs (table/RPC\n// grain from the REST request path, both credential profiles)\n// 2. a direct, read-only Postgres connection reading pg_stat_statements\n// (local profile from day one; hosted is a fast-follow pending a\n// customer-provisioned least-privilege role — supabase.md §Scope)\n//\n// Every field name below was confirmed against Supabase's own live docs\n// during this connector's build, not recalled from training data:\n// - endpoint + request/response envelope (GET, not POST — confirmed\n// against the live OpenAPI spec at https://api.supabase.com/api/v1-json,\n// operationId `v1-get-project-logs-all`, `deprecated: true` but still the\n// documented and currently-working surface): query params `sql` /\n// `iso_timestamp_start` / `iso_timestamp_end`, response `{ result, error }`\n// - edge_logs row shape (`metadata[].request.{method,path}`,\n// `metadata[].response.status_code`): supabase.com/docs/guides/telemetry/logs,\n// .../advanced-log-filtering, and\n// .../troubleshooting/discovering-and-interpreting-api-errors-in-the-logs-7xREI9\n// (all fetched during this build — each shows a worked `cross join\n// unnest(...)` query selecting exactly these fields)\n// - 24h window cap + 1000-row cap: the logs guide's own \"LIMIT and result\n// row limitations\" section (\"a maximum of 1000 rows per run\") and the\n// OpenAPI description's \"timestamp range must be no more than 24 hours\"\n// - pg_stat_statements column names: postgresql.org/docs/current/pgstatstatements.html\n// Anything not directly confirmed by those pages is flagged inline as\n// needs-endpoint-testing, the same discipline railway/types.ts uses.\n\n/**\n * `ConnectorContext.credentials` shape for this provider. Both fields are\n * genuine secrets (contracts.md §6 — never logged, never written to a node\n * or edge, never reaches the snapshot):\n *\n * - `managementToken` — a bearer token for the Management API's log-query\n * surface. The connector doesn't need to know which kind: a developer's own\n * personal access token (`sbp_...`) locally, or an OAuth-app token for the\n * hosted profile. The exact OAuth scope that gates the `logs.all` endpoint is\n * NOT confirmed (#792): Supabase's public OAuth taxonomy is resource-level\n * (`projects`, `database`, `rest`, … each `:read`/`:write`) with no\n * analytics- or logs-specific scope, so the log query most likely rides\n * `projects:read` rather than any `analytics:read`. Treat the precise gating\n * scope as needs-endpoint-testing against a live OAuth token — a personal\n * access token sidesteps the question locally either way.\n * - `postgresConnectionString` — optional. Present only when this connector\n * instance should also poll pg_stat_statements (the local profile, which\n * already holds a full database credential for its own project). Its\n * absence is exactly how the hosted profile's \"log-surface-only\" first cut\n * (supabase.md §Scope) falls out of this same code path, with no\n * profile-conditional branch in the connector logic itself (connectors.md\n * §3 — profile changes credential source, never pull/map/fuse logic).\n */\nexport interface SupabaseCredentials {\n managementToken: string\n postgresConnectionString?: string\n}\n\nexport function readSupabaseCredentials(raw: Record<string, unknown>): SupabaseCredentials {\n const managementToken = raw['managementToken']\n if (typeof managementToken !== 'string' || managementToken.length === 0) {\n throw new Error('supabase connector: credentials.managementToken must be a non-empty string')\n }\n const postgresConnectionString = raw['postgresConnectionString']\n if (\n postgresConnectionString !== undefined &&\n (typeof postgresConnectionString !== 'string' || postgresConnectionString.length === 0)\n ) {\n throw new Error(\n 'supabase connector: credentials.postgresConnectionString must be a non-empty string when present',\n )\n }\n return {\n managementToken,\n ...(postgresConnectionString ? { postgresConnectionString } : {}),\n }\n}\n\n/**\n * Config resolved once at connector setup (never re-derived from a response\n * at poll time, the same \"resolved once, never guessed\" discipline ADR-127\n * states for Railway's serviceNameById).\n */\nexport interface SupabaseConnectorConfig {\n // The real Supabase project ref — a 20-character lowercase string\n // (confirmed against the live OpenAPI path-parameter schema:\n // `minLength: 20, maxLength: 20, pattern: \"^[a-z]+$\"`) — used as the `{ref}`\n // path segment calling the Management API. This is NOT necessarily the same\n // string as `nodeRef` below; see that field's own doc comment for why the\n // two are kept separate.\n apiProjectRef: string\n // The exact node-identity token this project resolves to under\n // `extract/calls/supabase.ts`'s own scheme (supabase.md §Fusion): either\n // the literal `*.supabase.co` host from a `createClient(...)` call\n // (`\"<apiProjectRef>.supabase.co\"` in the common case, since a Supabase\n // project's URL is always that shape) or the literal string `'env'` when\n // the app's code passes a non-literal URL (`process.env.SUPABASE_URL`).\n // Kept as an explicit, separate field — never derived by string-\n // concatenating `apiProjectRef` + `.supabase.co` here — because that\n // derivation would silently be wrong for the (very common) env-driven case,\n // and this identity has to match the extractor's own resolution exactly or\n // fusion never lands (identity.md — ids constructed via the shared\n // `infraId` helper, never guessed at by a second producer).\n nodeRef: string\n // The NEAT manifest service name this connector's signals attribute the\n // OBSERVED CALLS edge's source to. Supabase's own telemetry carries no\n // caller-service dimension at all on either surface (both are project- or\n // database-scoped, not per-caller), unlike Railway's per-service httpLogs\n // or Firebase's per-resource Cloud Logging entries — so there is no signal\n // to map from; this is supplied once, honestly, rather than guessed.\n serviceName: string\n // Management API base URL override, for tests. Defaults to the real host.\n managementApiUrl?: string\n // Cap on how far an absent `since` (or a gap wider than this window, e.g. a\n // laptop off for a week) backfills, ms. Hard-capped at 24h regardless of\n // this value — the Management API's own documented maximum query window\n // (supabase.md §Surfaces, confirmed against the live OpenAPI description\n // above) — a caller passing a larger value here still only ever gets a\n // last-24h query.\n maxLookbackMs?: number\n // Rows requested per log query. Defaults to 1000, the Logs Explorer's own\n // documented per-run maximum (see this file's header) — raising this above\n // 1000 has no effect since the provider caps it there regardless.\n logLimit?: number\n // Max pg_stat_statements rows read per poll (surface 2), ordered by `calls`\n // descending so the busiest statements are never starved by an unbounded\n // table falling off a smaller cap. A defensive bound, not a documented\n // Postgres limit — pg_stat_statements can grow to `pg_stat_statements.max`\n // (default 5000) distinct statements on a busy project.\n statementLimit?: number\n}\n\n// ── Surface 1: Management API log query ────────────────────────────────────\n\n// AnalyticsResponse per the live OpenAPI spec's `components.schemas` — `result`\n// is typed `array of {}` there (an intentionally untyped passthrough of\n// whatever the underlying query returns); the concrete row shape below is\n// this connector's own SQL's flat projection (see client.ts's query string),\n// not the full nested `edge_logs` row.\nexport interface SupabaseLogsAllResponse {\n result?: SupabaseEdgeLogRow[]\n error?:\n | string\n | {\n code: number\n message: string\n status: string\n errors: { domain: string; location: string; locationType: string; message: string; reason: string }[]\n }\n}\n\n/**\n * One row of this connector's own flat SQL projection over `edge_logs`\n * (client.ts) — not the raw nested log row Supabase stores (which nests\n * `request`/`response` inside a repeated `metadata` field, requiring\n * `cross join unnest(...)` to reach). `timestamp` is formatted to a strict\n * ISO8601 UTC string inside the SQL itself (`FORMAT_TIMESTAMP(...)`) so this\n * connector never has to guess a timezone client-side.\n *\n * needs-endpoint-testing: Supabase's own docs (surfaced during this build)\n * disagree with themselves on the query dialect this endpoint accepts —\n * every worked example on supabase.com/docs uses BigQuery syntax\n * (`cross join unnest`, `regexp_contains`, `cast(... as datetime)`), which is\n * what the query in client.ts is written in, but at least one indexed source\n * describes this endpoint as running \"ClickHouse SQL\" instead. Both dialects\n * support `cross join unnest`-style array flattening, but the exact function\n * names (`FORMAT_TIMESTAMP` vs a ClickHouse equivalent) could differ. Build\n * this against a live Supabase project before treating the query string as\n * locked, exactly as railway/types.ts's own needs-endpoint-testing notes ask\n * for every other unconfirmed surface in this codebase.\n */\nexport interface SupabaseEdgeLogRow {\n timestamp: string\n method: string\n path: string\n status_code: number\n}\n\n// ── Surface 2: pg_stat_statements ───────────────────────────────────────────\n\n/**\n * One row of `pg_stat_statements`, the subset this connector reads\n * (postgresql.org/docs/current/pgstatstatements.html, Table F.22). Reading\n * `query`/`queryid` for statements another role executed requires the\n * connecting role to hold `pg_read_all_stats` or superuser — exactly the\n * built-in role supabase.md §Surfaces names as available on every Supabase\n * Cloud project without needing `service_role` or the project's admin\n * `postgres` role.\n *\n * `queryid`, `calls`, and `rows` are Postgres `bigint` columns; node-postgres\n * returns `bigint` as a JS `string` by default (no custom type parser\n * configured here) to avoid silent precision loss on values past\n * `Number.MAX_SAFE_INTEGER` — map.ts's diffing logic accounts for this.\n * `total_exec_time` is `double precision`, which node-postgres does return as\n * a native JS `number`.\n */\nexport interface PgStatStatementsRow {\n queryid: string\n query: string\n calls: string\n total_exec_time: number\n rows: string\n}\n\n// Provider vocabulary for ObservedSignal.targetKind/targetName (connectors.md\n// §1 — \"the provider's own vocabulary\"; only this connector's own map.ts /\n// resolve.ts ever interpret these strings). Matches the `infraId` kind\n// literals ADR-124 §5 specifies for Supabase sub-resources.\nexport const SUPABASE_TABLE_TARGET_KIND = 'supabase-table'\nexport const SUPABASE_RPC_TARGET_KIND = 'supabase-rpc'\n","// Maps both Supabase surfaces to ObservedSignal[] (docs/connectors/\n// supabase.md §Fusion, ADR-124). Two independent producers feed the same\n// signal vocabulary (SUPABASE_TABLE_TARGET_KIND / SUPABASE_RPC_TARGET_KIND):\n//\n// 1. edge_logs rows (client.ts) — the request path names the table/RPC\n// directly (`/rest/v1/<table>` / `/rest/v1/rpc/<fn>`), so this mapping\n// is a straight parse, aggregated per (kind, name) the same bucketing\n// shape railway/connector.ts uses for httpLogs.\n// 2. pg_stat_statements rows (postgres-client.ts) — carries no table\n// column at all (postgresql.org/docs/current/pgstatstatements.html: \"the\n// view provides no table-level identification columns\"), only raw query\n// text, and its counters are lifetime cumulative, not per-poll-window —\n// so this mapping both extracts a table name from a recognized\n// PostgREST-shaped query and diffs against the previous poll's counts to\n// turn a cumulative total into this window's delta.\n\nimport { columnsFromSqlStatement } from '../../otel.js'\nimport type { ObservedSignal } from '../types.js'\nimport { SUPABASE_RPC_TARGET_KIND, SUPABASE_TABLE_TARGET_KIND, type PgStatStatementsRow, type SupabaseEdgeLogRow } from './types.js'\n\n// ── Surface 1: edge_logs → ObservedSignal[] ─────────────────────────────────\n\n// `/rest/v1/rpc/<fn>` must be checked before the bare table pattern — every\n// RPC path is also a `/rest/v1/...` path, so checking table-shape first would\n// misclassify every RPC call as a table named `rpc`.\nconst REST_RPC_PATH_RE = /^\\/rest\\/v1\\/rpc\\/([^/?]+)/\nconst REST_TABLE_PATH_RE = /^\\/rest\\/v1\\/([^/?]+)/\n\ninterface RestTarget {\n targetKind: typeof SUPABASE_TABLE_TARGET_KIND | typeof SUPABASE_RPC_TARGET_KIND\n name: string\n}\n\n// Exported for direct testing of the path-parsing rule supabase.md §Fusion\n// specifies verbatim (\"`/rest/v1/orders` → table `orders`, `/rest/v1/rpc/\n// get_totals` → RPC `get_totals`\"). Returns null for anything else this\n// connector's edge_logs query shouldn't even have returned (its own `where\n// regexp_contains(path, '^/rest/v1/')` filter already narrows to this\n// prefix) but is checked again here rather than trusting the query alone —\n// the same \"never trust the filter alone\" discipline firebase/map.ts states\n// for its own resource-type check.\nexport function targetFromRestPath(path: string): RestTarget | null {\n const rpcMatch = REST_RPC_PATH_RE.exec(path)\n if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1]! }\n const tableMatch = REST_TABLE_PATH_RE.exec(path)\n if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1]! }\n return null\n}\n\n// 5xx is the unambiguous failure threshold, the same convention firebase/\n// map.ts and cloudflare/map.ts already use for their own status-code signals\n// (a bare 4xx is often correct PostgREST behavior — a RLS-denied read, a\n// not-found row — not necessarily a service defect).\nconst ERROR_STATUS_THRESHOLD = 500\n\ninterface Bucket {\n targetKind: RestTarget['targetKind']\n targetName: string\n callCount: number\n errorCount: number\n lastObservedIso: string\n}\n\nexport function mapEdgeLogRowsToSignals(rows: SupabaseEdgeLogRow[]): ObservedSignal[] {\n const buckets = new Map<string, Bucket>()\n if (!Array.isArray(rows)) return []\n\n for (const row of rows) {\n // A shape-drifted response can carry a null/garbage slot or a row missing\n // the path/timestamp a signal needs — drop it honestly rather than\n // throwing (connectors.md §4). Timestamp is required: a signal claiming an\n // observation with no time to stand on isn't a truthful one (the same\n // `if (!timestamp) return null` firebase/map.ts already applies).\n if (!row || typeof row !== 'object') continue\n if (typeof row.path !== 'string' || typeof row.timestamp !== 'string') continue\n const target = targetFromRestPath(row.path)\n // Not a `/rest/v1/...` PostgREST path — Auth/Storage/Realtime/Functions\n // traffic on the same project, out of scope for this cut (supabase.md\n // §Out of scope). Dropped honestly, never forced through as a guessed\n // table/RPC name.\n if (!target) continue\n\n const key = `${target.targetKind}:${target.name}`\n const isError = row.status_code >= ERROR_STATUS_THRESHOLD\n const existing = buckets.get(key)\n if (existing) {\n existing.callCount += 1\n if (isError) existing.errorCount += 1\n if (row.timestamp > existing.lastObservedIso) existing.lastObservedIso = row.timestamp\n } else {\n buckets.set(key, {\n targetKind: target.targetKind,\n targetName: target.name,\n callCount: 1,\n errorCount: isError ? 1 : 0,\n lastObservedIso: row.timestamp,\n })\n }\n }\n\n return [...buckets.values()].map((b) => ({\n targetKind: b.targetKind,\n targetName: b.targetName,\n callCount: b.callCount,\n errorCount: b.errorCount,\n lastObservedIso: b.lastObservedIso,\n }))\n}\n\n// ── Surface 2: pg_stat_statements → ObservedSignal[] ────────────────────────\n\n// PostgREST always issues a fully schema-qualified, double-quoted-identifier\n// query per request (`... FROM \"public\".\"orders\" ...`, or bare `\"orders\"`\n// without a schema qualifier depending on search_path) — a recognizable,\n// mechanical shape, the same \"framework-aware, not a bare guess\" discipline\n// extract/calls/supabase.ts itself applies to a JS call site, just applied to\n// generated SQL text here instead. A query whose FROM target doesn't match\n// this shape (a user-defined Postgres function's own internal query, a\n// migration, an ORM issuing something else entirely) is dropped honestly —\n// pg_stat_statements carries no table column to fall back on\n// (postgresql.org/docs/current/pgstatstatements.html), so guessing past a\n// parse miss here would fabricate a target, not infer one.\nconst FROM_TABLE_RE = /\\bfrom\\s+\"?(?:[a-z_][a-z0-9_]*\"?\\.)?\"?([a-z_][a-z0-9_]*)\"?/i\nconst SYSTEM_SCHEMA_PREFIXES = ['pg_', 'information_schema']\n\nexport function tableNameFromQueryText(query: string): string | null {\n const match = FROM_TABLE_RE.exec(query)\n if (!match) return null\n const name = match[1]!\n const lower = name.toLowerCase()\n if (SYSTEM_SCHEMA_PREFIXES.some((prefix) => lower.startsWith(prefix))) return null\n return name\n}\n\nexport interface StatementBaseline {\n calls: number\n}\n\n/**\n * `pg_stat_statements.calls` is a lifetime cumulative counter, not a per-poll\n * count — replaying it verbatim every tick would re-mint the statement's\n * entire history as \"this window's calls\" on every single poll, ballooning\n * without bound. This diffs each row's `calls` against the previous poll's\n * value for the same `queryid`, carried in `previous` (owned by the\n * connector instance across ticks — see index.ts) and mutated in place:\n *\n * - A `queryid` seen for the first time only establishes a baseline this\n * tick — no signal, since there is no \"since last poll\" value yet to\n * subtract from (never replay a statement's full lifetime history as if it\n * were one window's activity, the same bounded-lookback discipline every\n * other connector applies to its own `since` watermark).\n * - A `queryid` whose `calls` decreased since the last poll (a Postgres\n * restart, an explicit `pg_stat_statements_reset()`) is treated as a fresh\n * baseline the same way — a negative delta is never fabricated into a\n * signal.\n * - A `queryid` that drops out of this poll's rows entirely (evicted past\n * `pg_stat_statements.max`, or simply out-ranked by busier statements past\n * `statementLimit`) has its baseline removed, so a later reappearance\n * starts fresh rather than diffing against stale state.\n *\n * `errorCount` is always 0 — pg_stat_statements carries no failure signal for\n * a statement (no distinction between a successful and a failed execution in\n * its own columns), so this is never fabricated.\n *\n * `lastObservedIso` uses the poll tick's own wall-clock time, not a\n * provider-supplied event time — unlike every other signal in this codebase.\n * pg_stat_statements has no per-row timestamp at all (it's a cumulative\n * counter snapshot, not an event log), so there is no provider event time to\n * read; the poll tick is the closest honest proxy for \"this activity was\n * observed as of this counter snapshot,\" the same tick-start-time convention\n * `startConnectorPollLoop` (connectors/index.ts) already uses for its own\n * `since` bookkeeping.\n */\nexport function diffPgStatStatementsToSignals(\n rows: PgStatStatementsRow[],\n previous: Map<string, StatementBaseline>,\n nowIso: string,\n): ObservedSignal[] {\n const signals: ObservedSignal[] = []\n const seen = new Set<string>()\n if (!Array.isArray(rows)) return signals\n\n for (const row of rows) {\n // Drop a null/garbage slot or a row with no usable queryid honestly rather\n // than throwing on `.queryid` (connectors.md §4).\n if (!row || typeof row !== 'object' || typeof row.queryid !== 'string') continue\n const calls = Number(row.calls)\n // A non-numeric `calls` (shape drift) can't produce an honest delta — skip\n // it entirely rather than diffing NaN into a NaN-valued callCount signal\n // the downstream pipeline would mis-tally (gate #8 truthful counts).\n if (!Number.isFinite(calls)) continue\n\n seen.add(row.queryid)\n const prior = previous.get(row.queryid)\n previous.set(row.queryid, { calls })\n\n if (!prior || calls < prior.calls) continue // fresh baseline this tick, no signal\n const delta = calls - prior.calls\n if (delta <= 0) continue\n\n const table = tableNameFromQueryText(row.query)\n if (!table) continue // can't honestly attribute a table — dropped, never guessed\n\n // ADR-157 — the same PostgREST-shaped query text names the columns it\n // touched; carry them (shared parser, not a second copy) so the pipeline\n // merges them onto the table node as OBSERVED column attributes. Empty for a\n // shape `columnsFromSqlStatement` degrades on.\n const columns = columnsFromSqlStatement(row.query)\n signals.push({\n targetKind: SUPABASE_TABLE_TARGET_KIND,\n targetName: table,\n callCount: delta,\n errorCount: 0,\n lastObservedIso: nowIso,\n ...(columns.length > 0 ? { columns } : {}),\n })\n }\n\n for (const queryid of [...previous.keys()]) {\n if (!seen.has(queryid)) previous.delete(queryid)\n }\n\n return signals\n}\n","// pg_stat_statements direct-Postgres client (docs/connectors/supabase.md\n// §Surfaces used #2). Read-only, ambient — a single SELECT against a system\n// view Postgres already maintains; this connector never writes to the\n// database it polls (connectors.md §2).\n//\n// Uses `pg` (node-postgres) — already a real dependency elsewhere in this\n// workspace (demo/service-b, e2e/capture/app) rather than a new client\n// library choice. `import pg from 'pg'` (default import, then destructure)\n// rather than `import { Client } from 'pg'`: `pg`'s CJS build doesn't\n// consistently expose static named exports across the major versions this\n// workspace already carries (demo/service-b pins 7.4.0; e2e/capture/app pins\n// ^8.12.0), so the default-import-then-destructure form node-postgres's own\n// docs recommend for ESM/TypeScript consumers is the version-safe one.\n\nimport pg from 'pg'\nimport { dbJunction } from '../junction.js'\nimport type { PgStatStatementsRow } from './types.js'\n\nconst { Client } = pg\n\n// Defensive cap, not a documented Postgres limit — see types.ts's\n// `statementLimit` doc comment for why (pg_stat_statements.max, default 5000\n// distinct statements on a busy project).\nexport const DEFAULT_STATEMENT_LIMIT = 500\n\n// Only ever SELECTs from pg_stat_statements, ordered by call volume so the\n// busiest statements are never starved by the LIMIT. The `query ~* '^\\s*select'`\n// filter narrows to read statements — the shape supabase-js's `.from()`/`.rpc()`\n// calls over PostgREST actually issue — rather than every INSERT/UPDATE/DDL\n// statement pg_stat_statements also tracks, which this connector's read-call-\n// count signal (supabase.md §Surfaces — \"call count / total time\") has no use\n// for.\nconst STATEMENTS_QUERY = `\n select queryid, query, calls, total_exec_time, rows\n from pg_stat_statements\n where query ~* '^\\\\s*select\\\\b'\n order by calls desc\n limit $1\n`\n\n// The minimal surface this module needs off a `pg.Client` — narrowed so tests\n// can inject a fake implementation (no real database) the same way\n// cloudflare/client.ts's `fetchImpl` parameter injects a fake `fetch`\n// (dependency injection, not a production mock — connectors.md §5 bars mocks\n// on the runtime poll path itself, not on this seam).\nexport interface PgClientLike {\n connect(): Promise<void>\n query<T>(text: string, values?: unknown[]): Promise<{ rows: T[] }>\n end(): Promise<void>\n}\n\n/**\n * Opens one short-lived connection per poll, runs the read, closes it. Poll\n * cadence is at most once a minute (DEFAULT_POLL_INTERVAL_MS,\n * connectors/index.ts) against a single query — a pool would add lifecycle\n * complexity (idle-connection reaping, exhaustion under a misconfigured\n * interval) for no real benefit at this call rate.\n *\n * `SET default_transaction_read_only = on` is a session-level, defense-in-\n * depth guard on top of whatever grant the connection string's role already\n * holds — belt-and-suspenders for the \"never writes on the read path\" rule\n * (connectors.md §2), not a substitute for the role itself being scoped\n * read-only (supabase.md §Scope's `pg_read_all_stats`-holding role).\n *\n * `clientFactory` defaults to a real `pg.Client`; tests override it with a\n * fake `PgClientLike` to exercise the query/session-guard behavior without a\n * live Postgres connection.\n *\n * Routed through `dbJunction` (ADR-131) — the same timeout/retry/rate-limit\n * discipline `junctionFetch` gives every HTTP-based connector, adapted to\n * this connect/query/end sequence. `accountKey` defaults to `'unknown'` only\n * for a caller that genuinely has no project identity to hand in (there is\n * none in this codebase — supabase/index.ts always passes its own\n * `config.apiProjectRef`); the fallback exists so this function still works\n * standalone rather than requiring every caller to thread one through.\n */\nexport async function fetchPgStatStatements(\n connectionString: string,\n limit: number = DEFAULT_STATEMENT_LIMIT,\n accountKey: string = 'unknown',\n clientFactory: (connectionString: string) => PgClientLike = (cs) => new Client({ connectionString: cs }),\n): Promise<PgStatStatementsRow[]> {\n return dbJunction(\n async () => {\n const client = clientFactory(connectionString)\n await client.connect()\n try {\n await client.query('SET default_transaction_read_only = on')\n const result = await client.query<PgStatStatementsRow>(STATEMENTS_QUERY, [limit])\n return result.rows\n } finally {\n await client.end()\n }\n },\n { provider: 'supabase-postgres', accountKey },\n )\n}\n","// Target resolution — the Supabase-specific half of the pull/map/fuse split\n// (connectors.md §Authority): turning a signal's (targetKind, targetName)\n// into a NEAT node id.\n//\n// This is the one place this connector's design departs from Railway/\n// Firebase's fusion pattern in a way worth calling out explicitly. Those two\n// providers fuse onto a RouteNode a static extractor (routes.ts) already\n// builds today. Supabase's own static extractor (extract/calls/supabase.ts)\n// recognizes only `createClient(...)` — not `.from()`/`.rpc()` — so the\n// table/RPC-grain InfraNode this connector's signals are meant to land on\n// (`infraId('supabase-table', ...)` / `infraId('supabase-rpc', ...)`,\n// supabase.md §Fusion) never exists in today's graph. `resolveTarget` cannot\n// create it either: a provider module has no mutation authority (ADR-030 —\n// only ingest.ts and extract/* call a graph mutator directly), and\n// `ResolveConnectorTarget`'s own signature only names an id, it never creates\n// one (the same constraint railway/connector.ts's own doc comment names for\n// its `unmatched-route` case).\n//\n// So: prefer the table/RPC InfraNode when it exists (the day a follow-up\n// extractor cut adds `.from()`/`.rpc()` parsing, this connector's edges\n// sharpen to that grain automatically, no connector-side change required —\n// exactly ADR-124's \"the fusion payoff compounds once a follow-up issue\n// extends the extractor to match\"); fall back to the project-level InfraNode\n// the *current* extractor already mints from a `createClient(...)` call\n// (`infraId('supabase', nodeRef)`) when it doesn't — project-level, honestly,\n// never fabricated. Neither existing is an honest miss (extraction hasn't run\n// against this project's code, or found no `createClient(...)` call to\n// resolve `nodeRef` from) — connectors.md §4's \"lands service-level (or\n// provider-node-level), honestly\" applies on the target side here exactly as\n// it does on the source side for every other connector's callSite-less case.\n\nimport { EdgeType, infraId } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport type { ResolveConnectorTarget, ResolvedConnectorTarget } from '../index.js'\nimport type { ConnectorContext, ObservedSignal } from '../types.js'\nimport { SUPABASE_RPC_TARGET_KIND, SUPABASE_TABLE_TARGET_KIND, type SupabaseConnectorConfig } from './types.js'\n\nexport function createSupabaseResolveTarget(\n graph: NeatGraph,\n config: SupabaseConnectorConfig,\n): ResolveConnectorTarget {\n return (signal: ObservedSignal, _ctx: ConnectorContext): ResolvedConnectorTarget | null => {\n if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {\n return null\n }\n\n const subResourceId = infraId(signal.targetKind, `${config.nodeRef}/${signal.targetName}`)\n if (graph.hasNode(subResourceId)) {\n return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType.CALLS }\n }\n\n // #803 — the static extractor mints table/RPC call sites at bare\n // `infra:<kind>:<table>` grain (it parses `<client>.from('table')` but can't\n // know the connector's nodeRef). Prefer that table node when it exists so the\n // observation lands table-grained — and can then fuse onto the file→table\n // static call site for file-grain — rather than collapsing to the project node.\n const bareResourceId = infraId(signal.targetKind, signal.targetName)\n if (graph.hasNode(bareResourceId)) {\n return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType.CALLS }\n }\n\n const projectLevelId = infraId('supabase', config.nodeRef)\n if (graph.hasNode(projectLevelId)) {\n return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType.CALLS }\n }\n\n // Neither node exists yet — extraction hasn't run against this project's\n // code, or found no `createClient(...)` call resolving to this `nodeRef`.\n // Honest miss (connectors.md's \"never fabricates a node or edge\"),\n // dropped rather than guessed.\n return null\n }\n}\n","// The Supabase connector (docs/connectors/supabase.md, ADR-124) — the\n// connectors plane's first provider. poll() pulls both surfaces\n// supabase.md §Surfaces used specifies: the Management API's edge_logs query\n// (client.ts, mapped by map.ts's mapEdgeLogRowsToSignals) always, and a\n// direct pg_stat_statements read (postgres-client.ts, mapped by map.ts's\n// diffPgStatStatementsToSignals) only when `ctx.credentials.postgresConnectionString`\n// is present — its absence is how the hosted profile's documented\n// \"log-surface-only\" first cut (supabase.md §Scope) falls out of this same\n// poll() with no profile-conditional branch (connectors.md §3). Target\n// resolution — preferring the table/RPC InfraNode a future extractor cut\n// will mint, falling back to the project-level InfraNode the current\n// extractor already does — lives in resolve.ts. Everything downstream of\n// resolution (minting the OBSERVED edge) is the shared connectors/index.ts\n// pipeline; this module never touches the graph directly (ADR-030).\n\nimport type { NeatGraph } from '../../graph.js'\nimport type { ResolveConnectorTarget } from '../index.js'\nimport type { ConnectorContext, ObservedConnector, ObservedSignal } from '../types.js'\nimport { boundedSupabaseLogWindow, fetchSupabaseEdgeLogs } from './client.js'\nimport { diffPgStatStatementsToSignals, mapEdgeLogRowsToSignals, type StatementBaseline } from './map.js'\nimport { fetchPgStatStatements, DEFAULT_STATEMENT_LIMIT } from './postgres-client.js'\nimport { createSupabaseResolveTarget } from './resolve.js'\nimport { readSupabaseCredentials, type SupabaseConnectorConfig } from './types.js'\n\nexport type {\n PgStatStatementsRow,\n SupabaseConnectorConfig,\n SupabaseCredentials,\n SupabaseEdgeLogRow,\n SupabaseLogsAllResponse,\n} from './types.js'\nexport { readSupabaseCredentials, SUPABASE_RPC_TARGET_KIND, SUPABASE_TABLE_TARGET_KIND } from './types.js'\nexport {\n boundedSupabaseLogWindow,\n DEFAULT_LOG_LIMIT,\n DEFAULT_SUPABASE_MANAGEMENT_API_URL,\n fetchSupabaseEdgeLogs,\n SUPABASE_LOG_QUERY_MAX_WINDOW_MS,\n} from './client.js'\nexport { DEFAULT_STATEMENT_LIMIT, fetchPgStatStatements } from './postgres-client.js'\nexport {\n diffPgStatStatementsToSignals,\n mapEdgeLogRowsToSignals,\n tableNameFromQueryText,\n targetFromRestPath,\n type StatementBaseline,\n} from './map.js'\nexport { createSupabaseResolveTarget } from './resolve.js'\n\n// supabase.md's own \"24h window\" governs `boundedSupabaseLogWindow` — this is\n// only the connector-level default passed in when a caller doesn't override\n// `config.maxLookbackMs`; the window is hard-capped at\n// SUPABASE_LOG_QUERY_MAX_WINDOW_MS regardless (client.ts).\nconst DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1000\n\nexport interface SupabaseConnectorDeps {\n fetchPgStatStatements?: typeof fetchPgStatStatements\n onPostgresSurfaceError?: (err: unknown, summary: string) => void\n}\n\nfunction errorCode(err: unknown): string | undefined {\n const code = (err as { code?: unknown } | undefined)?.code\n return typeof code === 'string' && code.length > 0 ? code : undefined\n}\n\nexport function describeSupabasePostgresSurfaceFailure(projectRef: string, err: unknown): string {\n const code = errorCode(err)\n let reason: string\n switch (code) {\n case '42501':\n reason = 'permission denied; grant pg_read_all_stats to the configured Postgres role'\n break\n case '42P01':\n case '42704':\n reason = 'pg_stat_statements is not enabled or visible to the configured Postgres role'\n break\n case '28P01':\n case '28000':\n reason = 'Postgres credential rejected'\n break\n case '3D000':\n reason = 'database not found'\n break\n default: {\n const name = err instanceof Error && err.name ? err.name : 'Error'\n reason = code ? `${name} ${code}` : name\n }\n }\n return (\n `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} ` +\n `(${reason}); continuing with Management API log surface.`\n )\n}\n\nexport class SupabaseConnector implements ObservedConnector {\n readonly provider = 'supabase'\n\n // pg_stat_statements.calls is cumulative, not per-window (map.ts's\n // diffPgStatStatementsToSignals doc comment) — this Map carries the\n // previous poll's counts across ticks, the same way\n // `startConnectorPollLoop` (connectors/index.ts) carries `since` across\n // ticks for every connector. Lives on the instance, not `ConnectorContext`,\n // because `ConnectorContext` is rebuilt fresh per tick (connectors/index.ts's\n // `{ ...ctx, since }`) while this connector object is the one thing every\n // tick shares.\n private readonly statementBaselines = new Map<string, StatementBaseline>()\n\n // `deps.fetchPgStatStatements` defaults to the real Postgres-backed\n // implementation; tests override it to exercise the \"both surfaces\n // combine\" and \"surface 2 only runs when a connection string is present\"\n // behavior without a live database — the same dependency-injection seam\n // `fetchImpl` gives cloudflare/client.ts's tests for `fetch`.\n constructor(\n private readonly config: SupabaseConnectorConfig,\n private readonly deps: SupabaseConnectorDeps = {},\n ) {}\n\n async poll(ctx: ConnectorContext): Promise<ObservedSignal[]> {\n const creds = readSupabaseCredentials(ctx.credentials)\n const now = new Date()\n const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS\n const { startIso, endIso } = boundedSupabaseLogWindow(ctx.since, now, maxLookbackMs)\n\n const logRows = await fetchSupabaseEdgeLogs(this.config, creds.managementToken, startIso, endIso)\n const signals: ObservedSignal[] = mapEdgeLogRowsToSignals(logRows)\n\n // Surface 2 only runs when a Postgres connection string is present —\n // supabase.md §Scope's local-profile-only-for-now split, expressed\n // entirely through what `ctx.credentials` carries rather than a\n // profile-conditional branch in this method (connectors.md §3).\n if (creds.postgresConnectionString) {\n const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements\n try {\n const statementRows = await fetchStatements(\n creds.postgresConnectionString,\n this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,\n this.config.apiProjectRef,\n )\n signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()))\n } catch (err) {\n const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err)\n if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary)\n else console.warn(summary)\n }\n }\n\n return signals\n }\n}\n\n/**\n * Wires up a ready-to-register Supabase connector: the `ObservedConnector`\n * plus the `resolveTarget` callback `runConnectorPoll` /\n * `startConnectorPollLoop` (connectors/index.ts) need alongside it. Built\n * together because `resolveTarget` closes over `graph` — the shared\n * scaffold's `ResolveConnectorTarget` signature (index.ts) never receives it\n * directly — the same pairing `createFirebaseConnector` uses.\n */\nexport function createSupabaseConnector(\n graph: NeatGraph,\n config: SupabaseConnectorConfig,\n deps: SupabaseConnectorDeps = {},\n): { connector: ObservedConnector; resolveTarget: ResolveConnectorTarget } {\n return {\n connector: new SupabaseConnector(config, deps),\n resolveTarget: createSupabaseResolveTarget(graph, config),\n }\n}\n","// Railway connector (ADR-127, docs/connectors/railway.md) — the second\n// connectors-plane provider after the shared pull/map/fuse scaffold\n// (ADR-124, connectors/index.ts). Railway names nothing in application code:\n// the signal comes entirely from Railway's own edge/ingress layer (httpLogs)\n// and L4 flow records (networkFlowLogs), so fusion binds onto the RouteNode\n// `extract/routes.ts` already builds — the same node an OBSERVED server span\n// would fuse onto if the app were OTel-instrumented — rather than a\n// client-SDK call site the way the Supabase connector design does.\n\nimport type { RouteNode } from '@neat.is/types'\nimport { EdgeType, NodeType, serviceId } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { normalizePathTemplate } from '../../extract/routes.js'\nimport type {\n ConnectorCallSite,\n ConnectorContext,\n ObservedConnector,\n ObservedSignal,\n ResolveConnectorTarget,\n} from '../index.js'\nimport type { RailwayConnectorConfig, RailwayHttpLogEntry, RailwayNetworkFlowLogEntry } from './types.js'\nimport {\n DEFAULT_MAX_LOOKBACK_MS,\n boundedRailwayStartDate,\n fetchRailwayHttpLogs,\n fetchRailwayNetworkFlowLogs,\n readRailwayToken,\n resolveLatestRailwayDeploymentId,\n} from './client.js'\n\nexport type { RailwayConnectorConfig, RailwayHttpLogEntry, RailwayNetworkFlowLogEntry } from './types.js'\nexport { DEFAULT_RAILWAY_API_URL, resolveLatestRailwayDeploymentId } from './client.js'\n\n// A signal's `targetKind` for this provider (README.md's \"provider's own\n// vocabulary\" — connectors/index.ts never inspects these strings itself):\n//\n// - 'route' — the record's (method, path) normalised onto an\n// existing RouteNode (extract/routes.ts). targetName\n// already carries that RouteNode's own graph id.\n// - 'unmatched-route' — no RouteNode resolved. See createRailwayResolveTarget\n// below for why this drops honestly rather than\n// fabricating a target.\n// - 'peer-service' — a networkFlowLogs record naming another Railway\n// service. targetName carries the raw Railway\n// peerServiceId, resolved through config.serviceNameById.\nconst ROUTE_TARGET_KIND = 'route'\nconst UNMATCHED_ROUTE_TARGET_KIND = 'unmatched-route'\nconst PEER_SERVICE_TARGET_KIND = 'peer-service'\n\n// ── route index (read-only graph query) ────────────────────────────────────\n//\n// Mirrors extract/calls/route-match.ts's own buildRouteIndex/findRoute — not\n// imported, since neither is exported from that module (only\n// normalizePathTemplate is, which this file reuses directly rather than\n// re-deriving the path-template normalisation rules). This is the\n// connectors-plane side of the same (method, normalised-path) matching\n// route-match.ts already does for a client call site, applied here to\n// Railway's own edge-observed (method, path) pairs instead of a parsed HTTP\n// client call (docs/connectors/railway.md §Fusion).\n\ninterface RailwayRouteIndexEntry {\n method: string\n normalizedPath: string\n routeNodeId: string\n path: string\n line?: number\n}\n\nexport function buildRailwayRouteIndex(graph: NeatGraph, serviceName: string): RailwayRouteIndexEntry[] {\n const out: RailwayRouteIndexEntry[] = []\n graph.forEachNode((_id, attrs) => {\n const node = attrs as unknown as { type?: string }\n if (node.type !== NodeType.RouteNode) return\n const route = attrs as unknown as RouteNode\n if (route.service !== serviceName) return\n out.push({\n method: route.method.toUpperCase(),\n normalizedPath: normalizePathTemplate(route.pathTemplate),\n routeNodeId: route.id,\n path: route.path,\n line: route.line,\n })\n })\n return out\n}\n\n// httpLogs always carries a concrete method (unlike a statically parsed\n// client call site, which sometimes can't determine one) — so unlike\n// route-match.ts's findRoute, there's no \"caller method unknown\" passthrough\n// case, only the route's own `ALL` wildcard.\nfunction findRailwayRoute(\n entries: RailwayRouteIndexEntry[],\n method: string,\n normalizedPath: string,\n): RailwayRouteIndexEntry | undefined {\n return entries.find(\n (e) => e.normalizedPath === normalizedPath && (e.method === 'ALL' || e.method === method),\n )\n}\n\nfunction bucketKey(method: string, normalizedPath: string): string {\n return `${method} ${normalizedPath}`\n}\n\nfunction isHttpErrorStatus(status: number): boolean {\n return status >= 400\n}\n\ninterface SignalBucket {\n callCount: number\n errorCount: number\n lastObservedIso: string\n targetKind: string\n targetName: string\n callSite?: ConnectorCallSite\n}\n\nfunction upsertBucket(\n buckets: Map<string, SignalBucket>,\n key: string,\n isError: boolean,\n timestamp: string,\n build: () => Omit<SignalBucket, 'callCount' | 'errorCount' | 'lastObservedIso'>,\n): void {\n const existing = buckets.get(key)\n if (existing) {\n existing.callCount += 1\n if (isError) existing.errorCount += 1\n if (timestamp > existing.lastObservedIso) existing.lastObservedIso = timestamp\n return\n }\n buckets.set(key, { callCount: 1, errorCount: isError ? 1 : 0, lastObservedIso: timestamp, ...build() })\n}\n\n// ── httpLogs → ObservedSignal[] ─────────────────────────────────────────────\n//\n// Route-grain fusion (docs/connectors/railway.md §Fusion): normalise each\n// record's `path` the same way route-match.ts normalises a client call\n// site's URL path, then match against the polled service's own RouteNodes.\n// A match carries the matched RouteNode's own `path`/`line` as this signal's\n// callSite — reconciled onto the EXTRACTED service-relative path\n// (connectors/index.ts's shared fuse step) the same way every other OBSERVED\n// surface in NEAT lands file-grained, because that path IS the extracted\n// path a static pass already walked (routes.ts's own file:line).\nexport function mapRailwayHttpLogsToSignals(\n entries: RailwayHttpLogEntry[],\n routeIndex: RailwayRouteIndexEntry[],\n): ObservedSignal[] {\n const buckets = new Map<string, SignalBucket>()\n if (!Array.isArray(entries)) return []\n\n for (const entry of entries) {\n // Drop a null/garbage slot or a row missing the method/path/timestamp a\n // signal needs, honestly, rather than throwing on `.toUpperCase()` /\n // normalizePathTemplate's `.split()` (connectors.md §4).\n if (!entry || typeof entry !== 'object') continue\n if (typeof entry.method !== 'string' || typeof entry.path !== 'string' || typeof entry.timestamp !== 'string') {\n continue\n }\n const method = entry.method.toUpperCase()\n const normalizedPath = normalizePathTemplate(entry.path)\n const match = findRailwayRoute(routeIndex, method, normalizedPath)\n const isError = isHttpErrorStatus(entry.httpStatus)\n\n if (match) {\n upsertBucket(buckets, `route:${match.routeNodeId}`, isError, entry.timestamp, () => ({\n targetKind: ROUTE_TARGET_KIND,\n targetName: match.routeNodeId,\n // RouteNode.line is optional in the schema (packages/types/src/\n // nodes.ts) even though routes.ts always sets it today — skip the\n // callSite rather than fabricate a line when it's ever absent\n // (file-awareness.md §6).\n ...(match.line !== undefined ? { callSite: { file: match.path, line: match.line } } : {}),\n }))\n } else {\n // No RouteNode resolves — the app's framework/router isn't one\n // routes.ts recognises yet, or the path doesn't match any declared\n // template. See createRailwayResolveTarget below for why this\n // targetKind always resolves to null (an honest \"unresolved\" miss)\n // rather than a fabricated target.\n upsertBucket(\n buckets,\n `unmatched:${bucketKey(method, normalizedPath)}`,\n isError,\n entry.timestamp,\n () => ({\n targetKind: UNMATCHED_ROUTE_TARGET_KIND,\n targetName: bucketKey(method, normalizedPath),\n }),\n )\n }\n }\n\n return [...buckets.values()].map((b) => ({\n targetKind: b.targetKind,\n targetName: b.targetName,\n callCount: b.callCount,\n errorCount: b.errorCount,\n lastObservedIso: b.lastObservedIso,\n ...(b.callSite ? { callSite: b.callSite } : {}),\n }))\n}\n\n// ── networkFlowLogs → ObservedSignal[] ──────────────────────────────────────\n//\n// Service-dependency signal, independent of any route (docs/connectors/\n// railway.md §Fusion — \"independent of whether any route resolves for the\n// traffic that produced the flow\"). A record with no `peerServiceId` names\n// no Railway-internal peer (public internet egress, say) and is dropped\n// honestly — never guessed at.\nexport function mapRailwayNetworkFlowLogsToSignals(\n entries: RailwayNetworkFlowLogEntry[],\n): ObservedSignal[] {\n const buckets = new Map<string, SignalBucket>()\n if (!Array.isArray(entries)) return []\n\n for (const entry of entries) {\n // A null/garbage slot, or a record with no Railway-internal peer (public\n // egress carries a null peerServiceId), drops honestly rather than\n // throwing or being guessed at (connectors.md §4, railway.md §Fusion).\n if (!entry || typeof entry !== 'object') continue\n if (typeof entry.peerServiceId !== 'string' || entry.peerServiceId.length === 0) continue\n if (typeof entry.timestamp !== 'string') continue\n const isError = entry.dropCause !== null && entry.dropCause !== ''\n upsertBucket(buckets, entry.peerServiceId, isError, entry.timestamp, () => ({\n targetKind: PEER_SERVICE_TARGET_KIND,\n targetName: entry.peerServiceId as string,\n }))\n }\n\n return [...buckets.values()].map((b) => ({\n targetKind: b.targetKind,\n targetName: b.targetName,\n callCount: b.callCount,\n errorCount: b.errorCount,\n lastObservedIso: b.lastObservedIso,\n }))\n}\n\n// ── target resolution (README.md pipeline step 2 — provider-specific) ──────\n//\n// A pure function of (signal, ctx, config) — no graph access, unlike\n// poll()/the route index above, which need a read-only graph query to match\n// against existing RouteNodes. That matching already happened in poll();\n// resolveTarget's job here is only the id-shape mapping README.md describes\n// (\"targetKind/targetName → NEAT node id\").\nexport function createRailwayResolveTarget(config: RailwayConnectorConfig): ResolveConnectorTarget {\n return (signal: ObservedSignal) => {\n const serviceName = config.serviceNameById[config.serviceId]\n // This connector's own polled service has no configured mapping — a\n // config error, not something to guess past (docs/connectors/railway.md\n // §Fusion, \"resolved once, never guessed\").\n if (!serviceName) return null\n\n if (signal.targetKind === ROUTE_TARGET_KIND) {\n // targetName is already the exact RouteNode graph id poll()'s route\n // index resolved — this is the two-sided divergence's OBSERVED half,\n // landing on the same node the EXTRACTED client↔route CALLS edge\n // (route-match.ts, ADR-119) and a future OBSERVED server span (#576)\n // would.\n return { targetNodeId: signal.targetName, serviceName, edgeType: EdgeType.CALLS }\n }\n\n if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {\n const peerName = config.serviceNameById[signal.targetName]\n // Unmapped peer serviceId — dropped honestly rather than guessed\n // (docs/connectors/railway.md §Fusion). A future config update adding\n // the mapping picks this traffic up on the next poll; it never\n // fabricates a peer identity in the meantime.\n if (!peerName) return null\n return { targetNodeId: serviceId(peerName), serviceName, edgeType: EdgeType.CONNECTS_TO }\n }\n\n // UNMATCHED_ROUTE_TARGET_KIND (and anything else unrecognised): no\n // RouteNode resolved for this traffic, and the scaffold's shared pipeline\n // (connectors/index.ts) has no primitive to auto-vivify one the way\n // ensureGraphqlOperationNode/ensureGrpcMethodNode do for an OTel span\n // (ingest.ts) — those exist on the OTLP-ingest path, not on\n // ResolveConnectorTarget's (signal, ctx) => ResolvedConnectorTarget\n // shape, which only names an id, never creates one. Even if it could:\n // RouteNode.path is a required field (packages/types/src/nodes.ts)\n // naming the real source location a static extractor found — a\n // Railway-observed route with no static match has no honest value to\n // put there, so minting one here would fabricate provenance\n // (file-awareness.md §6). Returning null routes this through the\n // scaffold's own honest-miss path (ConnectorPollResult.unresolved)\n // instead of forcing an edge. See this PR's description for the gap\n // this leaves open: a route Railway observes but no static extractor\n // recognises today produces no missing-extracted divergence via this\n // connector, pending either a scaffold extension (observed-first target\n // creation, mirroring the OTel-ingest pattern) or a product decision on\n // how to represent it without fabricating a path.\n return null\n }\n}\n\n// ── connector ────────────────────────────────────────────────────────────────\n//\n// Constructed with a `graph` reference so poll() can read (never mutate,\n// per ADR-030 mutation authority — every write this connector's signals\n// eventually cause flows back through connectors/index.ts's ingest.ts\n// primitives, not through this module) the polled service's existing\n// RouteNodes to match httpLogs against, the same read-only access\n// extract/calls/route-match.ts already has to the graph for the identical\n// purpose on the static-extraction side.\nexport function createRailwayConnector(graph: NeatGraph, config: RailwayConnectorConfig): ObservedConnector {\n return {\n provider: 'railway',\n async poll(ctx: ConnectorContext): Promise<ObservedSignal[]> {\n const token = readRailwayToken(ctx.credentials)\n const now = new Date()\n const maxLookbackMs = config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS\n const startDate = boundedRailwayStartDate(ctx.since, now, maxLookbackMs)\n const endDate = now.toISOString()\n\n // httpLogs is scoped by deploymentId, which is minted fresh on every\n // redeploy — resolved here, every poll, from the stable\n // (environmentId, serviceId) pair (client.ts). A service with no\n // deployment yet (or none this token can see) gets an honest empty\n // httpLogs result rather than a thrown error — the same \"empty window,\n // not a failure\" treatment an empty startDate..endDate range gets.\n const deploymentId = await resolveLatestRailwayDeploymentId(config, token)\n // allSettled, not all: httpLogs and networkFlowLogs are independent\n // surfaces (docs/connectors/railway.md §Surfaces) — a transient failure\n // on one (a flaky upstream 400, a timeout) shouldn't discard a\n // perfectly good result from the other. Each failure is reported\n // through ctx's own onError path (connectors/index.ts's\n // startConnectorPollLoop), not swallowed — it just doesn't cost the\n // other surface's signals too.\n const [httpLogsResult, flowLogsResult] = await Promise.allSettled([\n deploymentId ? fetchRailwayHttpLogs(config, token, deploymentId, startDate, endDate) : Promise.resolve([]),\n fetchRailwayNetworkFlowLogs(config, token),\n ])\n if (httpLogsResult.status === 'rejected') {\n console.error('[neat connector] railway httpLogs poll failed', httpLogsResult.reason)\n }\n if (flowLogsResult.status === 'rejected') {\n console.error('[neat connector] railway networkFlowLogs poll failed', flowLogsResult.reason)\n }\n const httpLogs = httpLogsResult.status === 'fulfilled' ? httpLogsResult.value : []\n const flowLogs = flowLogsResult.status === 'fulfilled' ? flowLogsResult.value : []\n\n const serviceName = config.serviceNameById[config.serviceId]\n const routeIndex = serviceName ? buildRailwayRouteIndex(graph, serviceName) : []\n\n return [\n ...mapRailwayHttpLogsToSignals(httpLogs, routeIndex),\n ...mapRailwayNetworkFlowLogsToSignals(flowLogs),\n ]\n },\n }\n}\n","// Railway GraphQL client — the fetch half of this connector's poll() (ADR-127,\n// docs/connectors/railway.md). Passive and ambient only (docs/contracts/\n// connectors.md §2): two read-only queries, never a mutation, never a\n// synthetic request to keep the connection warm.\n\nimport { junctionFetch } from '../junction.js'\nimport type { RailwayConnectorConfig, RailwayHttpLogEntry, RailwayNetworkFlowLogEntry } from './types.js'\n\nexport const DEFAULT_RAILWAY_API_URL = 'https://backboard.railway.com/graphql/v2'\n\n// docs/connectors/railway.md §\"Out of scope for this cut\" flags the\n// neighbouring plan-tier question the same way: Railway's docs don't publish\n// a retention/lookback window for httpLogs/networkFlowLogs as of this\n// writing. 24h is a conservative default pending a live project confirming\n// the real cap — never an unbounded full-history query regardless\n// (docs/contracts/connectors.md §\"Poll cadence and backfill\").\nexport const DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1000\nexport const DEFAULT_LOG_LIMIT = 1000\n\ninterface RailwayGraphQLError {\n message: string\n}\n\ninterface RailwayGraphQLResponse<T> {\n data?: T\n errors?: RailwayGraphQLError[]\n}\n\n// A Project-Access-Token is the credential ADR-127 scopes this connector to\n// (docs/connectors/railway.md §Scope) — both local and hosted profiles carry\n// it the same way (docs/contracts/connectors.md §3). Read from\n// `ConnectorContext.credentials` at poll time, never logged, never written\n// into a node/edge (contract §6).\nexport function readRailwayToken(credentials: Record<string, unknown>): string {\n const token = credentials.token\n if (typeof token !== 'string' || token.length === 0) {\n throw new Error(\n 'Railway connector requires ctx.credentials.token (a Project-Access-Token or account Bearer token)',\n )\n }\n return token\n}\n\n// Railway authenticates its two token families through different headers, and a\n// token presented on the wrong one is rejected exactly the way a bad token is:\n// an HTTP-200 body carrying a GraphQL \"Not Authorized\" error, never a 401/403\n// (#868; the 2026-07-08 live check in #738 saw this for a Project-Access-Token\n// on `Authorization: Bearer`). A **project** token uses Railway's dedicated\n// `Project-Access-Token` header; an **account** or **team** token uses\n// `Authorization: Bearer`. `readRailwayToken` accepts either and can't know\n// which it holds up front, so the connector resolves the working header on a\n// token's first query and reuses it for the rest of the process — the same\n// \"resolved once, never guessed\" discipline this connector applies to the\n// service mapping (docs/connectors/railway.md §Fusion), here applied to auth.\ntype RailwayAuthStyle = 'bearer' | 'project-access-token'\n\n// Ordered account-first: `Authorization: Bearer` is the header a personal\n// account token wants — the credential a user reaches for first, and the\n// connector's pre-#749 default — so the common case resolves on the first\n// attempt and only a project token pays a single extra probe per process.\nconst RAILWAY_AUTH_STYLES: readonly RailwayAuthStyle[] = ['bearer', 'project-access-token']\n\nfunction railwayAuthHeader(style: RailwayAuthStyle, token: string): Record<string, string> {\n return style === 'bearer' ? { Authorization: `Bearer ${token}` } : { 'Project-Access-Token': token }\n}\n\n// The header a given token resolved to, memoised for this process so steady-\n// state polling never re-probes. Keyed by the raw token — which already lives\n// in memory for the life of a poll, and is never logged nor written to a node\n// or edge (docs/contracts/connectors.md §6). Bounded by the number of distinct\n// Railway tokens a daemon carries (one per connector), i.e. tiny.\nconst resolvedRailwayAuthStyle = new Map<string, RailwayAuthStyle>()\n\n// Test-only: the memo is process-global, so a suite exercising both token\n// families needs to start each case from an unresolved state.\nexport function __resetRailwayAuthStyleCache(): void {\n resolvedRailwayAuthStyle.clear()\n}\n\nfunction isRailwayNotAuthorized(err: unknown): boolean {\n return err instanceof Error && /not authorized/i.test(err.message)\n}\n\nasync function railwayGraphQLOnce<T>(\n apiUrl: string,\n style: RailwayAuthStyle,\n token: string,\n query: string,\n variables: Record<string, unknown>,\n accountKey: string,\n fetchImpl?: typeof fetch,\n): Promise<T> {\n const res = await junctionFetch(\n apiUrl,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...railwayAuthHeader(style, token),\n },\n body: JSON.stringify({ query, variables }),\n },\n { provider: 'railway', accountKey, ...(fetchImpl ? { fetchImpl } : {}) },\n )\n if (!res.ok) {\n throw new Error(`Railway GraphQL request failed: ${res.status} ${res.statusText}`)\n }\n const body = (await res.json()) as RailwayGraphQLResponse<T>\n if (body.errors && body.errors.length > 0) {\n throw new Error(`Railway GraphQL errors: ${body.errors.map((e) => e.message).join('; ')}`)\n }\n if (!body.data) throw new Error('Railway GraphQL response carried no data')\n return body.data\n}\n\n// `accountKey` is Railway's environmentId (ADR-131's per-`(provider,\n// accountKey)` rate-limit bucket) — a Project-Access-Token is minted per\n// environment, not per account (docs/connectors/railway.md §Scope), so the\n// environment is the closest thing this connector's config carries to \"one\n// customer's account\" for this provider.\nasync function railwayGraphQL<T>(\n apiUrl: string,\n token: string,\n query: string,\n variables: Record<string, unknown>,\n accountKey: string,\n fetchImpl?: typeof fetch,\n): Promise<T> {\n const known = resolvedRailwayAuthStyle.get(token)\n const styles = known ? [known] : RAILWAY_AUTH_STYLES\n let lastNotAuthorized: Error | undefined\n for (let i = 0; i < styles.length; i++) {\n const style = styles[i]\n try {\n const data = await railwayGraphQLOnce<T>(apiUrl, style, token, query, variables, accountKey, fetchImpl)\n resolvedRailwayAuthStyle.set(token, style)\n return data\n } catch (err) {\n // Only an auth rejection is worth retrying on the other header. A real\n // query error (bad variables, a provider outage) fails the same way on\n // both headers, so surface it immediately rather than mask it behind a\n // second call. When the style is already memoised, `styles` has one entry\n // and this never retries.\n if (isRailwayNotAuthorized(err) && i < styles.length - 1) {\n lastNotAuthorized = err as Error\n continue\n }\n throw err\n }\n }\n // Both headers rejected the token: it is genuinely unauthorized. Surface the\n // provider's own message, unchanged from the single-header days (#738 tests).\n throw lastNotAuthorized ?? new Error('Railway GraphQL: no auth style resolved')\n}\n\n// Live-confirmed against backboard.railway.com/graphql/v2 (2026-07-08) via\n// schema introspection — httpLogs is scoped by `deploymentId`, not\n// `environmentId`/`serviceId` as originally assumed; startDate/endDate/limit\n// are valid but optional. `HttpLog`'s own field names matched the original\n// guess exactly (docs.railway.com/cli/logs's attribute list was accurate),\n// so only the query's arguments changed, not the row shape below.\nconst HTTP_LOGS_QUERY = `\n query HttpLogs($deploymentId: String!, $startDate: String, $endDate: String, $limit: Int) {\n httpLogs(deploymentId: $deploymentId, startDate: $startDate, endDate: $endDate, limit: $limit) {\n timestamp\n method\n path\n httpStatus\n totalDuration\n requestId\n deploymentId\n edgeRegion\n }\n }\n`\n\n// Live-confirmed: networkFlowLogs takes no startDate/endDate/limit at all —\n// only environmentId (required) + serviceId (optional); its only windowing\n// is cursor pagination (afterDate/afterLimit/anchorDate/beforeDate/\n// beforeLimit) this connector doesn't exercise yet. `NetworkFlowLog` has no\n// `timestamp` field either — aliased here from its real field `captureStart`\n// so the rest of the connector keeps reading `entry.timestamp` unchanged.\nconst NETWORK_FLOW_LOGS_QUERY = `\n query NetworkFlowLogs($environmentId: String!, $serviceId: String) {\n networkFlowLogs(environmentId: $environmentId, serviceId: $serviceId) {\n timestamp: captureStart\n peerServiceId\n peerKind\n direction\n byteCount\n packetCount\n dropCause\n }\n }\n`\n\n// httpLogs needs a deploymentId, which is minted fresh on every redeploy —\n// unlike the serviceId→name mapping (docs/connectors/railway.md §Fusion,\n// \"resolved once, never guessed\"), a deploymentId can't be resolved once at\n// connector-setup time without going stale the next time the service\n// redeploys. Resolved fresh each poll from the stable (environmentId,\n// serviceId) pair instead. Picks the newest SUCCESS deployment; falls back to\n// the newest of any status if none has succeeded yet, so a service between\n// its first (failed) deploy and its first success still has an id to poll\n// against (httpLogs simply comes back empty for it until a real deploy lands).\nconst DEPLOYMENTS_QUERY = `\n query LatestDeployment($environmentId: String!, $serviceId: String!) {\n deployments(input: { environmentId: $environmentId, serviceId: $serviceId }, first: 5) {\n edges { node { id status createdAt } }\n }\n }\n`\n\ninterface RailwayDeploymentNode {\n id: string\n status: string\n createdAt: string\n}\n\nexport async function resolveLatestRailwayDeploymentId(\n config: RailwayConnectorConfig,\n token: string,\n fetchImpl?: typeof fetch,\n): Promise<string | null> {\n const data = await railwayGraphQL<{ deployments: { edges: { node: RailwayDeploymentNode }[] } }>(\n config.apiUrl ?? DEFAULT_RAILWAY_API_URL,\n token,\n DEPLOYMENTS_QUERY,\n { environmentId: config.environmentId, serviceId: config.serviceId },\n config.environmentId,\n fetchImpl,\n )\n const nodes = data.deployments.edges.map((e) => e.node)\n const newestFirst = [...nodes].sort((a, b) => b.createdAt.localeCompare(a.createdAt))\n const success = newestFirst.find((n) => n.status === 'SUCCESS')\n return (success ?? newestFirst[0])?.id ?? null\n}\n\nexport async function fetchRailwayHttpLogs(\n config: RailwayConnectorConfig,\n token: string,\n deploymentId: string,\n startDate: string,\n endDate: string,\n): Promise<RailwayHttpLogEntry[]> {\n const data = await railwayGraphQL<{ httpLogs: RailwayHttpLogEntry[] }>(\n config.apiUrl ?? DEFAULT_RAILWAY_API_URL,\n token,\n HTTP_LOGS_QUERY,\n { deploymentId, startDate, endDate, limit: config.limit ?? DEFAULT_LOG_LIMIT },\n config.environmentId,\n )\n return data.httpLogs\n}\n\nexport async function fetchRailwayNetworkFlowLogs(\n config: RailwayConnectorConfig,\n token: string,\n): Promise<RailwayNetworkFlowLogEntry[]> {\n const data = await railwayGraphQL<{ networkFlowLogs: RailwayNetworkFlowLogEntry[] }>(\n config.apiUrl ?? DEFAULT_RAILWAY_API_URL,\n token,\n NETWORK_FLOW_LOGS_QUERY,\n { environmentId: config.environmentId, serviceId: config.serviceId },\n config.environmentId,\n )\n return data.networkFlowLogs\n}\n\n// `since` bounded by the provider's max lookback window (docs/contracts/\n// connectors.md §\"Poll cadence and backfill\") — a gap wider than the window\n// (a laptop off for a week) backfills from `now - maxLookbackMs`, never an\n// unbounded full-history replay. An absent or unparseable `since` (no prior\n// poll) gets the same treatment as too-old a `since`.\nexport function boundedRailwayStartDate(since: string | undefined, now: Date, maxLookbackMs: number): string {\n const floor = new Date(now.getTime() - maxLookbackMs)\n if (!since) return floor.toISOString()\n const sinceMs = new Date(since).getTime()\n if (Number.isNaN(sinceMs)) return floor.toISOString()\n return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString()\n}\n","// Cloud Logging `entries.list` — the raw fetch half of the Firebase connector\n// (docs/connectors/firebase.md, ADR-128). This file owns exactly the wire\n// shape and the HTTP call; mapping a `LogEntry` to an `ObservedSignal` lives\n// in map.ts, and target resolution lives in resolve.ts — the same\n// fetch/map/resolve split every other provider module keeps (connectors.md\n// §Authority).\n//\n// Every field name below was confirmed live against Google's own docs rather\n// than recalled from training data (per this connector's build instructions):\n// - request/response envelope + endpoint:\n// https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/list\n// - LogEntry + HttpRequest field names:\n// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry\n// - monitored-resource labels for cloud_function / cloud_run_revision /\n// firebase_domain:\n// https://cloud.google.com/logging/docs/api/v2/resource-list\n// Anything not directly confirmed by those pages is called out inline as\n// unconfirmed rather than asserted as fact.\n\nimport { bearerAuthHeader, junctionFetch } from '../junction.js'\n\n// ── credentials ──────────────────────────────────────────────────────────\n\n// `ConnectorContext.credentials` is opaque at the shared-scaffold layer\n// (types.ts) — this is the Firebase-specific shape it must carry. Per\n// docs/connectors/firebase.md §Surfaces used, both the local and hosted\n// profile use the same narrow grant (`roles/monitoring.viewer`,\n// `roles/logging.viewer`, `roles/cloudfunctions.viewer`,\n// `roles/firebasehosting.viewer`) — there's no Fork-A-style split here the\n// way Supabase's `pg_stat_statements` gap forced. Minting `accessToken` (a\n// service-account or ADC-derived OAuth token scoped to those roles) is a\n// profile-specific broker/config concern (connectors.md §3) outside this\n// connector's job — `poll()` only ever consumes an already-minted token, the\n// same way Railway's connector consumes an already-minted\n// `Project-Access-Token` rather than performing its own auth handshake.\nexport interface FirebaseCredentials {\n projectId: string\n accessToken: string\n}\n\nexport function readFirebaseCredentials(raw: Record<string, unknown>): FirebaseCredentials {\n const projectId = raw['projectId']\n const accessToken = raw['accessToken']\n if (typeof projectId !== 'string' || projectId.length === 0) {\n throw new Error('firebase connector: credentials.projectId must be a non-empty string')\n }\n if (typeof accessToken !== 'string' || accessToken.length === 0) {\n throw new Error('firebase connector: credentials.accessToken must be a non-empty string')\n }\n return { projectId, accessToken }\n}\n\n// ── Cloud Logging wire types ─────────────────────────────────────────────\n\n// The three monitored-resource types this connector polls (firebase.md\n// §Surfaces used). 2nd-gen Cloud Functions can surface under either\n// `cloud_function` or `cloud_run_revision` depending on how Google's own\n// logging pipeline attributes the request — both are polled so neither shape\n// is missed.\nexport type FirebaseResourceType = 'cloud_function' | 'cloud_run_revision' | 'firebase_domain'\n\nconst RESOURCE_TYPES: readonly FirebaseResourceType[] = [\n 'cloud_function',\n 'cloud_run_revision',\n 'firebase_domain',\n]\n\nexport function isFirebaseResourceType(value: string): value is FirebaseResourceType {\n return (RESOURCE_TYPES as readonly string[]).includes(value)\n}\n\n// MonitoredResource (LogEntry.resource). `labels` carries the resource-type-\n// specific identity fields confirmed at\n// https://cloud.google.com/logging/docs/api/v2/resource-list:\n// cloud_function: project_id, function_name, region\n// cloud_run_revision: project_id, service_name, revision_name, location,\n// configuration_name\n// firebase_domain: project_id, site_name, domain_name\nexport interface MonitoredResource {\n type: string\n labels?: Record<string, string>\n}\n\n// HttpRequest (LogEntry.httpRequest), field names confirmed against the\n// LogEntry reference page above. Every field is optional in the wire type —\n// a structured request log is expected to carry requestMethod/requestUrl/\n// status, but nothing here is guaranteed present, so the mapper (map.ts)\n// treats an absent field as an honest miss rather than guessing a default.\nexport interface HttpRequest {\n requestMethod?: string\n requestUrl?: string\n requestSize?: string\n status?: number\n responseSize?: string\n userAgent?: string\n remoteIp?: string\n serverIp?: string\n referer?: string\n latency?: string\n cacheLookup?: boolean\n cacheHit?: boolean\n cacheValidatedWithOriginServer?: boolean\n cacheFillBytes?: string\n protocol?: string\n}\n\n// LogEntry, the subset of fields this connector reads. `timestamp` is the\n// provider's own event time (confirmed string/Timestamp-format field) — used\n// as `ObservedSignal.lastObservedIso`, never `receiveTimestamp` (ingest-time),\n// matching the \"provider's own event time, never poll-arrival time\" rule\n// (connectors/README.md §Poll cadence and backfill).\nexport interface LogEntry {\n logName?: string\n resource?: MonitoredResource\n timestamp?: string\n receiveTimestamp?: string\n severity?: string\n insertId?: string\n httpRequest?: HttpRequest\n}\n\nexport interface EntriesListRequest {\n resourceNames: string[]\n filter?: string\n orderBy?: string\n pageSize?: number\n pageToken?: string\n}\n\nexport interface EntriesListResponse {\n entries?: LogEntry[]\n nextPageToken?: string\n}\n\n// ── filter construction ──────────────────────────────────────────────────\n\n// Cloud Logging query-language operators confirmed against\n// https://cloud.google.com/logging/docs/view/logging-query-language:\n// - `resource.type = (\"a\" OR \"b\")` for a multi-value match\n// - `httpRequest:*` as the field-exists test\n// - `timestamp >= \"<RFC3339>\"` for a lower bound\n// Joined with explicit `AND` rather than relying on the query language's\n// documented (but not directly re-confirmed here) implicit-AND-per-line\n// behaviour.\nexport function buildEntriesFilter(sinceIso: string): string {\n return [\n 'resource.type = (\"cloud_function\" OR \"cloud_run_revision\" OR \"firebase_domain\")',\n 'httpRequest:*',\n `timestamp >= \"${sinceIso}\"`,\n ].join(' AND ')\n}\n\n// No documented lookback cap surfaced for `entries.list` itself (bounded only\n// by the log bucket's own retention, typically 30 days on the `_Default`\n// bucket) — mirrors docs/connectors/supabase.md's \"capped at 24h lookback\"\n// convention for a local-profile poll with no prior high-water mark, the\n// closest documented analog in this codebase, rather than inventing an\n// unbounded first query.\nexport const DEFAULT_LOOKBACK_MS = 24 * 60 * 60 * 1000\n\nconst ENTRIES_LIST_URL = 'https://logging.googleapis.com/v2/entries:list'\nconst PAGE_SIZE = 1000\n// Defensive cap on pagination — entries.list documents no page-count limit,\n// so this bounds the loop the same way STITCH_MAX_DEPTH bounds trace-stitch\n// BFS elsewhere in ingest.ts, rather than trusting an unbounded while(true).\nconst MAX_PAGES = 20\n\nexport async function fetchHttpRequestLogEntries(\n creds: FirebaseCredentials,\n sinceIso: string,\n): Promise<LogEntry[]> {\n const filter = buildEntriesFilter(sinceIso)\n const out: LogEntry[] = []\n let pageToken: string | undefined\n for (let page = 0; page < MAX_PAGES; page++) {\n const body: EntriesListRequest = {\n resourceNames: [`projects/${creds.projectId}`],\n filter,\n orderBy: 'timestamp asc',\n pageSize: PAGE_SIZE,\n ...(pageToken ? { pageToken } : {}),\n }\n const res = await junctionFetch(\n ENTRIES_LIST_URL,\n {\n method: 'POST',\n headers: {\n ...bearerAuthHeader(creds.accessToken),\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n },\n // accountKey: the GCP project id (ADR-131's own worked example for\n // Firebase) — one customer's Cloud Logging quota is scoped per GCP\n // project, not per Firebase site/function.\n { provider: 'firebase', accountKey: creds.projectId },\n )\n if (!res.ok) {\n throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`)\n }\n const json = (await res.json()) as EntriesListResponse\n // A well-formed page carries an array (absent when the window is empty);\n // a shape drift handing back a non-array `entries` drops honestly rather\n // than throwing on `push(...nonIterable)` (connectors.md §4).\n if (Array.isArray(json.entries)) out.push(...json.entries)\n if (!json.nextPageToken) break\n pageToken = json.nextPageToken\n }\n return out\n}\n","// LogEntry -> ObservedSignal mapping (docs/connectors/firebase.md, ADR-128).\n// One signal per log entry — no aggregation across entries — so each\n// concrete request stays individually traceable back to the raw log line it\n// came from; the shared pipeline (connectors/index.ts) already replays a\n// signal's callCount/errorCount as individual upserts, so nothing is lost by\n// keeping this 1:1.\n//\n// This module never touches Firestore or Firebase Auth data or APIs —\n// out of scope per firebase.md §Scope. It only reads the httpRequest /\n// resource fields off a Cloud Logging LogEntry.\n\nimport type { ObservedSignal } from '../types.js'\nimport { isFirebaseResourceType, type FirebaseResourceType, type LogEntry } from './logging-api.js'\n\n// The provider-vocabulary identity this connector's signals carry through\n// the shared pipeline (targetKind/targetName, per types.ts's ObservedSignal\n// doc comment: \"the provider's own vocabulary\"). Packed into a single string\n// because ObservedSignal.targetName is a plain string field; unpacked only by\n// this connector's own resolveTarget (resolve.ts), never surfaced to the\n// graph. `\\x00` is used as a separator since it cannot appear in an HTTP\n// method token, a GCP resource name, or a URL path.\nconst FIELD_SEP = '\\x00'\n\nexport interface FirebaseTargetIdentity {\n resourceName: string\n method: string\n path: string\n}\n\nexport function packFirebaseTargetName(identity: FirebaseTargetIdentity): string {\n return [identity.resourceName, identity.method, identity.path].join(FIELD_SEP)\n}\n\nexport function parseFirebaseTargetName(targetName: string): FirebaseTargetIdentity | null {\n // Split on the first two separators only — a path (always the last field)\n // may itself carry no separator characters in practice, but splitting\n // greedily here rather than on every occurrence keeps this correct even if\n // it ever did.\n const firstSep = targetName.indexOf(FIELD_SEP)\n if (firstSep === -1) return null\n const resourceName = targetName.slice(0, firstSep)\n const rest = targetName.slice(firstSep + 1)\n const secondSep = rest.indexOf(FIELD_SEP)\n if (secondSep === -1) return null\n const method = rest.slice(0, secondSep)\n const path = rest.slice(secondSep + 1)\n if (!resourceName || !method || !path) return null\n return { resourceName, method, path }\n}\n\n// The resource-identity label Google's own monitored-resource schema carries\n// per type (confirmed at\n// https://cloud.google.com/logging/docs/api/v2/resource-list):\n// cloud_function -> labels.function_name\n// cloud_run_revision -> labels.service_name\n// firebase_domain -> labels.site_name\nfunction resourceNameFor(\n type: FirebaseResourceType,\n labels: Record<string, string> | undefined,\n): string | null {\n if (!labels) return null\n switch (type) {\n case 'cloud_function':\n return labels['function_name'] ?? null\n case 'cloud_run_revision':\n return labels['service_name'] ?? null\n case 'firebase_domain':\n return labels['site_name'] ?? null\n }\n}\n\n// `httpRequest.requestUrl` is documented as \"typically without the scheme,\n// host, port, and query portion\" — i.e. usually already a bare path — but a\n// deployment that logs a full absolute URL is handled too. Returns null when\n// the value is neither a bare path nor a parseable absolute URL, the same\n// \"honest miss, never guessed\" discipline `pathOf` uses in\n// extract/calls/route-match.ts.\nfunction pathFromRequestUrl(requestUrl: unknown): string | null {\n // Typed a string upstream, but this reads a raw Cloud Logging record — a\n // shape drift handing a number/object here drops honestly rather than\n // throwing on `.startsWith` (connectors.md §4).\n if (typeof requestUrl !== 'string' || requestUrl.length === 0) return null\n if (requestUrl.startsWith('/')) {\n const withoutQuery = requestUrl.split('?')[0]\n return withoutQuery && withoutQuery.length > 0 ? withoutQuery : '/'\n }\n try {\n const candidate = requestUrl.startsWith('//') ? `https:${requestUrl}` : requestUrl\n const parsed = new URL(candidate)\n return parsed.pathname || '/'\n } catch {\n return null\n }\n}\n\n// A response is counted as an error at the 5xx threshold — the same\n// unambiguous-failure line ingest.ts draws for a failing HTTP response\n// (see the `status >= 500` branch in handleSpan's failing-response-incident\n// logic). A 4xx is a plausible client error, not necessarily a service\n// defect, so it isn't counted here.\nconst ERROR_STATUS_THRESHOLD = 500\n\n// Maps one Cloud Logging LogEntry to one ObservedSignal. Returns null for an\n// entry this connector can't honestly attribute — an unrecognised resource\n// type (filter should already exclude these, but never trust the filter\n// alone), a resource with no identity label, or an httpRequest missing the\n// method/path a signal needs. Nothing here is guessed or fabricated.\nexport function mapLogEntryToSignal(entry: LogEntry | null | undefined): ObservedSignal | null {\n // A shape-drifted response can carry a null/garbage slot; drop it honestly\n // rather than throwing on `.resource` (connectors.md §4).\n if (!entry || typeof entry !== 'object') return null\n const resourceType = entry.resource?.type\n if (!resourceType || !isFirebaseResourceType(resourceType)) return null\n\n const resourceName = resourceNameFor(resourceType, entry.resource?.labels)\n if (!resourceName) return null\n\n const req = entry.httpRequest\n if (!req) return null\n if (typeof req.requestMethod !== 'string' || req.requestMethod.length === 0) return null\n const method = req.requestMethod.toUpperCase()\n const path = pathFromRequestUrl(req.requestUrl)\n if (path === null) return null\n\n const timestamp = entry.timestamp\n if (typeof timestamp !== 'string' || timestamp.length === 0) return null\n\n const isError = typeof req.status === 'number' && req.status >= ERROR_STATUS_THRESHOLD\n\n return {\n targetKind: resourceType,\n targetName: packFirebaseTargetName({ resourceName, method, path }),\n callCount: 1,\n errorCount: isError ? 1 : 0,\n lastObservedIso: timestamp,\n }\n}\n\nexport function mapLogEntriesToSignals(entries: LogEntry[]): ObservedSignal[] {\n const out: ObservedSignal[] = []\n for (const entry of entries) {\n const signal = mapLogEntryToSignal(entry)\n if (signal) out.push(signal)\n }\n return out\n}\n","// Target resolution — the Firebase-specific half of the pull/map/fuse split\n// (connectors.md §Authority): turning a signal's (resourceName, method,\n// path) into a NEAT node id. Two independent lookups happen here, in order:\n//\n// 1. resourceName -> NEAT manifest service name, via an explicit config-time\n// mapping (FirebaseServiceMap below) — GCP resource names won't\n// generally match `package.json#name`, the same gap ADR-127 documents\n// for Railway's own `serviceId` and resolves the same way: supplied\n// once at connector setup, never guessed at poll time.\n// 2. (service, method, path) -> a statically-extracted RouteNode, via the\n// same path-template normalisation extract/calls/route-match.ts already\n// uses to match a client call site against a server's declared route.\n//\n// A miss at either step returns null — an honest, documented gap (firebase.md\n// §Fusion's \"raw handler, no Express app\" case), never a fabricated edge.\n//\n// This module never mutates the graph (ADR-030) — it only reads RouteNode /\n// ServiceNode attributes already there, matching every other file in\n// packages/core/src/connectors/**.\n\nimport { NodeType, EdgeType, type RouteNode } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { normalizePathTemplate } from '../../extract/routes.js'\nimport type { ConnectorContext, ObservedSignal } from '../types.js'\nimport type { ResolveConnectorTarget, ResolvedConnectorTarget } from '../index.js'\nimport type { FirebaseResourceType } from './logging-api.js'\nimport { parseFirebaseTargetName } from './map.js'\n\n// Explicit config-time mapping from a GCP hosting-platform resource name to\n// the NEAT manifest service name that resolves `serviceId(name)`\n// (packages/types/src/identity.ts) — the same \"resolved once, never guessed\"\n// discipline ADR-127 states for Railway's serviceId mapping. Keyed by\n// resource type because the same literal name could coincidentally collide\n// across a Cloud Function, a Cloud Run service, and a Hosting site.\nexport interface FirebaseServiceMap {\n // Cloud Functions (2nd gen) surfaced under the `cloud_function` monitored\n // resource — function_name -> NEAT service name.\n functions?: Record<string, string>\n // Cloud Run services, including 2nd-gen Functions surfaced under\n // `cloud_run_revision` instead — service_name -> NEAT service name.\n cloudRun?: Record<string, string>\n // Firebase Hosting sites (`firebase_domain`) — site_name -> NEAT service\n // name.\n hosting?: Record<string, string>\n}\n\nfunction neatServiceNameFor(\n resourceType: FirebaseResourceType,\n resourceName: string,\n serviceMap: FirebaseServiceMap,\n): string | null {\n switch (resourceType) {\n case 'cloud_function':\n return serviceMap.functions?.[resourceName] ?? null\n case 'cloud_run_revision':\n return serviceMap.cloudRun?.[resourceName] ?? null\n case 'firebase_domain':\n return serviceMap.hosting?.[resourceName] ?? null\n }\n}\n\ninterface RouteEntry {\n method: string\n normalizedPath: string\n routeNodeId: string\n}\n\n// Every RouteNode owned by one NEAT service, normalised for matching — the\n// same reduction extract/calls/route-match.ts's buildRouteIndex applies,\n// scoped here to a single service since resolveTarget already knows which\n// service a signal maps to. Rebuilt per call rather than cached: a connector\n// poll tick runs at most once a minute (DEFAULT_POLL_INTERVAL_MS,\n// connectors/index.ts) against a project-sized graph, so a fresh scan per\n// signal is cheap relative to the network round-trip poll() already made,\n// and it never risks matching against a stale route table if static\n// extraction re-ran mid-poll.\nfunction routeEntriesFor(graph: NeatGraph, serviceName: string): RouteEntry[] {\n const entries: RouteEntry[] = []\n graph.forEachNode((_id, attrs) => {\n const node = attrs as unknown as { type?: string }\n if (node.type !== NodeType.RouteNode) return\n const route = attrs as unknown as RouteNode\n if (route.service !== serviceName) return\n entries.push({\n method: route.method.toUpperCase(),\n normalizedPath: normalizePathTemplate(route.pathTemplate),\n routeNodeId: route.id,\n })\n })\n return entries\n}\n\n// Same compatibility rule route-match.ts's findRoute uses: exact normalised-\n// path match, and a method match unless the route is method-agnostic (`ALL`)\n// — a connector signal's method is always known (Cloud Logging's\n// httpRequest.requestMethod), unlike a statically-parsed client call site\n// where the method can be unresolvable, so there's no \"method undetermined\"\n// branch to mirror here.\nfunction findRoute(entries: RouteEntry[], method: string, normalizedPath: string): RouteEntry | undefined {\n return entries.find(\n (e) => e.normalizedPath === normalizedPath && (e.method === 'ALL' || e.method === method),\n )\n}\n\n// Builds the resolveTarget callback runConnectorPoll (connectors/index.ts)\n// calls once per signal. Closes over `graph` because ResolveConnectorTarget's\n// own signature (types.ts) doesn't carry it — the same closure pattern every\n// ConnectorRegistration's resolveTarget is expected to use.\nexport function createFirebaseResolveTarget(\n graph: NeatGraph,\n serviceMap: FirebaseServiceMap,\n): ResolveConnectorTarget {\n return (signal: ObservedSignal, _ctx: ConnectorContext): ResolvedConnectorTarget | null => {\n const resourceType = signal.targetKind\n if (resourceType !== 'cloud_function' && resourceType !== 'cloud_run_revision' && resourceType !== 'firebase_domain') {\n return null\n }\n const identity = parseFirebaseTargetName(signal.targetName)\n if (!identity) return null\n\n const serviceName = neatServiceNameFor(resourceType, identity.resourceName, serviceMap)\n // No configured mapping for this resource — a setup gap (an unconfigured\n // FirebaseServiceMap entry), honestly unresolved rather than guessed.\n if (!serviceName) return null\n\n const normalizedPath = normalizePathTemplate(identity.path)\n const match = findRoute(routeEntriesFor(graph, serviceName), identity.method, normalizedPath)\n // No static route recognises this path — the \"raw handler, no Express\n // app\" gap firebase.md §Fusion documents explicitly as an accepted,\n // honest gap rather than something to route around. Landing this on the\n // service's own ServiceNode as both source and target would be a\n // self-loop (the graph disallows them, graph.ts's\n // `allowSelfLoops: false`), so it's left unresolved rather than forcing\n // a meaningless edge or inventing a new node type firebase.md's \"no new\n // NodeType\" explicitly rules out.\n if (!match) return null\n\n return {\n targetNodeId: match.routeNodeId,\n serviceName,\n edgeType: EdgeType.CALLS,\n }\n }\n}\n","// The Firebase connector (docs/connectors/firebase.md, ADR-128) — third\n// connectors-plane provider, after Supabase (ADR-124) and Railway (ADR-127).\n//\n// Scoped to Cloud Functions / Cloud Run / Firebase Hosting request logs only.\n// Firestore and Firebase Auth are named non-goals (firebase.md §Scope — no\n// least-privilege telemetry path exists for either, not merely unbuilt); this\n// module never reads or references either surface.\n//\n// poll() pulls Cloud Logging's `entries.list` (logging-api.ts), filtered to\n// entries carrying an `httpRequest`, and maps each one to an ObservedSignal\n// (map.ts). Target resolution — matching a signal onto a statically-\n// extracted RouteNode via the shared route-match.ts normalisation, or an\n// honest miss when none resolves — lives in resolve.ts. Everything\n// downstream of resolution (minting the OBSERVED edge) is the shared\n// connectors/index.ts pipeline; this module never touches the graph\n// directly (ADR-030).\n\nimport type { NeatGraph } from '../../graph.js'\nimport type { ConnectorContext, ObservedConnector, ObservedSignal } from '../types.js'\nimport type { ResolveConnectorTarget } from '../index.js'\nimport { fetchHttpRequestLogEntries, readFirebaseCredentials, DEFAULT_LOOKBACK_MS } from './logging-api.js'\nimport { mapLogEntriesToSignals } from './map.js'\nimport { createFirebaseResolveTarget, type FirebaseServiceMap } from './resolve.js'\n\nexport type { FirebaseCredentials, FirebaseResourceType } from './logging-api.js'\nexport type { FirebaseServiceMap } from './resolve.js'\nexport { createFirebaseResolveTarget } from './resolve.js'\nexport type { FirebaseTargetIdentity } from './map.js'\nexport { mapLogEntryToSignal, mapLogEntriesToSignals, packFirebaseTargetName, parseFirebaseTargetName } from './map.js'\n\nexport class FirebaseConnector implements ObservedConnector {\n readonly provider = 'firebase'\n\n async poll(ctx: ConnectorContext): Promise<ObservedSignal[]> {\n const creds = readFirebaseCredentials(ctx.credentials)\n const sinceIso = ctx.since ?? new Date(Date.now() - DEFAULT_LOOKBACK_MS).toISOString()\n const entries = await fetchHttpRequestLogEntries(creds, sinceIso)\n return mapLogEntriesToSignals(entries)\n }\n}\n\n/**\n * Wires up a ready-to-register Firebase connector: the `ObservedConnector`\n * plus the `resolveTarget` callback `runConnectorPoll` /\n * `startConnectorPollLoop` (connectors/index.ts) need alongside it. Both are\n * built together because `resolveTarget` closes over `graph` — the shared\n * scaffold's `ResolveConnectorTarget` signature (index.ts) never receives it\n * directly.\n */\nexport function createFirebaseConnector(\n graph: NeatGraph,\n serviceMap: FirebaseServiceMap,\n): { connector: ObservedConnector; resolveTarget: ResolveConnectorTarget } {\n return {\n connector: new FirebaseConnector(),\n resolveTarget: createFirebaseResolveTarget(graph, serviceMap),\n }\n}\n","// The Cloudflare Workers/Pages connector (docs/connectors/cloudflare.md,\n// ADR-129). Owns the two provider-specific steps README.md's pipeline\n// diagram assigns to `connectors/<provider>/`: fetching signals off\n// Cloudflare's own API (`poll`, via client.ts + map.ts) and mapping a\n// signal's targetKind/targetName to a NEAT node id\n// (`createCloudflareResolveTarget`). Everything downstream — resolving a\n// static call site, minting the OBSERVED edge — is the shared pipeline in\n// connectors/index.ts; this module never mutates the graph itself (ADR-030 —\n// the honest-fallback case below declares a need via `ensureInfraNode`\n// instead of creating anything directly, docs/contracts/connectors.md §4a).\n\nimport { EdgeType, NodeType, fileId, infraId } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { normalizePathTemplate } from '../../extract/routes.js'\nimport type { ResolveConnectorTarget, ResolvedConnectorTarget } from '../index.js'\nimport type { ConnectorContext, ObservedConnector } from '../types.js'\nimport { queryWorkerInvocations } from './client.js'\nimport { mapEventToSignal } from './map.js'\nimport { CLOUDFLARE_TARGET_KIND, type CloudflareConnectorConfig, type CloudflareObservedSignal } from './types.js'\n\n// Cloudflare's docs don't confirm the Telemetry Query API's own max lookback\n// window (docs/connectors/cloudflare.md needs-endpoint-testing); a\n// conservative hour, same discipline connectors.md's \"Poll cadence and\n// backfill\" requires of every provider — a gap larger than this backfills\n// from `now - maxLookbackMs`, never an unbounded full-history query.\nconst DEFAULT_MAX_LOOKBACK_MS = 60 * 60 * 1000\n\nfunction resolveFromMs(since: string | undefined, maxLookbackMs: number | undefined): number {\n const cap = maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS\n const now = Date.now()\n const floor = now - cap\n if (!since) return floor\n const parsed = Date.parse(since)\n if (Number.isNaN(parsed)) return floor\n return Math.max(parsed, floor)\n}\n\nexport class CloudflareConnector implements ObservedConnector {\n readonly provider = 'cloudflare'\n\n constructor(private readonly config: CloudflareConnectorConfig) {}\n\n async poll(ctx: ConnectorContext): Promise<CloudflareObservedSignal[]> {\n const toMs = Date.now()\n const fromMs = resolveFromMs(ctx.since, this.config.maxLookbackMs)\n const events = await queryWorkerInvocations(ctx, this.config, { fromMs, toMs })\n\n const signals: CloudflareObservedSignal[] = []\n for (const event of events) {\n const signal = mapEventToSignal(event)\n if (signal) signals.push(signal)\n }\n return signals\n }\n}\n\n// Scan the live graph for the entry FileNode ADR-133's `extract/infra/\n// cloudflare.ts` tagged with this exact Worker script name\n// (`platform === 'cloudflare' && platformName === workerName`). A plain scan,\n// not a cached index — mirrors `reconcileObservedRelPath`'s own\n// `graph.forEachNode` walk (ingest.ts); per-project Worker counts are small\n// enough that this isn't worth a cache the live graph would have to\n// invalidate on every re-extraction.\nfunction findTaggedWorkerFileNode(graph: NeatGraph, workerName: string): string | null {\n let found: string | null = null\n graph.forEachNode((id, attrs) => {\n if (found) return\n const a = attrs as { type?: string; platform?: string; platformName?: string }\n if (a.type === NodeType.FileNode && a.platform === 'cloudflare' && a.platformName === workerName) {\n found = id\n }\n })\n return found\n}\n\n// Route-grain sharpening (ADR-133 §5): once a Hono (or future-recognized)\n// Worker's routes exist as RouteNodes, an invocation whose parsed\n// (method, path) matches one lands on that RouteNode instead of the whole\n// entry file — the same param-agnostic comparison `route-match.ts` already\n// uses for cross-service HTTP client↔route matching. No match (no route\n// recognizer covers this Worker's router, or the path doesn't line up) keeps\n// the existing whole-file target — sharpens automatically when the static\n// side supports it, stays honest when it doesn't.\nfunction findMatchingRouteNode(\n graph: NeatGraph,\n serviceName: string,\n method: string,\n path: string,\n): string | null {\n const normalizedPath = normalizePathTemplate(path)\n let found: string | null = null\n graph.forEachNode((id, attrs) => {\n if (found) return\n const a = attrs as { type?: string; service?: string; method?: string; pathTemplate?: string }\n if (a.type !== NodeType.RouteNode || a.service !== serviceName) return\n if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return\n const routeMethod = (a.method ?? '').toUpperCase()\n if (routeMethod !== 'ALL' && routeMethod !== method) return\n found = id\n })\n return found\n}\n\n// Provider-specific target resolution (README.md's pipeline step 2). Per\n// docs/connectors/cloudflare.md §Fusion, the signal's target is the Worker's\n// single entry FileNode by default — not the caller, since this API carries\n// no caller identity at all, only \"this script was invoked\". The edge that\n// results, `service --CALLS--> entryFile`, mirrors the WebSocket channel\n// precedent (ADR-125): a liveness signal minted from a service onto its own\n// child node, reusing an existing edge verb rather than inventing one for\n// \"this file got reached\".\n//\n// Resolution order (ADR-133 §3, docs/contracts/connectors.md §4a):\n// 1. `config.workers[scriptName]` — an explicit override, wins outright.\n// 2. The extracted graph's own `platform`/`platformName` tag — the derived\n// default, no config entry needed at all for a scanned project.\n// 3. Honest fallback — Cloudflare observed a Worker this scan never\n// declared. Lands a real edge via `ensureInfraNode` (surfacing as a\n// `missing-extracted` divergence) instead of a silent `null` drop.\n// Whichever of (1)/(2) resolves, a route-grain match (ADR-133 §5) is\n// attempted against that service's own RouteNodes before falling back to the\n// whole-file target — sharpening happens automatically once a static router\n// recognizer covers this Worker, never forced when it doesn't.\nexport function createCloudflareResolveTarget(\n config: CloudflareConnectorConfig,\n graph: NeatGraph,\n): ResolveConnectorTarget {\n return (signal): ResolvedConnectorTarget | null => {\n if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null\n const scriptName = signal.targetName\n const { method, path } = signal as CloudflareObservedSignal\n\n const resolveRouteGrain = (serviceName: string, wholeFileId: string): string => {\n if (!method || !path) return wholeFileId\n return findMatchingRouteNode(graph, serviceName, method, path) ?? wholeFileId\n }\n\n const mapping = config.workers?.[scriptName]\n if (mapping) {\n const wholeFileId = fileId(mapping.service, mapping.entryFile)\n return {\n targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),\n serviceName: mapping.service,\n edgeType: EdgeType.CALLS,\n }\n }\n\n const taggedFileId = findTaggedWorkerFileNode(graph, scriptName)\n if (taggedFileId) {\n const fileNode = graph.getNodeAttributes(taggedFileId) as { service: string }\n return {\n targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),\n serviceName: fileNode.service,\n edgeType: EdgeType.CALLS,\n }\n }\n\n return {\n targetNodeId: infraId('cloudflare-worker', scriptName),\n serviceName: scriptName,\n edgeType: EdgeType.CALLS,\n ensureInfraNode: { kind: 'cloudflare-worker', name: scriptName, provider: 'cloudflare' },\n }\n }\n}\n","// Workers Observability Telemetry Query API client (docs/connectors/cloudflare.md\n// §Surfaces used #1). Read-only, ambient — this issues exactly one query per\n// poll tick against telemetry Cloudflare already collects when a Worker\n// deploys with `observability.enabled`; it never issues a synthetic request\n// to the Worker itself (connectors.md §2).\n\nimport { randomUUID } from 'node:crypto'\nimport { bearerAuthHeader, junctionFetch } from '../junction.js'\nimport type { ConnectorContext } from '../types.js'\nimport type { CloudflareConnectorConfig, CloudflareTelemetryEvent, CloudflareTelemetryQueryResponse } from './types.js'\n\nconst DEFAULT_BASE_URL = 'https://api.cloudflare.com/client/v4'\n\n// The API's own per-response event cap isn't documented (needs-endpoint-\n// testing, docs/connectors/cloudflare.md); this is a conservative default\n// kept well under any plausible limit, overridable per connector config.\nconst DEFAULT_EVENT_LIMIT = 1000\n\nexport interface TelemetryWindow {\n fromMs: number\n toMs: number\n}\n\n// `fetchImpl` defaults to the platform global (Node 20 ships `fetch`) and is\n// the seam tests inject a fake response through — dependency injection, not\n// a production mock (contracts.md Rule 5 / connectors.md §5 only bar mocks\n// on the runtime poll path itself).\nexport async function queryWorkerInvocations(\n ctx: ConnectorContext,\n config: CloudflareConnectorConfig,\n window: TelemetryWindow,\n fetchImpl: typeof fetch = fetch,\n): Promise<CloudflareTelemetryEvent[]> {\n const token = ctx.credentials.apiToken\n if (typeof token !== 'string' || token.length === 0) {\n throw new Error('cloudflare connector: ctx.credentials.apiToken must be a non-empty string')\n }\n\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL\n const url = `${baseUrl}/accounts/${config.accountId}/workers/observability/telemetry/query`\n\n const body = {\n // Cloudflare's schema requires an identifier per query even for an\n // ad-hoc, unsaved one — a fresh id per tick, never reused.\n queryId: `neat-connector-${randomUUID()}`,\n timeframe: { from: window.fromMs, to: window.toMs },\n view: 'events',\n limit: config.eventLimit ?? DEFAULT_EVENT_LIMIT,\n // The inline query definition. WITHOUT `parameters`, the API treats\n // `queryId` as a reference to a previously *saved* query and answers 400\n // \"Query not found\" (confirmed against the live endpoint) — omitting it was\n // the reason the connector never worked against real Cloudflare. Naming the\n // `cloudflare-workers` dataset runs the invocation-telemetry query ad-hoc.\n parameters: { datasets: ['cloudflare-workers'] },\n // Execute without persisting — this is a read, not a saved query\n // (connectors.md §2's \"never writes on the read path\" applies to\n // Cloudflare's own query-history state too).\n dry: true,\n }\n\n const res = await junctionFetch(\n url,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...bearerAuthHeader(token),\n },\n body: JSON.stringify(body),\n },\n // accountKey: the Cloudflare account id (ADR-131's own worked example) —\n // the Telemetry Query API's ~300/5min limit is enforced per account.\n { provider: 'cloudflare', accountKey: config.accountId, fetchImpl },\n )\n\n if (!res.ok) {\n throw new Error(\n `cloudflare connector: telemetry query failed (${res.status} ${res.statusText})`,\n )\n }\n\n const payload = (await res.json()) as CloudflareTelemetryQueryResponse\n if (!payload.success) {\n const message = payload.errors?.map((e) => e.message).join('; ') || 'unknown error'\n throw new Error(`cloudflare connector: telemetry query returned an error (${message})`)\n }\n\n // `success: true` with no `result.events.events` array at all is shape\n // drift — a real API-contract change, not the ordinary \"no events this\n // window\" case (which arrives as an *empty* array here and warrants no\n // warning). Silently treating both the same way hid an API change behind a\n // quiet [] return; this distinguishes them so a real drift is loud. A\n // non-array `events` (drift handing back an object) is the same class of\n // change — caught here rather than crashing poll()'s `for..of` on it.\n const events = payload.result?.events?.events\n if (!Array.isArray(events)) {\n console.warn(\n '[neat connector] cloudflare: telemetry query returned success:true but no result.events.events array — the response shape may have changed; treating as zero events this tick',\n )\n return []\n }\n return events\n}\n","// Cloudflare Workers/Pages connector — provider-specific shapes\n// (docs/connectors/cloudflare.md, ADR-129; resolution rewired onto the\n// extracted graph's platform tag by ADR-133).\n\nimport type { ObservedSignal } from '../types.js'\n\n// Provider vocabulary for ObservedSignal.targetKind (connectors.md §1 — \"the\n// provider's own vocabulary\", same shape as Supabase's 'supabase-table' /\n// 'supabase-rpc').\nexport const CLOUDFLARE_TARGET_KIND = 'cloudflare-worker-invocation'\n\n// One Cloudflare Worker/Pages script's mapping onto a NEAT service + file.\n// ADR-133's `extract/infra/cloudflare.ts` now derives this pairing\n// automatically — it reads the Worker's `wrangler.toml`/`wrangler.jsonc`\n// `name` field and stamps it (`platformName`) on the entry FileNode\n// `createCloudflareResolveTarget` resolves against directly. This shape\n// survives as the explicit *override* for the config entries below: a repo\n// NEAT hasn't scanned, or a naming edge case auto-discovery can't cover.\nexport interface CloudflareWorkerMapping {\n service: string\n entryFile: string\n}\n\nexport interface CloudflareConnectorConfig {\n accountId: string\n // Explicit override, checked before the graph's own platform tag (see\n // createCloudflareResolveTarget in connector.ts). Optional — most projects\n // need no entry here at all now that extraction auto-discovers the pairing;\n // scripts with neither an override nor a tagged match fall back to an\n // honest placeholder InfraNode rather than being silently dropped.\n workers?: Record<string, CloudflareWorkerMapping>\n // Cap on how far an absent `since` backfills, ms. Cloudflare's docs don't\n // confirm the Telemetry Query API's own max lookback window\n // (docs/connectors/cloudflare.md flags this needs-endpoint-testing); default\n // below is a conservative hour, capped the same way connectors.md's\n // \"Poll cadence and backfill\" section requires for every provider.\n maxLookbackMs?: number\n // Events requested per query. The API's own per-response cap isn't\n // documented either; kept well under any plausible limit until a live\n // check confirms one. Pagination (the `offset` cursor) isn't implemented in\n // v1 — same needs-endpoint-testing gap, since the docs surveyed don't spell\n // out the cursor's exact semantics.\n eventLimit?: number\n // Override for tests; defaults to Cloudflare's real API host.\n baseUrl?: string\n}\n\n// --- Workers Observability Telemetry Query API response shapes ---\n//\n// Confirmed against\n// developers.cloudflare.com/api/resources/workers/subresources/observability/subresources/telemetry/methods/query/\n// (fetched 2026-07-03) rather than assumed. Only the fields this connector\n// actually reads are typed here — the live response carries substantially\n// more (the `traces`/`calculations`/`invocations` views, per-event\n// diagnostics-channel data, etc.) that v1 has no use for.\n//\n// Two fields name the Worker script per event: `$metadata.service` (present\n// on every event type) and `$workers.scriptName` (present only when the\n// `$workers` sub-object is, i.e. for genuine Workers Runtime events). Both\n// are documented as \"Worker script name\" — map.ts prefers `$workers.scriptName`\n// when present and falls back to `$metadata.service`.\nexport interface CloudflareTelemetryEventMetadata {\n service?: string\n trigger?: string\n url?: string\n statusCode?: number\n duration?: number\n traceDuration?: number\n startTime?: number\n endTime?: number\n}\n\nexport interface CloudflareTelemetryWorkersMetadata {\n eventType?: string\n scriptName?: string\n outcome?: string\n}\n\nexport interface CloudflareTelemetryEvent {\n timestamp?: number\n dataset?: string\n $metadata?: CloudflareTelemetryEventMetadata\n $workers?: CloudflareTelemetryWorkersMetadata\n}\n\nexport interface CloudflareTelemetryQueryResponse {\n success: boolean\n errors?: { message: string }[]\n messages?: { message: string }[]\n result?: {\n events?: {\n count?: number\n events: CloudflareTelemetryEvent[]\n }\n }\n}\n\n// ObservedSignal, widened with the fields this connector's own mapping step\n// carries — `method` and `path` (parsed from `trigger`), `statusCode`,\n// `duration`. None of these are read by the generic connectors pipeline\n// (connectors/index.ts only consumes the base ObservedSignal shape); `method`/\n// `statusCode`/`duration` exist for metadata/testing purposes, while `path` is\n// read by this provider's own `createCloudflareResolveTarget` (connector.ts)\n// to attempt a route-grain match against the Worker's RouteNodes (ADR-133 §5)\n// before falling back to the whole-file target ADR-129 shipped.\nexport interface CloudflareObservedSignal extends ObservedSignal {\n method: string\n path?: string\n statusCode?: number\n duration?: number\n}\n","// Maps one Telemetry Query API invocation record to an ObservedSignal\n// (docs/connectors/cloudflare.md §Fusion, ADR-129).\n//\n// The only route-shaped field this API exposes is `$metadata.trigger`\n// (\"GET /users\", \"POST /orders\", or a non-HTTP trigger like \"queue message\").\n// The leading HTTP method token is parsed off it, and — now that a static\n// route recognizer exists for at least one in-Worker router shape (Hono,\n// ADR-133 §5) — the remainder is carried as `path` too, so\n// `createCloudflareResolveTarget` (connector.ts) can attempt a route-grain\n// match against that Worker's own RouteNodes. When no match resolves, the\n// signal still fuses at whole-file grain — the same \"sharpens automatically\n// once the static side supports it\" pattern the rest of the connectors plane\n// already follows (route-match.ts's own client↔route matching). A trigger\n// that isn't HTTP-shaped (cron, queue, alarm, ...) carries no method to parse\n// and is out of scope for this cut (§Out of scope) — dropped here, honestly,\n// rather than fabricating a method or forcing it through as an unresolvable\n// signal.\n\nimport { CLOUDFLARE_TARGET_KIND, type CloudflareObservedSignal, type CloudflareTelemetryEvent } from './types.js'\n\n// The HTTP methods a `trigger` string's leading token can actually name\n// (RFC 7231 + CONNECT/TRACE). A closed vocabulary, the same discipline\n// routes.ts's own ROUTER_METHODS set applies to router registrations, so a\n// non-HTTP trigger (\"queue message\", a cron expression, \"scheduled\") never\n// misparses its own leading word as a method.\nconst HTTP_METHODS = new Set([\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'HEAD',\n 'OPTIONS',\n 'TRACE',\n 'CONNECT',\n])\n\nconst LEADING_TOKEN_RE = /^(\\S+)\\s+\\S/\n\nexport function parseHttpMethodFromTrigger(trigger: unknown): string | null {\n // `trigger` is typed a string upstream, but this maps a raw JSON response —\n // a shape drift that hands us a number or object here drops honestly rather\n // than throwing on `.trim()` (connectors.md §4 honest-miss discipline).\n if (typeof trigger !== 'string') return null\n const match = LEADING_TOKEN_RE.exec(trigger.trim())\n const token = match?.[1]\n if (!token) return null\n const method = token.toUpperCase()\n return HTTP_METHODS.has(method) ? method : null\n}\n\n// The path portion of an HTTP-shaped trigger — everything after the leading\n// method token, e.g. \"GET /users/123\" → \"/users/123\". Only called once\n// `parseHttpMethodFromTrigger` has already confirmed the leading token is a\n// real method, so there's no risk of misreading a non-HTTP trigger's own\n// text as a path. A query string, if present, rides along; route matching\n// canonicalises it away the same way a declared RouteNode template does\n// (routes.ts's `canonicalizeTemplate`).\nexport function parsePathFromTrigger(trigger: string): string | undefined {\n const trimmed = trigger.trim()\n const spaceIdx = trimmed.indexOf(' ')\n if (spaceIdx === -1) return undefined\n const rest = trimmed.slice(spaceIdx + 1).trim()\n return rest.length > 0 ? rest : undefined\n}\n\n// 5xx is treated as the unambiguous failure threshold for this connector's\n// own errorCount, mirroring the span-derived `isError` convention elsewhere\n// in ingest.ts (a bare 4xx is often correct app behavior — an auth probe, a\n// conditional fetch — and isn't held against the edge's own error tally).\nconst ERROR_STATUS_THRESHOLD = 500\n\nexport function mapEventToSignal(event: CloudflareTelemetryEvent | null | undefined): CloudflareObservedSignal | null {\n // A shape-drifted response can hand a poll tick a null/garbage array slot;\n // drop it honestly rather than throwing on `.$metadata` (connectors.md §4).\n if (!event || typeof event !== 'object') return null\n const metadata = event.$metadata\n const workers = event.$workers\n\n const method = parseHttpMethodFromTrigger(metadata?.trigger)\n if (!method) return null\n\n // `$workers.scriptName` and `$metadata.service` are both documented as\n // \"Worker script name\" — prefer the Workers-Runtime-specific field when\n // present, fall back to the always-present metadata field. A non-string\n // value (shape drift) drops honestly rather than riding through as a\n // non-string targetName the graph would then have to carry.\n const scriptName = workers?.scriptName ?? metadata?.service\n if (typeof scriptName !== 'string' || scriptName.length === 0) return null\n\n const timestampMs = event.timestamp ?? metadata?.startTime\n if (typeof timestampMs !== 'number' || !Number.isFinite(timestampMs)) return null\n // A finite-but-out-of-range epoch (a value in ns/µs, or a bogus one) makes\n // an Invalid Date whose `.toISOString()` throws — drop it honestly rather\n // than fabricating a timestamp or crashing the whole tick.\n const observedAt = new Date(timestampMs)\n if (Number.isNaN(observedAt.getTime())) return null\n\n const statusCode = metadata?.statusCode\n const isError = typeof statusCode === 'number' && statusCode >= ERROR_STATUS_THRESHOLD\n const path = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : undefined\n\n return {\n targetKind: CLOUDFLARE_TARGET_KIND,\n targetName: scriptName,\n callCount: 1,\n errorCount: isError ? 1 : 0,\n lastObservedIso: observedAt.toISOString(),\n method,\n ...(path ? { path } : {}),\n ...(typeof statusCode === 'number' ? { statusCode } : {}),\n ...(typeof metadata?.duration === 'number' ? { duration: metadata.duration } : {}),\n }\n}\n","// Vercel Drains REST API client (docs/contracts/connectors.md §9, ADR-146).\n// Three calls, all through the shared junction (ADR-131) with a `vercel` rate\n// bucket and the `fetchImpl` DI seam every connector uses for tests:\n//\n// createDrain POST /v1/drains provision the trace drain\n// deleteDrain DELETE /v1/drains/{id} deprovision it (idempotent)\n// testDelivery POST /v1/drains/test validate config + endpoint reachability\n//\n// Unlike a pull client this issues no telemetry query — it configures the\n// provider to push. The drain forwards traces as OTLP/HTTP `encoding: 'json'`\n// (`{ resourceSpans: [...] }`) to `config.endpoint`, which the daemon's\n// `/v1/traces` receiver decodes on its `application/json` path (otel.ts).\n\nimport { bearerAuthHeader, junctionFetch } from '../junction.js'\nimport type {\n VercelConnectorConfig,\n VercelCredentials,\n VercelDrainCreated,\n VercelDrainTestResult,\n} from './types.js'\n\nconst DEFAULT_API_BASE_URL = 'https://api.vercel.com'\nconst DEFAULT_DRAIN_NAME = 'neat-otlp'\n\n// `schemas: { trace: { version: 'v1' } }` is how POST /v1/drains selects\n// distributed traces in OpenTelemetry format (verified against the live API).\nconst TRACE_SCHEMAS = { trace: { version: 'v1' } } as const\n\nfunction apiBase(config: VercelConnectorConfig): string {\n return config.apiBaseUrl ?? DEFAULT_API_BASE_URL\n}\n\n// teamId rides as a query param on every Drains call (Drains are team-scoped).\nfunction teamQuery(config: VercelConnectorConfig): string {\n return `?teamId=${encodeURIComponent(config.teamId)}`\n}\n\n// The delivery block, shared by create and test so both describe the exact same\n// sink. `type: 'http'` + `encoding: 'json'` is the custom-endpoint OTLP/JSON\n// variant (verified live: `type` is a per-variant constant, `encoding` for a\n// trace drain is `json`/`ndjson`). The daemon's OTLP bearer travels as a custom\n// delivery header — the receiver requires it (ADR-073 §4).\nfunction drainDelivery(config: VercelConnectorConfig, otelToken: string): Record<string, unknown> {\n return {\n type: 'http',\n endpoint: config.endpoint,\n encoding: 'json',\n headers: bearerAuthHeader(otelToken),\n ...(config.secret ? { secret: config.secret } : {}),\n }\n}\n\n// Pull a short, secret-free message out of a Vercel error body (`{ error: {\n// code, message } }`). The message names validation/HTTP detail — never the\n// token, which only ever rode in the Authorization header.\nasync function describeError(res: Response): Promise<string> {\n try {\n const data = (await res.json()) as { error?: { message?: unknown } }\n const message = data?.error?.message\n return typeof message === 'string' && message.length > 0 ? ` — ${message}` : ''\n } catch {\n return ''\n }\n}\n\n/**\n * Provision the trace drain. Returns the created drain's id (stored back into\n * the entry's `options.drainId` so `remove` can tear it down) and its status.\n * Throws on any non-2xx or a response missing the id — a missing id is shape\n * drift, not a silent success (the #841 discipline).\n */\nexport async function createVercelDrain(\n config: VercelConnectorConfig,\n credentials: VercelCredentials,\n fetchImpl: typeof fetch = fetch,\n): Promise<VercelDrainCreated> {\n const projectIds = config.projectIds ?? []\n const body = {\n name: config.drainName ?? DEFAULT_DRAIN_NAME,\n projects: projectIds.length > 0 ? 'some' : 'all',\n ...(projectIds.length > 0 ? { projectIds } : {}),\n schemas: TRACE_SCHEMAS,\n delivery: drainDelivery(config, credentials.otelToken),\n source: { kind: 'self-served' },\n }\n\n const res = await junctionFetch(\n `${apiBase(config)}/v1/drains${teamQuery(config)}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...bearerAuthHeader(credentials.token) },\n body: JSON.stringify(body),\n },\n { provider: 'vercel', accountKey: config.teamId, fetchImpl },\n )\n\n if (!res.ok) {\n throw new Error(\n `vercel connector: create drain failed (${res.status} ${res.statusText}${await describeError(res)})`,\n )\n }\n const payload = (await res.json().catch(() => null)) as Partial<VercelDrainCreated> | null\n if (!payload || typeof payload.id !== 'string' || payload.id.length === 0) {\n throw new Error(\n 'vercel connector: create drain returned no drain id — the Drains API response shape may have changed',\n )\n }\n return {\n id: payload.id,\n ...(payload.status ? { status: payload.status } : {}),\n ...(payload.disabledReason ? { disabledReason: payload.disabledReason } : {}),\n }\n}\n\n/**\n * Tear the drain down. Idempotent by contract (connectors.md §9): a 404 —\n * already gone — is success, not an error, so a re-run of `remove` (or a drain\n * a human deleted in the dashboard) never wedges.\n */\nexport async function deleteVercelDrain(\n config: VercelConnectorConfig,\n drainId: string,\n credentials: VercelCredentials,\n fetchImpl: typeof fetch = fetch,\n): Promise<void> {\n const res = await junctionFetch(\n `${apiBase(config)}/v1/drains/${encodeURIComponent(drainId)}${teamQuery(config)}`,\n { method: 'DELETE', headers: { ...bearerAuthHeader(credentials.token) } },\n { provider: 'vercel', accountKey: config.teamId, fetchImpl },\n )\n if (res.ok || res.status === 404) return\n throw new Error(\n `vercel connector: delete drain failed (${res.status} ${res.statusText}${await describeError(res)})`,\n )\n}\n\n/**\n * Validate the drain configuration without provisioning anything. Vercel's\n * `POST /v1/drains/test` both authenticates the API token and pings the\n * endpoint with a sample event, so a `status: 'success'` verdict means the\n * token is live *and* the daemon's OTLP endpoint is reachable and accepted the\n * drain's bearer. A bad token (401/403) and a bad request (4xx) are mapped to\n * `failure` with a distinct, secret-free reason; a reachable-but-rejecting\n * endpoint comes back in the 200 body's own `status`/`error`.\n */\nexport async function testVercelDrainDelivery(\n config: VercelConnectorConfig,\n credentials: VercelCredentials,\n fetchImpl: typeof fetch = fetch,\n): Promise<VercelDrainTestResult> {\n const res = await junctionFetch(\n `${apiBase(config)}/v1/drains/test${teamQuery(config)}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...bearerAuthHeader(credentials.token) },\n body: JSON.stringify({ schemas: TRACE_SCHEMAS, delivery: drainDelivery(config, credentials.otelToken) }),\n },\n { provider: 'vercel', accountKey: config.teamId, fetchImpl },\n )\n\n if (res.status === 401 || res.status === 403) {\n return { status: 'failure', error: `vercel rejected the API token (HTTP ${res.status})` }\n }\n if (!res.ok) {\n return {\n status: 'failure',\n error: `vercel drain validation failed (${res.status} ${res.statusText}${await describeError(res)})`,\n }\n }\n const payload = (await res.json().catch(() => null)) as VercelDrainTestResult | null\n return {\n ...(payload?.status ? { status: payload.status } : {}),\n ...(payload?.error ? { error: payload.error } : {}),\n ...(payload?.endpoint ? { endpoint: payload.endpoint } : {}),\n }\n}\n","import pg from 'pg'\nimport { dbJunction } from '../junction.js'\nimport type { NeonStatementRow } from './types.js'\n\nconst { Client } = pg\n\nexport const DEFAULT_NEON_STATEMENT_LIMIT = 500\n\nconst NEON_STATEMENTS_QUERY = `\n select queryid::text, query, calls\n from pg_stat_statements\n where query ~* '^\\\\s*(select|insert|update|delete)\\\\b'\n order by calls desc\n limit $1\n`\n\nexport interface NeonPgClientLike {\n connect(): Promise<void>\n query<T>(text: string, values?: unknown[]): Promise<{ rows: T[] }>\n end(): Promise<void>\n}\n\nexport async function fetchNeonStatements(\n connectionString: string,\n projectId: string,\n limit: number = DEFAULT_NEON_STATEMENT_LIMIT,\n clientFactory: (connectionString: string) => NeonPgClientLike = (value) =>\n new Client({ connectionString: value }),\n): Promise<NeonStatementRow[]> {\n return dbJunction(\n async () => {\n const client = clientFactory(connectionString)\n await client.connect()\n try {\n await client.query('SET default_transaction_read_only = on')\n const result = await client.query<NeonStatementRow>(NEON_STATEMENTS_QUERY, [\n Math.max(1, Math.trunc(limit) || DEFAULT_NEON_STATEMENT_LIMIT),\n ])\n return result.rows\n } finally {\n await client.end()\n }\n },\n { provider: 'neon', accountKey: projectId },\n )\n}\n","export const NEON_SQL_TABLE_TARGET_KIND = 'sql-table'\n\nexport interface NeonConnectorConfig {\n projectId: string\n serviceName: string\n statementLimit?: number\n}\n\nexport interface NeonCredentials {\n connectionString: string\n}\n\nexport interface NeonStatementRow {\n queryid: string\n query: string\n calls: string | number\n}\n\nexport function readNeonCredentials(input: Record<string, unknown>): NeonCredentials {\n const connectionString = input.connectionString\n if (typeof connectionString !== 'string' || connectionString.length === 0) {\n throw new Error('neon connector: credentials.connectionString is required')\n }\n return { connectionString }\n}\n","import { columnsFromSqlStatement, tableFromSqlStatement } from '../../otel.js'\nimport type { ObservedSignal } from '../types.js'\nimport { NEON_SQL_TABLE_TARGET_KIND, type NeonStatementRow } from './types.js'\n\nexport interface NeonStatementBaseline {\n calls: number\n}\n\nexport function diffNeonStatementsToSignals(\n rows: NeonStatementRow[],\n previous: Map<string, NeonStatementBaseline>,\n observedAtIso: string,\n): ObservedSignal[] {\n const signals: ObservedSignal[] = []\n const seen = new Set<string>()\n if (!Array.isArray(rows)) return signals\n\n for (const row of rows) {\n if (\n !row ||\n typeof row !== 'object' ||\n typeof row.queryid !== 'string' ||\n typeof row.query !== 'string'\n ) {\n continue\n }\n const calls = Number(row.calls)\n if (!Number.isFinite(calls)) continue\n\n seen.add(row.queryid)\n const prior = previous.get(row.queryid)\n previous.set(row.queryid, { calls })\n if (!prior || calls <= prior.calls) continue\n\n const table = tableFromSqlStatement(row.query)\n if (!table) continue\n // ADR-157 — the same query text names the columns it touched; carry them so\n // the shared pipeline merges them onto the `sql-table` node as OBSERVED\n // column attributes. Empty for a shape the parser degrades on.\n const columns = columnsFromSqlStatement(row.query)\n signals.push({\n targetKind: NEON_SQL_TABLE_TARGET_KIND,\n targetName: table,\n callCount: calls - prior.calls,\n errorCount: 0,\n lastObservedIso: observedAtIso,\n ...(columns.length > 0 ? { columns } : {}),\n })\n }\n\n for (const queryid of previous.keys()) {\n if (!seen.has(queryid)) previous.delete(queryid)\n }\n return signals\n}\n","import { EdgeType, infraId } from '@neat.is/types'\nimport type { ResolveConnectorTarget } from '../index.js'\nimport { NEON_SQL_TABLE_TARGET_KIND, type NeonConnectorConfig } from './types.js'\n\nexport function createNeonResolveTarget(config: NeonConnectorConfig): ResolveConnectorTarget {\n return (signal) => {\n if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null\n return {\n targetNodeId: infraId('sql-table', signal.targetName),\n serviceName: config.serviceName,\n edgeType: EdgeType.CALLS,\n ensureInfraNode: { kind: 'sql-table', name: signal.targetName, provider: 'neon' },\n }\n }\n}\n","import type { ResolveConnectorTarget } from '../index.js'\nimport type { ConnectorContext, ObservedConnector, ObservedSignal } from '../types.js'\nimport { DEFAULT_NEON_STATEMENT_LIMIT, fetchNeonStatements } from './client.js'\nimport { diffNeonStatementsToSignals, type NeonStatementBaseline } from './map.js'\nimport { createNeonResolveTarget } from './resolve.js'\nimport { readNeonCredentials, type NeonConnectorConfig } from './types.js'\n\nexport * from './client.js'\nexport * from './map.js'\nexport * from './resolve.js'\nexport * from './types.js'\n\nexport interface NeonConnectorDeps {\n fetchStatements?: typeof fetchNeonStatements\n now?: () => Date\n}\n\nexport class NeonConnector implements ObservedConnector {\n readonly provider = 'neon'\n private readonly baselines = new Map<string, NeonStatementBaseline>()\n\n constructor(\n private readonly config: NeonConnectorConfig,\n private readonly deps: NeonConnectorDeps = {},\n ) {}\n\n async poll(ctx: ConnectorContext): Promise<ObservedSignal[]> {\n const credentials = readNeonCredentials(ctx.credentials)\n const fetchStatements = this.deps.fetchStatements ?? fetchNeonStatements\n const rows = await fetchStatements(\n credentials.connectionString,\n this.config.projectId,\n this.config.statementLimit ?? DEFAULT_NEON_STATEMENT_LIMIT,\n )\n return diffNeonStatementsToSignals(\n rows,\n this.baselines,\n (this.deps.now?.() ?? new Date()).toISOString(),\n )\n }\n}\n\nexport function createNeonConnector(\n config: NeonConnectorConfig,\n deps: NeonConnectorDeps = {},\n): { connector: ObservedConnector; resolveTarget: ResolveConnectorTarget } {\n return {\n connector: new NeonConnector(config, deps),\n resolveTarget: createNeonResolveTarget(config),\n }\n}\n","// Cloud Logging `entries.list` — the fetch half of the Cloud Run connector\n// (docs/connectors/cloud-run.md, ADR-165). This file owns the wire shape and\n// the HTTP call; mapping a `LogEntry` to an `ObservedSignal` lives in map.ts,\n// and target resolution in resolve.ts — the same fetch/map/resolve split every\n// other provider module keeps (connectors.md §Authority). Passive and ambient\n// only (connectors.md §2): one read-only query, never a mutation, never a\n// synthetic request.\n//\n// Every field name below was confirmed live against Google's own docs rather\n// than recalled from training data (ADR-165 §Context, ADR-150/152 discipline):\n// - request/response envelope + endpoint:\n// https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/list\n// - LogEntry + HttpRequest field names:\n// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry\n// - cloud_run_revision monitored-resource labels:\n// https://cloud.google.com/logging/docs/api/v2/resource-list\n// - the request log name (run.googleapis.com%2Frequests) + auto-emission:\n// https://cloud.google.com/run/docs/logging\n\nimport { bearerAuthHeader, junctionFetch } from '../junction.js'\nimport type { CloudRunCredentials } from './types.js'\n\n// ── Cloud Logging wire types ─────────────────────────────────────────────\n\n// MonitoredResource (LogEntry.resource). For a Cloud Run request log the type\n// is `cloud_run_revision` and `labels` carries service_name / revision_name /\n// location / configuration_name / project_id (resource-list docs above). This\n// connector reads `service_name`.\nexport interface MonitoredResource {\n type: string\n labels?: Record<string, string>\n}\n\n// HttpRequest (LogEntry.httpRequest), field names confirmed against the\n// LogEntry reference page. Every field is optional in the wire type — a request\n// log is expected to carry requestMethod/requestUrl/status, but nothing here is\n// guaranteed present, so the mapper (map.ts) treats an absent field as an\n// honest miss rather than guessing a default.\nexport interface HttpRequest {\n requestMethod?: string\n requestUrl?: string\n requestSize?: string\n status?: number\n responseSize?: string\n userAgent?: string\n remoteIp?: string\n serverIp?: string\n referer?: string\n latency?: string\n protocol?: string\n}\n\n// LogEntry, the subset this connector reads. `timestamp` is the request's own\n// event time (a string RFC3339/Timestamp field) — used as\n// `ObservedSignal.lastObservedIso`, never `receiveTimestamp` (ingest time),\n// matching the \"provider's own event time, never poll-arrival time\" rule\n// (connectors/README.md §Poll cadence and backfill).\nexport interface LogEntry {\n logName?: string\n resource?: MonitoredResource\n timestamp?: string\n receiveTimestamp?: string\n severity?: string\n insertId?: string\n httpRequest?: HttpRequest\n}\n\nexport interface EntriesListRequest {\n resourceNames: string[]\n filter?: string\n orderBy?: string\n pageSize?: number\n pageToken?: string\n}\n\nexport interface EntriesListResponse {\n entries?: LogEntry[]\n nextPageToken?: string\n}\n\n// ── filter construction ──────────────────────────────────────────────────\n\n// The Cloud Run request log's own name (confirmed at cloud.google.com/run/docs/\n// logging): `projects/<id>/logs/run.googleapis.com%2Frequests`. The `/` inside\n// the log id is URL-encoded `%2F`, and the Logging query-language filter carries\n// that encoded form. Pinning the log name is what separates this connector from\n// Firebase's broader `cloud_run_revision` sweep — it reads exactly Cloud Run's\n// own request record, never an app's stdout/stderr on the same resource.\nexport function cloudRunRequestLogName(projectId: string): string {\n return `projects/${projectId}/logs/run.googleapis.com%2Frequests`\n}\n\n// Cloud Logging query-language operators confirmed against\n// https://cloud.google.com/logging/docs/view/logging-query-language:\n// - `logName = \"...\"` pins the request log\n// - `resource.type = \"...\"` pins the monitored resource\n// - `httpRequest.requestMethod != \"\"` requires a request (an httpRequest that\n// actually carries a method), rather than any entry on the resource\n// - `timestamp >= \"<RFC3339>\"` for the watermark lower bound\n// Joined with explicit `AND`.\nexport function buildCloudRunEntriesFilter(projectId: string, sinceIso: string): string {\n return [\n `logName = \"${cloudRunRequestLogName(projectId)}\"`,\n `resource.type = \"${CLOUD_RUN_RESOURCE_TYPE}\"`,\n 'httpRequest.requestMethod != \"\"',\n `timestamp >= \"${sinceIso}\"`,\n ].join(' AND ')\n}\n\nconst CLOUD_RUN_RESOURCE_TYPE = 'cloud_run_revision'\n\n// No documented lookback cap surfaced for `entries.list` itself (bounded only\n// by the log bucket's retention, typically 30 days on `_Default`) — a\n// conservative 24h default for a first poll with no prior high-water mark, the\n// same convention Firebase and Supabase keep, never an unbounded first query\n// (connectors.md \"Poll cadence and backfill\").\nexport const DEFAULT_LOOKBACK_MS = 24 * 60 * 60 * 1000\n\nconst ENTRIES_LIST_URL = 'https://logging.googleapis.com/v2/entries:list'\nconst PAGE_SIZE = 1000\n// Defensive cap on pagination — entries.list documents no page-count limit, so\n// this bounds the loop rather than trusting an unbounded while(true), the same\n// way STITCH_MAX_DEPTH bounds trace-stitch BFS elsewhere.\nconst MAX_PAGES = 20\n\nexport async function fetchCloudRunRequestLogEntries(\n creds: CloudRunCredentials,\n sinceIso: string,\n apiUrl: string = ENTRIES_LIST_URL,\n): Promise<LogEntry[]> {\n const filter = buildCloudRunEntriesFilter(creds.projectId, sinceIso)\n const out: LogEntry[] = []\n let pageToken: string | undefined\n for (let page = 0; page < MAX_PAGES; page++) {\n const body: EntriesListRequest = {\n resourceNames: [`projects/${creds.projectId}`],\n filter,\n orderBy: 'timestamp asc',\n pageSize: PAGE_SIZE,\n ...(pageToken ? { pageToken } : {}),\n }\n const res = await junctionFetch(\n apiUrl,\n {\n method: 'POST',\n headers: {\n ...bearerAuthHeader(creds.accessToken),\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n },\n // accountKey: the GCP project id — one customer's Cloud Logging quota is\n // scoped per GCP project (ADR-131's per-(provider, accountKey) rate-limit\n // bucket), the same key Firebase's connector uses.\n { provider: 'cloud-run', accountKey: creds.projectId },\n )\n if (!res.ok) {\n throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`)\n }\n const json = (await res.json()) as EntriesListResponse\n // A well-formed page carries an array (absent when the window is empty); a\n // shape drift handing back a non-array `entries` drops honestly rather than\n // throwing on `push(...nonIterable)` (connectors.md §4).\n if (Array.isArray(json.entries)) out.push(...json.entries)\n if (!json.nextPageToken) break\n pageToken = json.nextPageToken\n }\n return out\n}\n","// Cloud Run connector — provider-specific types (docs/connectors/cloud-run.md,\n// ADR-165). Sixth connectors-plane pull provider, following the\n// hosting-platform pattern ADR-127 (Railway) established and ADR-128 (Firebase)\n// already applies to `cloud_run_revision` request logs.\n//\n// Cloud Run names nothing in application code: the signal comes entirely from\n// Google's own request-logging pipeline (Cloud Logging), so fusion binds onto\n// the RouteNode `extract/routes.ts` already builds — the same node an OBSERVED\n// server span would fuse onto if the app were OTel-instrumented.\n\n// ── credentials ────────────────────────────────────────────────────────────\n\n// `ConnectorContext.credentials` is opaque at the shared-scaffold layer\n// (connectors/types.ts) — this is the Cloud-Run-specific shape it must carry.\n// `projectId` is not a secret (it names the GCP project); `accessToken` is a\n// short-lived OAuth token scoped to `roles/logging.viewer`, minted from a\n// service-account key or ADC. `poll()` only ever consumes an already-minted\n// token, never performing its own auth handshake — the same way Firebase's\n// connector consumes an already-minted `accessToken` and Railway's an\n// already-minted `Project-Access-Token` (docs/connectors/cloud-run.md\n// §Credential + least privilege, connectors.md §3).\nexport interface CloudRunCredentials {\n projectId: string\n accessToken: string\n}\n\nexport function readCloudRunCredentials(raw: Record<string, unknown>): CloudRunCredentials {\n const projectId = raw['projectId']\n const accessToken = raw['accessToken']\n if (typeof projectId !== 'string' || projectId.length === 0) {\n throw new Error('cloud-run connector: credentials.projectId must be a non-empty string')\n }\n if (typeof accessToken !== 'string' || accessToken.length === 0) {\n throw new Error('cloud-run connector: credentials.accessToken must be a non-empty string')\n }\n return { projectId, accessToken }\n}\n\n// ── config ─────────────────────────────────────────────────────────────────\n\n/**\n * Config resolved once at connector setup (docs/connectors/cloud-run.md\n * §Fusion, \"resolved once, never guessed\") — never re-derived from a Cloud\n * Logging response at poll time.\n */\nexport interface CloudRunConnectorConfig {\n // GCP `service_name` (resource.labels.service_name) -> the NEAT manifest\n // service name that resolves `serviceId(name)` (packages/types/src/identity.ts).\n // A Cloud Run service name need not match `package.json#name`, so the mapping\n // is supplied once at setup, never inferred from a log record. An unmapped\n // service still produces honest service-grained edges (resolve.ts tier 2),\n // so this is optional — a connector added before the map is filled stays\n // coarse rather than silent.\n serviceMap?: Record<string, string>\n // Bounded lookback cap in ms for a first poll (no prior `since`) or a gap\n // wider than this window. Overridable; defaults to 24h\n // (connectors.md \"Poll cadence and backfill\").\n maxLookbackMs?: number\n // Cloud Logging API base. Defaults to the public endpoint; overridable for\n // tests and any proxy in front of it.\n apiUrl?: string\n}\n\n// ── target identity (the provider's own vocabulary) ─────────────────────────\n\n// A signal's `targetKind` for this provider (connectors/types.ts's\n// ObservedSignal doc: \"the provider's own vocabulary\" — the shared pipeline\n// never inspects this string itself). Cloud Run has exactly one monitored\n// resource type for request logs, so this is a single constant rather than a\n// set the way Firebase carries three.\nexport const CLOUD_RUN_TARGET_KIND = 'cloud_run_revision'\n\n// The (service_name, method, path) identity a signal carries through the shared\n// pipeline, packed into ObservedSignal.targetName (a plain string field) and\n// unpacked only by this connector's own resolveTarget (resolve.ts), never\n// surfaced to the graph. `\\x00` is the separator: it cannot appear in an HTTP\n// method token, a GCP service name, or a URL path.\nconst FIELD_SEP = '\\x00'\n\nexport interface CloudRunTargetIdentity {\n serviceName: string\n method: string\n path: string\n}\n\nexport function packCloudRunTargetName(identity: CloudRunTargetIdentity): string {\n return [identity.serviceName, identity.method, identity.path].join(FIELD_SEP)\n}\n\nexport function parseCloudRunTargetName(targetName: string): CloudRunTargetIdentity | null {\n // Split on the first two separators only — a path (always the last field)\n // carries no separator in practice, but splitting non-greedily keeps this\n // correct even if it ever did.\n const firstSep = targetName.indexOf(FIELD_SEP)\n if (firstSep === -1) return null\n const serviceName = targetName.slice(0, firstSep)\n const rest = targetName.slice(firstSep + 1)\n const secondSep = rest.indexOf(FIELD_SEP)\n if (secondSep === -1) return null\n const method = rest.slice(0, secondSep)\n const path = rest.slice(secondSep + 1)\n if (!serviceName || !method || !path) return null\n return { serviceName, method, path }\n}\n","// LogEntry -> ObservedSignal mapping (docs/connectors/cloud-run.md, ADR-165).\n// One signal per request-log entry — no aggregation — so each concrete request\n// stays traceable back to the raw log line it came from; the shared pipeline\n// (connectors/index.ts) already replays a signal's callCount/errorCount as\n// individual upserts, so nothing is lost by keeping this 1:1.\n\nimport type { ObservedSignal } from '../types.js'\nimport type { LogEntry } from './client.js'\nimport { CLOUD_RUN_TARGET_KIND, packCloudRunTargetName } from './types.js'\n\n// Only the Cloud Run request-log resource. The filter (client.ts) already pins\n// `resource.type = \"cloud_run_revision\"`, but the mapper never trusts the\n// filter alone — a shape drift or a hand-supplied fixture entry of another\n// resource type drops honestly rather than being mis-attributed.\nconst CLOUD_RUN_RESOURCE_TYPE = 'cloud_run_revision'\n\n// `httpRequest.requestUrl` is documented as \"typically without the scheme,\n// host, port, and query portion\" — usually already a bare path — but a\n// deployment that logs a full absolute URL is handled too. Returns null when\n// the value is neither a bare path nor a parseable absolute URL, the same\n// \"honest miss, never guessed\" discipline `pathOf` uses in\n// extract/calls/route-match.ts.\nfunction pathFromRequestUrl(requestUrl: unknown): string | null {\n // Typed a string upstream, but this reads a raw Cloud Logging record — a\n // shape drift handing a number/object here drops honestly rather than\n // throwing on `.startsWith` (connectors.md §4).\n if (typeof requestUrl !== 'string' || requestUrl.length === 0) return null\n if (requestUrl.startsWith('/')) {\n const withoutQuery = requestUrl.split('?')[0]\n return withoutQuery && withoutQuery.length > 0 ? withoutQuery : '/'\n }\n try {\n const candidate = requestUrl.startsWith('//') ? `https:${requestUrl}` : requestUrl\n const parsed = new URL(candidate)\n return parsed.pathname || '/'\n } catch {\n return null\n }\n}\n\n// A response is counted as an error at the 5xx threshold — the same\n// unambiguous-failure line ingest.ts draws for a failing HTTP response. A 4xx\n// is a plausible client error, not necessarily a service defect, so it isn't\n// counted here.\nconst ERROR_STATUS_THRESHOLD = 500\n\n// Maps one Cloud Logging LogEntry to one ObservedSignal. Returns null for an\n// entry this connector can't honestly attribute — a non-Cloud-Run resource, a\n// resource with no `service_name` label, or an httpRequest missing the\n// method/path a signal needs. Nothing here is guessed or fabricated.\nexport function mapLogEntryToSignal(entry: LogEntry | null | undefined): ObservedSignal | null {\n // A shape-drifted response can carry a null/garbage slot; drop it honestly\n // rather than throwing on `.resource` (connectors.md §4).\n if (!entry || typeof entry !== 'object') return null\n if (entry.resource?.type !== CLOUD_RUN_RESOURCE_TYPE) return null\n\n const serviceName = entry.resource?.labels?.['service_name']\n if (typeof serviceName !== 'string' || serviceName.length === 0) return null\n\n const req = entry.httpRequest\n if (!req) return null\n if (typeof req.requestMethod !== 'string' || req.requestMethod.length === 0) return null\n const method = req.requestMethod.toUpperCase()\n const path = pathFromRequestUrl(req.requestUrl)\n if (path === null) return null\n\n const timestamp = entry.timestamp\n if (typeof timestamp !== 'string' || timestamp.length === 0) return null\n\n const isError = typeof req.status === 'number' && req.status >= ERROR_STATUS_THRESHOLD\n\n return {\n targetKind: CLOUD_RUN_TARGET_KIND,\n targetName: packCloudRunTargetName({ serviceName, method, path }),\n callCount: 1,\n errorCount: isError ? 1 : 0,\n lastObservedIso: timestamp,\n }\n}\n\nexport function mapLogEntriesToSignals(entries: LogEntry[]): ObservedSignal[] {\n const out: ObservedSignal[] = []\n for (const entry of entries) {\n const signal = mapLogEntryToSignal(entry)\n if (signal) out.push(signal)\n }\n return out\n}\n","// Target resolution — the Cloud-Run-specific half of the pull/map/fuse split\n// (connectors.md §Authority): turning a signal's (service_name, method, path)\n// into a NEAT node id. Two tiers, in order (docs/connectors/cloud-run.md\n// §Fusion):\n//\n// 1. Route grain — map the GCP `service_name` to a NEAT manifest service via\n// the config-time `serviceMap` (resolved once, never guessed), then match\n// (method, path) against a RouteNode that service declares, using the same\n// path-template normalisation extract/routes.ts uses. A match lands the\n// OBSERVED CALLS edge on that RouteNode; the shared pipeline sharpens it to\n// the route's own definition file/line (ADR-143). This file precision is\n// drawn from the STATIC route definition, not a runtime code.* stamp.\n// 2. Service grain, honestly — no static route resolves. The connector does\n// not fabricate a route. It declares an honest fallback via\n// `ensureInfraNode` (connectors.md §4a, ADR-133): the edge lands on\n// `infraId('cloud-run-service', service_name)`, the Cloud Run service the\n// log's own `resource.labels.service_name` names — a real platform\n// resource — surfacing as a `missing-extracted` divergence rather than a\n// silent drop. An unmapped service sources the edge from that service's own\n// name, so a connector added before the serviceMap is filled stays coarse\n// rather than silent.\n//\n// This module never mutates the graph (ADR-030) — it reads RouteNode attributes\n// already there and declares the infra-node need for the shared pipeline to\n// enact, matching every other file in packages/core/src/connectors/**.\n\nimport { EdgeType, NodeType, infraId, type RouteNode } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { normalizePathTemplate } from '../../extract/routes.js'\nimport type { ResolveConnectorTarget, ResolvedConnectorTarget } from '../index.js'\nimport {\n CLOUD_RUN_TARGET_KIND,\n parseCloudRunTargetName,\n type CloudRunConnectorConfig,\n} from './types.js'\n\n// The InfraNode kind the tier-2 honest fallback lands on. A future Cloud Run\n// service-manifest extractor (the analog of Cloudflare's\n// extract/infra/cloudflare.ts) should mint the same `infraId('cloud-run-service',\n// name)` so declared and observed fuse rather than twin.\nconst CLOUD_RUN_SERVICE_INFRA_KIND = 'cloud-run-service'\n\n// Scan the polled service's own RouteNodes for one whose normalised\n// (method, path) matches this request — the same param-agnostic comparison\n// route-match.ts uses for cross-service HTTP client↔route matching, and the\n// same per-call graph scan Firebase's connector and ingest.ts's\n// reconcileObservedRelPath already use (per-project route counts are small\n// enough not to warrant a cache the live graph would have to invalidate on\n// every re-extraction).\nfunction findMatchingRouteNode(\n graph: NeatGraph,\n serviceName: string,\n method: string,\n normalizedPath: string,\n): string | null {\n let found: string | null = null\n graph.forEachNode((_id, attrs) => {\n if (found) return\n const node = attrs as unknown as { type?: string }\n if (node.type !== NodeType.RouteNode) return\n const route = attrs as unknown as RouteNode\n if (route.service !== serviceName || !route.pathTemplate) return\n if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return\n const routeMethod = route.method.toUpperCase()\n if (routeMethod !== 'ALL' && routeMethod !== method) return\n found = route.id\n })\n return found\n}\n\n// Builds the resolveTarget callback runConnectorPoll (connectors/index.ts)\n// calls once per signal. Closes over `graph` because ResolveConnectorTarget's\n// own signature (connectors/types.ts) doesn't carry it — the same closure\n// pattern every ConnectorRegistration's resolveTarget uses.\nexport function createCloudRunResolveTarget(\n graph: NeatGraph,\n config: CloudRunConnectorConfig,\n): ResolveConnectorTarget {\n return (signal): ResolvedConnectorTarget | null => {\n if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null\n const identity = parseCloudRunTargetName(signal.targetName)\n if (!identity) return null\n const { serviceName: gcpServiceName, method, path } = identity\n\n const mappedService = config.serviceMap?.[gcpServiceName]\n\n // Tier 1 — mapped service + a matching static route → route grain.\n if (mappedService) {\n const routeNodeId = findMatchingRouteNode(\n graph,\n mappedService,\n method,\n normalizePathTemplate(path),\n )\n if (routeNodeId) {\n return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType.CALLS }\n }\n }\n\n // Tier 2 — no static route resolved (or the service is unmapped). Land an\n // honest service-grained edge on the Cloud Run service's own InfraNode\n // rather than fabricating a route or dropping the observation. The source\n // is the mapped NEAT service when known, else the Cloud Run service name\n // itself (auto-created by the shared pipeline the same way Cloudflare's own\n // fallback auto-creates its source).\n return {\n targetNodeId: infraId(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),\n serviceName: mappedService ?? gcpServiceName,\n edgeType: EdgeType.CALLS,\n ensureInfraNode: {\n kind: CLOUD_RUN_SERVICE_INFRA_KIND,\n name: gcpServiceName,\n provider: 'cloud-run',\n },\n }\n }\n}\n","// The Cloud Run connector (docs/connectors/cloud-run.md, ADR-165) — sixth\n// connectors-plane pull provider.\n//\n// poll() pulls Cloud Logging's `entries.list` (client.ts), filtered to Cloud\n// Run's own request log (`run.googleapis.com/requests`), and maps each entry to\n// an ObservedSignal (map.ts). Target resolution — a mapped service's RouteNode\n// via the shared route normalisation, or an honest `cloud-run-service` InfraNode\n// fallback when none resolves — lives in resolve.ts. Everything downstream of\n// resolution (minting the OBSERVED edge, the `ensureInfraNode` fallback) is the\n// shared connectors/index.ts pipeline; this module never touches the graph\n// directly (ADR-030).\n\nimport type { NeatGraph } from '../../graph.js'\nimport type { ConnectorContext, ObservedConnector, ObservedSignal } from '../types.js'\nimport type { ResolveConnectorTarget } from '../index.js'\nimport { DEFAULT_LOOKBACK_MS, fetchCloudRunRequestLogEntries } from './client.js'\nimport { mapLogEntriesToSignals } from './map.js'\nimport { createCloudRunResolveTarget } from './resolve.js'\nimport { readCloudRunCredentials, type CloudRunConnectorConfig } from './types.js'\n\nexport * from './client.js'\nexport * from './map.js'\nexport * from './resolve.js'\nexport * from './types.js'\n\nexport class CloudRunConnector implements ObservedConnector {\n readonly provider = 'cloud-run'\n\n constructor(private readonly config: CloudRunConnectorConfig = {}) {}\n\n async poll(ctx: ConnectorContext): Promise<ObservedSignal[]> {\n const creds = readCloudRunCredentials(ctx.credentials)\n const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_LOOKBACK_MS\n const sinceIso = boundedSinceIso(ctx.since, new Date(), maxLookbackMs)\n const entries = await fetchCloudRunRequestLogEntries(creds, sinceIso, this.config.apiUrl)\n return mapLogEntriesToSignals(entries)\n }\n}\n\n// `since` bounded by the provider's max lookback window (connectors.md \"Poll\n// cadence and backfill\") — a gap wider than the window (a laptop off for a\n// week) backfills from `now - maxLookbackMs`, never an unbounded full-history\n// replay. An absent or unparseable `since` (no prior poll) gets the same floor.\nexport function boundedSinceIso(since: string | undefined, now: Date, maxLookbackMs: number): string {\n const floor = new Date(now.getTime() - maxLookbackMs)\n if (!since) return floor.toISOString()\n const sinceMs = new Date(since).getTime()\n if (Number.isNaN(sinceMs)) return floor.toISOString()\n return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString()\n}\n\n/**\n * Wires up a ready-to-register Cloud Run connector: the `ObservedConnector`\n * plus the `resolveTarget` callback the shared pipeline needs alongside it.\n * Both are built together because `resolveTarget` closes over `graph` — the\n * shared scaffold's `ResolveConnectorTarget` signature never receives it\n * directly.\n */\nexport function createCloudRunConnector(\n graph: NeatGraph,\n config: CloudRunConnectorConfig = {},\n): { connector: ObservedConnector; resolveTarget: ResolveConnectorTarget } {\n return {\n connector: new CloudRunConnector(config),\n resolveTarget: createCloudRunResolveTarget(graph, config),\n }\n}\n","// Render connector (ADR-166, docs/connectors/render.md) — a pull provider on\n// the connectors plane (ADR-124, connectors/index.ts). Render is a hosting\n// platform, so like Railway (ADR-127) it names nothing in application code:\n// the signal comes entirely from Render's own edge-layer HTTP request logs\n// (`GET /v1/logs?type=request`), and fusion binds onto the RouteNode\n// `extract/routes.ts` already builds — the same node an OBSERVED server span\n// would fuse onto if the app were OTel-instrumented. This is the second\n// route-fusion connector, after Railway's `httpLogs`.\n\nimport type { RouteNode } from '@neat.is/types'\nimport { EdgeType, NodeType } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport { normalizePathTemplate } from '../../extract/routes.js'\nimport type {\n ConnectorCallSite,\n ConnectorContext,\n ObservedConnector,\n ObservedSignal,\n ResolveConnectorTarget,\n} from '../index.js'\nimport type { RenderConnectorConfig, RenderLogEntry } from './types.js'\nimport { readRenderToken, renderLabelValue } from './types.js'\nimport {\n DEFAULT_MAX_LOOKBACK_MS,\n boundedRenderStartTime,\n fetchRenderRequestLogs,\n} from './client.js'\n\nexport type { RenderConnectorConfig, RenderLogEntry, RenderLogLabel, RenderLogsResponse } from './types.js'\nexport { DEFAULT_RENDER_API_URL } from './client.js'\n\n// A signal's `targetKind` for this provider (connectors/index.ts never inspects\n// these strings itself):\n//\n// - 'route' — the log's (method, path) normalised onto an existing\n// RouteNode (extract/routes.ts). targetName already\n// carries that RouteNode's own graph id.\n// - 'unmatched-route' — no RouteNode resolved. createRenderResolveTarget drops\n// it honestly rather than fabricating a target (a\n// RouteNode's `path` is a required real source location,\n// packages/types/src/nodes.ts).\nconst ROUTE_TARGET_KIND = 'route'\nconst UNMATCHED_ROUTE_TARGET_KIND = 'unmatched-route'\n\n// ── route index (read-only graph query) ────────────────────────────────────\n//\n// Mirrors railway/index.ts's own buildRailwayRouteIndex/findRailwayRoute,\n// which in turn mirrors extract/calls/route-match.ts — none is imported, since\n// none is exported for reuse; each connector rolls the same (method,\n// normalised-path) match against its polled service's RouteNodes. Only\n// normalizePathTemplate is shared (from routes.ts) so the path-template\n// normalisation rules can't drift between the two sides.\n\ninterface RenderRouteIndexEntry {\n method: string\n normalizedPath: string\n routeNodeId: string\n path: string\n line?: number\n}\n\nexport function buildRenderRouteIndex(graph: NeatGraph, serviceName: string): RenderRouteIndexEntry[] {\n const out: RenderRouteIndexEntry[] = []\n graph.forEachNode((_id, attrs) => {\n const node = attrs as unknown as { type?: string }\n if (node.type !== NodeType.RouteNode) return\n const route = attrs as unknown as RouteNode\n if (route.service !== serviceName) return\n out.push({\n method: route.method.toUpperCase(),\n normalizedPath: normalizePathTemplate(route.pathTemplate),\n routeNodeId: route.id,\n path: route.path,\n line: route.line,\n })\n })\n return out\n}\n\n// A request log always carries a concrete method — so, like Railway's\n// findRailwayRoute (and unlike route-match.ts's client-call-site case), there's\n// no \"caller method unknown\" passthrough, only the route's own `ALL` wildcard.\nfunction findRenderRoute(\n entries: RenderRouteIndexEntry[],\n method: string,\n normalizedPath: string,\n): RenderRouteIndexEntry | undefined {\n return entries.find(\n (e) => e.normalizedPath === normalizedPath && (e.method === 'ALL' || e.method === method),\n )\n}\n\nfunction bucketKey(method: string, normalizedPath: string): string {\n return `${method} ${normalizedPath}`\n}\n\nfunction isHttpErrorStatus(status: number): boolean {\n return status >= 400\n}\n\ninterface SignalBucket {\n callCount: number\n errorCount: number\n lastObservedIso: string\n targetKind: string\n targetName: string\n callSite?: ConnectorCallSite\n}\n\nfunction upsertBucket(\n buckets: Map<string, SignalBucket>,\n key: string,\n isError: boolean,\n timestamp: string,\n build: () => Omit<SignalBucket, 'callCount' | 'errorCount' | 'lastObservedIso'>,\n): void {\n const existing = buckets.get(key)\n if (existing) {\n existing.callCount += 1\n if (isError) existing.errorCount += 1\n if (timestamp > existing.lastObservedIso) existing.lastObservedIso = timestamp\n return\n }\n buckets.set(key, { callCount: 1, errorCount: isError ? 1 : 0, lastObservedIso: timestamp, ...build() })\n}\n\n// ── request logs → ObservedSignal[] ─────────────────────────────────────────\n//\n// Route-grain fusion (docs/connectors/render.md §Fusion): read each request\n// log's method/path/statusCode from its `labels` (Render's log object shape),\n// normalise the path the same way route-match.ts normalises a client call\n// site's URL path, and match against the polled service's own RouteNodes. A\n// match carries the matched RouteNode's own `path`/`line` as this signal's\n// callSite — reconciled onto the EXTRACTED service-relative path by the shared\n// fuse step (connectors/index.ts) the same way every other OBSERVED surface\n// lands file-grained, because that path IS the extracted path routes.ts walked.\nexport function mapRenderRequestLogsToSignals(\n entries: RenderLogEntry[],\n routeIndex: RenderRouteIndexEntry[],\n): ObservedSignal[] {\n const buckets = new Map<string, SignalBucket>()\n if (!Array.isArray(entries)) return []\n\n for (const entry of entries) {\n // Drop a null/garbage slot, or a row missing the timestamp/method/path a\n // signal needs, honestly rather than throwing (connectors.md §4). The\n // request attributes live in `labels`; a request log with none isn't one\n // this connector can place on a route.\n if (!entry || typeof entry !== 'object') continue\n if (typeof entry.timestamp !== 'string') continue\n const method = renderLabelValue(entry, 'method')\n const rawPath = renderLabelValue(entry, 'path')\n if (typeof method !== 'string' || method.length === 0) continue\n if (typeof rawPath !== 'string' || rawPath.length === 0) continue\n\n const methodUpper = method.toUpperCase()\n // Strip any query string — the route template is a path, never `?...`.\n const pathOnly = rawPath.split('?')[0]\n const normalizedPath = normalizePathTemplate(pathOnly)\n const statusCode = Number.parseInt(renderLabelValue(entry, 'statusCode') ?? '', 10)\n // A missing/garbage statusCode is not an error signal — treat it as a\n // non-error observation rather than fabricating a 4xx/5xx.\n const isError = Number.isFinite(statusCode) && isHttpErrorStatus(statusCode)\n const match = findRenderRoute(routeIndex, methodUpper, normalizedPath)\n\n if (match) {\n upsertBucket(buckets, `route:${match.routeNodeId}`, isError, entry.timestamp, () => ({\n targetKind: ROUTE_TARGET_KIND,\n targetName: match.routeNodeId,\n // RouteNode.line is optional in the schema (packages/types/src/\n // nodes.ts) even though routes.ts always sets it today — skip the\n // callSite rather than fabricate a line when it's ever absent\n // (file-awareness.md §6).\n ...(match.line !== undefined ? { callSite: { file: match.path, line: match.line } } : {}),\n }))\n } else {\n // No RouteNode resolves — the app's framework/router isn't one routes.ts\n // recognises yet, or the path matches no declared template. This drops\n // honestly (createRenderResolveTarget returns null for it) rather than\n // fabricating a route target.\n upsertBucket(\n buckets,\n `unmatched:${bucketKey(methodUpper, normalizedPath)}`,\n isError,\n entry.timestamp,\n () => ({\n targetKind: UNMATCHED_ROUTE_TARGET_KIND,\n targetName: bucketKey(methodUpper, normalizedPath),\n }),\n )\n }\n }\n\n return [...buckets.values()].map((b) => ({\n targetKind: b.targetKind,\n targetName: b.targetName,\n callCount: b.callCount,\n errorCount: b.errorCount,\n lastObservedIso: b.lastObservedIso,\n ...(b.callSite ? { callSite: b.callSite } : {}),\n }))\n}\n\n// ── target resolution (provider-specific id-shape mapping) ──────────────────\n//\n// A pure function of the signal — the route matching already happened in\n// poll()/the route index above; resolveTarget's job here is only the\n// id-shape mapping (targetKind/targetName → NEAT node id).\nexport function createRenderResolveTarget(config: RenderConnectorConfig): ResolveConnectorTarget {\n return (signal: ObservedSignal) => {\n if (signal.targetKind === ROUTE_TARGET_KIND) {\n // targetName is already the exact RouteNode graph id the route index\n // resolved — the OBSERVED half of the two-sided divergence, landing on\n // the same node the EXTRACTED client↔route CALLS edge (route-match.ts)\n // and a future OBSERVED server span would.\n return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: EdgeType.CALLS }\n }\n\n // UNMATCHED_ROUTE_TARGET_KIND (and anything unrecognised): no RouteNode\n // resolved. The shared pipeline (connectors/index.ts) has no primitive to\n // auto-vivify a RouteNode — and RouteNode.path is a required field naming a\n // real source location (packages/types/src/nodes.ts), so minting one here\n // would fabricate provenance (file-awareness.md §6). Returning null routes\n // this through the scaffold's honest-miss path\n // (ConnectorPollResult.unresolved). The gap it leaves — a route Render\n // observes but no static extractor recognises today — belongs to routes.ts's\n // framework coverage, and shrinks as routes.ts grows, the same as Railway.\n return null\n }\n}\n\n// ── connector ────────────────────────────────────────────────────────────────\n//\n// Constructed with a `graph` reference so poll() can read (never mutate, per\n// ADR-030 — every write flows back through connectors/index.ts's ingest.ts\n// primitives) the polled service's existing RouteNodes to match request logs\n// against, the same read-only access extract/calls/route-match.ts has for the\n// identical purpose on the static-extraction side.\nexport function createRenderConnector(graph: NeatGraph, config: RenderConnectorConfig): ObservedConnector {\n return {\n provider: 'render',\n async poll(ctx: ConnectorContext): Promise<ObservedSignal[]> {\n const token = readRenderToken(ctx.credentials)\n const now = new Date()\n const maxLookbackMs = config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS\n const startTime = boundedRenderStartTime(ctx.since, now, maxLookbackMs)\n const endTime = now.toISOString()\n\n const logs = await fetchRenderRequestLogs(config, token, startTime, endTime)\n const routeIndex = buildRenderRouteIndex(graph, config.serviceName)\n return mapRenderRequestLogsToSignals(logs, routeIndex)\n },\n }\n}\n","// Render connector — provider-specific types (docs/connectors/render.md,\n// ADR-166). Render is a hosting platform, so like Railway (ADR-127) it names\n// nothing in application code: the signal comes entirely from Render's own\n// edge layer (its HTTP request logs), and fusion binds onto the RouteNode\n// `extract/routes.ts` already builds. Render is SaaS-only, so there is no\n// self-hosted-vs-Cloud branch to carry through these types.\n//\n// Surface + field names are sourced from Render's public API docs\n// (api-docs.render.com/reference/list-logs; render.com/docs/logging and\n// render.com/docs/log-streams), not from a live authenticated introspection —\n// this connector has no Render account to probe against. The request-log\n// attributes (method / statusCode / path / host) are the documented log-query\n// filter parameters, carried on each entry as `labels` per Render's log\n// object shape; where the real entry layout differs from the documented one,\n// the mapping layer (index.ts) drops the row honestly rather than guessing.\n// See docs/connectors/render.md §\"What is verified\" for exactly what remains\n// unconfirmed pending a live project.\n\n/**\n * Config resolved once at connector setup (docs/connectors/render.md §Fusion,\n * \"resolved once, never guessed\") — never re-derived from a Render API\n * response at poll time.\n */\nexport interface RenderConnectorConfig {\n // Render's REST API base. Defaults to the public API\n // (api-docs.render.com/reference/introduction confirms\n // `https://api.render.com/v1`); overridable for tests and any proxy.\n apiUrl?: string\n // The Render workspace (owner) id the `GET /v1/logs` query is scoped to —\n // `ownerId` is a required query parameter (api-docs.render.com/reference/\n // list-logs). Also this connector's rate-limit bucket key (ADR-131), the\n // closest thing Render's config carries to \"one customer's account\".\n ownerId: string\n // The Render service id whose request logs this connector instance polls —\n // the `resource` filter on the logs query. Render names a service by its own\n // `srv-...` id, which carries no relationship to NEAT's manifest-derived\n // `serviceId(name)` (packages/types/src/identity.ts); the two are different\n // naming authorities with no shared source of truth.\n resourceId: string\n // The NEAT manifest service name this connector fuses onto — the name that\n // resolves `serviceId(name)` and owns the RouteNodes a request log lands on.\n // Supplied once at connector setup; never inferred from a Render API\n // response (docs/connectors/render.md §Fusion).\n serviceName: string\n // Max rows requested per page. Render caps `limit` at 100\n // (api-docs.render.com/reference/list-logs); values above are clamped.\n limit?: number\n // Bounded number of pages to follow per poll — Render paginates request logs\n // by `hasMore` + `nextStartTime`/`nextEndTime`, so an unbounded follow could\n // walk the whole window on a busy service. Capped so a single tick stays\n // bounded (docs/contracts/connectors.md §\"Poll cadence and backfill\").\n maxPages?: number\n // Bounded lookback cap in ms for a first poll (no prior `since`) or a gap\n // wider than this window. Render's request-log retention varies by plan and\n // isn't published as a single number; 24h is a conservative chosen default,\n // not a provider-confirmed value — overridable once a live project confirms\n // the real window.\n maxLookbackMs?: number\n}\n\n// ── raw provider response shapes ────────────────────────────────────────────\n//\n// Sourced from Render's public API docs (api-docs.render.com/reference/\n// list-logs), not from a live introspection. `GET /v1/logs` returns a page of\n// entries plus timestamp cursors; each entry carries a `labels` array, and a\n// request log's method/statusCode/path/host live in those labels (the same\n// names the log-query filter parameters use). What remains unconfirmed against\n// a real response — whether every request attribute is a label vs a top-level\n// field, and the exact retention window — is flagged in docs/connectors/\n// render.md §\"What is verified\".\n\n/** One `{ name, value }` label on a Render log entry. */\nexport interface RenderLogLabel {\n name: string\n value: string\n}\n\n/** One entry in the `logs` array of a `GET /v1/logs` response. */\nexport interface RenderLogEntry {\n // Render's own event time for the log line — never poll-arrival time.\n timestamp: string\n // The formatted log line; unused for fusion (the structured attributes come\n // from `labels`), carried for the LogEntry emit (ADR-132) a later cut adds.\n message?: string\n id?: string\n // Structured attributes. For a request log: `type=request`, `method`,\n // `statusCode`, `path`, `host` (render.com/docs/logging). Absent or\n // partial → the row is dropped honestly by the mapping layer.\n labels?: RenderLogLabel[]\n}\n\n/** A `GET /v1/logs` response page. */\nexport interface RenderLogsResponse {\n hasMore: boolean\n // Timestamp cursors for the next page — passed back as the next query's\n // `startTime`/`endTime` (api-docs.render.com/reference/list-logs).\n nextStartTime?: string\n nextEndTime?: string\n logs: RenderLogEntry[]\n}\n\n// A Render API key is the single credential this connector carries — a Bearer\n// token created from Render's account settings (render.com/docs/api). Read from\n// `ConnectorContext.credentials` at poll time, never logged, never written into\n// a node or edge (docs/contracts/connectors.md §6).\nexport function readRenderToken(credentials: Record<string, unknown>): string {\n const token = credentials.token\n if (typeof token !== 'string' || token.length === 0) {\n throw new Error('Render connector requires ctx.credentials.token (a Render API key)')\n }\n return token\n}\n\n// Read a single label's value off an entry, or undefined when absent. The\n// request-log attributes (method/statusCode/path/host) live here per Render's\n// log object shape; a missing label means the mapping layer drops the row\n// rather than guessing (connectors.md §4).\nexport function renderLabelValue(entry: RenderLogEntry, name: string): string | undefined {\n if (!Array.isArray(entry.labels)) return undefined\n const label = entry.labels.find((l) => l && typeof l === 'object' && l.name === name)\n return label && typeof label.value === 'string' ? label.value : undefined\n}\n","// Render REST client — the fetch half of this connector's poll() (ADR-166,\n// docs/connectors/render.md). Passive and ambient only (docs/contracts/\n// connectors.md §2): one read-only query against Render's own request-log\n// surface, never a mutation, never a synthetic request to the observed app.\n//\n// Render exposes its edge-layer HTTP request logs through `GET /v1/logs`\n// (api-docs.render.com/reference/list-logs), filterable to `type=request` and\n// scoped by `ownerId` + `resource`. Auth is a Bearer API key\n// (render.com/docs/api). This is the pull path chosen for Render because its\n// push surfaces don't reach NEAT's OTLP ingest: log streams forward RFC5424\n// syslog (render.com/docs/log-streams), and metrics streams forward OTLP\n// *metrics*, not traces (render.com/docs/metrics-streams) — neither is a\n// trace-drain the daemon's `/v1/traces` receiver accepts, so a Vercel-style\n// drain (ADR-146) has nothing to point at. See ADR-166 for the full argument.\n\nimport { bearerAuthHeader, junctionFetch } from '../junction.js'\nimport type { RenderConnectorConfig, RenderLogEntry, RenderLogsResponse } from './types.js'\n\nexport const DEFAULT_RENDER_API_URL = 'https://api.render.com/v1'\n\n// Render caps `limit` at 100 rows per page (api-docs.render.com/reference/\n// list-logs); a poll follows `hasMore`/`nextStartTime`/`nextEndTime` up to\n// DEFAULT_RENDER_MAX_PAGES so a busy service's window stays bounded per tick.\nexport const DEFAULT_RENDER_LOG_LIMIT = 100\nexport const RENDER_MAX_LOG_LIMIT = 100\nexport const DEFAULT_RENDER_MAX_PAGES = 20\n\n// Render's request-log retention varies by plan and isn't published as a single\n// number; 24h is a conservative bounded default for a first poll or a wide gap,\n// never an unbounded full-history query regardless (docs/contracts/\n// connectors.md §\"Poll cadence and backfill\").\nexport const DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1000\n\nfunction clampLimit(limit: number | undefined): number {\n const raw = Math.trunc(limit ?? DEFAULT_RENDER_LOG_LIMIT)\n if (!Number.isFinite(raw) || raw < 1) return DEFAULT_RENDER_LOG_LIMIT\n return Math.min(raw, RENDER_MAX_LOG_LIMIT)\n}\n\n// `since` bounded by the provider's max lookback window (docs/contracts/\n// connectors.md §\"Poll cadence and backfill\") — a gap wider than the window\n// (a laptop off for a week) backfills from `now - maxLookbackMs`, never an\n// unbounded full-history replay. An absent or unparseable `since` (no prior\n// poll) gets the same treatment as too-old a `since`.\nexport function boundedRenderStartTime(since: string | undefined, now: Date, maxLookbackMs: number): string {\n const floor = new Date(now.getTime() - maxLookbackMs)\n if (!since) return floor.toISOString()\n const sinceMs = new Date(since).getTime()\n if (Number.isNaN(sinceMs)) return floor.toISOString()\n return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString()\n}\n\nasync function fetchRenderLogPage(\n config: RenderConnectorConfig,\n token: string,\n startTime: string,\n endTime: string,\n limit: number,\n fetchImpl?: typeof fetch,\n): Promise<RenderLogsResponse> {\n const url = new URL(`${config.apiUrl ?? DEFAULT_RENDER_API_URL}/logs`)\n url.searchParams.set('ownerId', config.ownerId)\n url.searchParams.set('resource', config.resourceId)\n // Only the edge-layer HTTP request logs — the surface that carries method /\n // path / statusCode and fuses onto a RouteNode. App/build logs are free-text\n // stdout with no structured route to bind to (render.com/docs/logging).\n url.searchParams.set('type', 'request')\n url.searchParams.set('startTime', startTime)\n url.searchParams.set('endTime', endTime)\n // Newest-first, matching Render's own default; pagination walks backward\n // through the window via the cursors below.\n url.searchParams.set('direction', 'backward')\n url.searchParams.set('limit', String(limit))\n\n const res = await junctionFetch(\n url,\n { method: 'GET', headers: { ...bearerAuthHeader(token) } },\n { provider: 'render', accountKey: config.ownerId, ...(fetchImpl ? { fetchImpl } : {}) },\n )\n if (!res.ok) {\n throw new Error(`Render logs request failed: ${res.status} ${res.statusText}`)\n }\n return (await res.json()) as RenderLogsResponse\n}\n\n/**\n * Fetch the request logs in [startTime, endTime], following Render's\n * timestamp-cursor pagination up to `maxPages`. Render returns\n * `hasMore` + `nextStartTime`/`nextEndTime` and expects those passed back as\n * the next query's `startTime`/`endTime` (api-docs.render.com/reference/\n * list-logs). A malformed page (no `logs` array) ends the walk honestly\n * rather than throwing.\n */\nexport async function fetchRenderRequestLogs(\n config: RenderConnectorConfig,\n token: string,\n startTime: string,\n endTime: string,\n fetchImpl?: typeof fetch,\n): Promise<RenderLogEntry[]> {\n const limit = clampLimit(config.limit)\n const maxPages = Math.max(1, Math.trunc(config.maxPages ?? DEFAULT_RENDER_MAX_PAGES))\n const out: RenderLogEntry[] = []\n let pageStart = startTime\n let pageEnd = endTime\n\n for (let page = 0; page < maxPages; page++) {\n const body = await fetchRenderLogPage(config, token, pageStart, pageEnd, limit, fetchImpl)\n if (Array.isArray(body.logs)) out.push(...body.logs)\n if (!body.hasMore || !body.nextStartTime || !body.nextEndTime) break\n pageStart = body.nextStartTime\n pageEnd = body.nextEndTime\n }\n return out\n}\n","// PlanetScale connector — provider-specific types (docs/connectors/planetscale.md,\n// ADR-175). PlanetScale is a hosted MySQL/Vitess platform that runs its own\n// query telemetry (Query Insights), so like Neon (ADR-156) it is a data-grain\n// connector: the signal fuses onto the `sql-table` nodes the static SQL/ORM\n// extractor already builds. It is *simpler* than Neon's `pg_stat_statements`\n// path in two ways the Insights API hands us for free:\n//\n// - tables come already parsed (the `tables` array), so there is no\n// FROM-clause regex, and\n// - counts are windowed to the poll interval (`query_count` is the count\n// over `[from, to]`), so there is no cumulative-counter delta bookkeeping.\n//\n// Surface + field names are sourced from PlanetScale's public API reference\n// (planetscale.com/docs/api/reference/list_branch_queries;\n// planetscale.com/changelog/query-insights-api-endpoints), not from a live\n// authenticated introspection — PlanetScale is a paid product with no free\n// tier, so this connector carries no live account to probe against. Where the\n// real response layout differs from the documented one, the mapping layer\n// (map.ts) drops the row honestly rather than guessing. See\n// docs/connectors/planetscale.md §\"What is verified\" for exactly what remains\n// unconfirmed pending a real branch.\n\nexport const PLANETSCALE_SQL_TABLE_TARGET_KIND = 'sql-table'\n\n/**\n * Config resolved once at connector setup (docs/connectors/planetscale.md\n * §Fusion, \"resolved once, never guessed\") — the `(organization, database,\n * branch)` triple that scopes the Insights query, plus the NEAT service the\n * signals fuse onto. Never re-derived from a PlanetScale API response at poll\n * time, the same way Railway resolves `(environmentId, serviceId)` once.\n */\nexport interface PlanetscaleConnectorConfig {\n // PlanetScale's REST API base. Defaults to the public API\n // (planetscale.com/docs/api); overridable for tests and any proxy.\n apiUrl?: string\n // The PlanetScale organization slug the Insights path is scoped to.\n organization: string\n // The PlanetScale database the Insights path is scoped to. Also this\n // connector's rate-limit bucket key (ADR-131), the closest thing\n // PlanetScale's config carries to \"one customer's account\".\n database: string\n // The database branch whose Insights this connector polls (e.g. `main`).\n branch: string\n // The NEAT manifest service name this connector fuses onto — the name that\n // owns the `sql-table` nodes a query row lands on. Supplied once at\n // connector setup; never inferred from a PlanetScale API response.\n serviceName: string\n // Rows requested per page. Insights paginates `page`/`per_page`; 100 keeps a\n // busy branch's window bounded per tick without over-fetching.\n perPage?: number\n // Bounded number of pages to follow per poll — Insights paginates by\n // `next_page`, so an unbounded follow could walk the whole window on a busy\n // branch. Capped so a single tick stays bounded (connectors.md §\"Poll\n // cadence and backfill\").\n maxPages?: number\n // Bounded lookback cap in ms for a first poll (no prior `since`) or a gap\n // wider than this window. PlanetScale's Insights retention isn't published\n // as a single number; 24h is a conservative chosen default, not a\n // provider-confirmed value — overridable once a live branch confirms the\n // real window.\n maxLookbackMs?: number\n}\n\n// ── credential ──────────────────────────────────────────────────────────────\n//\n// A PlanetScale service token is two parts: an id and the secret itself. The\n// least-privilege grant is a per-database `read_database` scope, read-only\n// (planetscale.com/docs/api/reference/service-tokens). Both parts are read\n// from `ConnectorContext.credentials` at poll time, never logged, never\n// written into a node or edge (connectors.md §6).\nexport interface PlanetscaleCredentials {\n serviceTokenId: string\n serviceToken: string\n}\n\nexport function readPlanetscaleCredentials(input: Record<string, unknown>): PlanetscaleCredentials {\n const serviceTokenId = input.serviceTokenId\n const serviceToken = input.serviceToken\n if (typeof serviceTokenId !== 'string' || serviceTokenId.length === 0) {\n throw new Error('planetscale connector: credentials.serviceTokenId is required')\n }\n if (typeof serviceToken !== 'string' || serviceToken.length === 0) {\n throw new Error('planetscale connector: credentials.serviceToken is required')\n }\n return { serviceTokenId, serviceToken }\n}\n\n/**\n * PlanetScale's service-token auth header is NOT a Bearer token — it is the\n * token id, a colon, then the token\n * (planetscale.com/docs/api/reference/service-tokens). This is the one place\n * that shape is constructed, shared between the poll path (client.ts) and the\n * add-time auth probe (registry.ts) so the two can't drift.\n *\n * (The auto-generated API reference renders a generic `Bearer {token}`\n * example for its OAuth2 flow; a service token uses this `id:token` form.\n * docs/connectors/planetscale.md §\"What is verified\" records that this is\n * taken from the docs, not confirmed against a live token.)\n */\nexport function planetscaleAuthHeader(\n serviceTokenId: string,\n serviceToken: string,\n): { Authorization: string } {\n return { Authorization: `${serviceTokenId}:${serviceToken}` }\n}\n\n// ── raw provider response shapes ────────────────────────────────────────────\n//\n// One entry in the `data[]` array of a Query Insights response\n// (planetscale.com/docs/api/reference/list_branch_queries). Only the fields\n// this connector reads are typed; the response carries many more latency /\n// bytes / shard metrics this connector ignores. Every field is optional here\n// because the mapping layer treats a missing/garbage field as \"drop this row\n// honestly\" rather than trusting the documented shape blindly.\nexport interface PlanetscaleInsightRow {\n // The tables this query fingerprint touched, already parsed by PlanetScale —\n // the fusion key. One ObservedSignal is emitted per name. A multi-table\n // query genuinely touched each table, so it fans out.\n tables?: string[]\n // Keyspace-qualified table names (`keyspace.table`) — the fallback fusion\n // key when `tables` is absent or empty.\n qualified_tables?: string[]\n // The keyspace this fingerprint ran in — disambiguates table identity across\n // keyspaces; carried for diagnostics.\n keyspace?: string\n // The normalized (literal-stripped) SQL — diagnostic only; this connector\n // does not parse it (Insights already parsed `tables`), and stays at table\n // grain (ADR-175 — no column grain).\n normalized_sql?: string\n // The number of times this fingerprint ran over the poll window → callCount.\n // Windowed, not cumulative — read directly, no baseline.\n query_count?: number\n // How many of those runs errored over the window → errorCount. A real\n // failure signal the pg_stat_statements path (Neon/Supabase) has none of.\n error_count?: number\n // PlanetScale's own last-run time for this fingerprint → lastObservedIso.\n // Provider event time, never poll-arrival time (README.md §Poll cadence).\n last_run_at?: string | null\n}\n\n/** One Query Insights response page. */\nexport interface PlanetscaleInsightsResponse {\n data?: PlanetscaleInsightRow[]\n // The next page number, or null on the last page — the pagination cursor\n // this connector follows up to `maxPages`.\n next_page?: number | null\n current_page?: number\n}\n","// PlanetScale REST client — the fetch half of this connector's poll() (ADR-175,\n// docs/connectors/planetscale.md). Passive and ambient only (connectors.md §2):\n// one read-only query against PlanetScale's own Query Insights surface, never a\n// mutation, never a synthetic query to the observed database.\n//\n// PlanetScale exposes per-fingerprint query telemetry through\n// GET /v1/organizations/{org}/databases/{db}/branches/{branch}/insights\n// (planetscale.com/docs/api/reference/list_branch_queries), bounded by a\n// `from`/`to` time window and paginated by `page`/`per_page`. Auth is a\n// service token in the NON-Bearer `id:token` header form (types.ts,\n// planetscale.com/docs/api/reference/service-tokens). The query is always\n// bounded by the poll window — never an unbounded full-history read.\n\nimport { junctionFetch } from '../junction.js'\nimport {\n planetscaleAuthHeader,\n type PlanetscaleConnectorConfig,\n type PlanetscaleCredentials,\n type PlanetscaleInsightRow,\n type PlanetscaleInsightsResponse,\n} from './types.js'\n\nexport const DEFAULT_PLANETSCALE_API_URL = 'https://api.planetscale.com/v1'\n\n// PlanetScale's Insights default `per_page` is 25; 100 keeps a busy branch's\n// window bounded in fewer round-trips without over-fetching.\nexport const DEFAULT_PLANETSCALE_PER_PAGE = 100\n// Bounded page-follow per poll — `next_page` could otherwise walk the whole\n// window on a busy branch (connectors.md §\"Poll cadence and backfill\").\nexport const DEFAULT_PLANETSCALE_MAX_PAGES = 20\n\n// PlanetScale's Insights retention varies and isn't published as a single\n// number; 24h is a conservative bounded default for a first poll or a wide\n// gap, never an unbounded full-history query regardless.\nexport const DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1000\n\nfunction clampPerPage(perPage: number | undefined): number {\n const raw = Math.trunc(perPage ?? DEFAULT_PLANETSCALE_PER_PAGE)\n if (!Number.isFinite(raw) || raw < 1) return DEFAULT_PLANETSCALE_PER_PAGE\n return Math.min(raw, DEFAULT_PLANETSCALE_PER_PAGE)\n}\n\n// `since` bounded by the provider's max lookback window (connectors.md §\"Poll\n// cadence and backfill\") — a gap wider than the window (a laptop off for a\n// week) backfills from `now - maxLookbackMs`, never an unbounded full-history\n// replay. An absent or unparseable `since` (no prior poll) gets the same\n// treatment as too-old a `since`. Mirrors render/client.ts's own bounding.\nexport function boundedPlanetscaleStartTime(\n since: string | undefined,\n now: Date,\n maxLookbackMs: number,\n): string {\n const floor = new Date(now.getTime() - maxLookbackMs)\n if (!since) return floor.toISOString()\n const sinceMs = new Date(since).getTime()\n if (Number.isNaN(sinceMs)) return floor.toISOString()\n return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString()\n}\n\nfunction insightsUrl(\n config: PlanetscaleConnectorConfig,\n from: string,\n to: string,\n perPage: number,\n page: number,\n): URL {\n const base = config.apiUrl ?? DEFAULT_PLANETSCALE_API_URL\n const url = new URL(\n `${base}/organizations/${encodeURIComponent(config.organization)}/databases/${encodeURIComponent(\n config.database,\n )}/branches/${encodeURIComponent(config.branch)}/insights`,\n )\n // Always bounded by the poll window — never an unbounded read.\n url.searchParams.set('from', from)\n url.searchParams.set('to', to)\n url.searchParams.set('per_page', String(perPage))\n url.searchParams.set('page', String(page))\n return url\n}\n\nasync function fetchInsightsPage(\n config: PlanetscaleConnectorConfig,\n credentials: PlanetscaleCredentials,\n from: string,\n to: string,\n perPage: number,\n page: number,\n fetchImpl?: typeof fetch,\n): Promise<PlanetscaleInsightsResponse> {\n const res = await junctionFetch(\n insightsUrl(config, from, to, perPage, page),\n {\n method: 'GET',\n headers: {\n ...planetscaleAuthHeader(credentials.serviceTokenId, credentials.serviceToken),\n Accept: 'application/json',\n },\n },\n { provider: 'planetscale', accountKey: config.database, ...(fetchImpl ? { fetchImpl } : {}) },\n )\n if (!res.ok) {\n throw new Error(`PlanetScale insights request failed: ${res.status} ${res.statusText}`)\n }\n return (await res.json()) as PlanetscaleInsightsResponse\n}\n\n/**\n * Fetch the Query Insights rows for `[from, to]`, following PlanetScale's\n * `next_page` pagination up to `maxPages`. A page with no `data` array ends\n * the walk honestly rather than throwing (connectors.md §4). The window is\n * fixed for the whole walk — every page reads the same `from`/`to`, only\n * `page` advances.\n */\nexport async function fetchPlanetscaleInsights(\n config: PlanetscaleConnectorConfig,\n credentials: PlanetscaleCredentials,\n from: string,\n to: string,\n fetchImpl?: typeof fetch,\n): Promise<PlanetscaleInsightRow[]> {\n const perPage = clampPerPage(config.perPage)\n const maxPages = Math.max(1, Math.trunc(config.maxPages ?? DEFAULT_PLANETSCALE_MAX_PAGES))\n const out: PlanetscaleInsightRow[] = []\n let page = 1\n\n for (let i = 0; i < maxPages; i++) {\n const body = await fetchInsightsPage(config, credentials, from, to, perPage, page, fetchImpl)\n if (Array.isArray(body.data)) out.push(...body.data)\n // `next_page` is null on the last page; a missing/garbage cursor also ends\n // the walk rather than looping forever.\n if (typeof body.next_page !== 'number' || body.next_page <= page) break\n page = body.next_page\n }\n return out\n}\n","// PlanetScale Query Insights → ObservedSignal[] (ADR-175,\n// docs/connectors/planetscale.md §Fusion). This is the whole mapping layer,\n// and it is deliberately small: PlanetScale already did the two hard parts the\n// pg_stat_statements path (Neon/Supabase) has to do by hand.\n//\n// - No FROM-clause regex. `tables` is already the parsed table set. One\n// signal per table name — a multi-table query genuinely touched each, so\n// it fans out. `qualified_tables` is the keyspace-qualified fallback when\n// `tables` is bare.\n// - No cumulative-delta bookkeeping. `query_count` is windowed to the poll\n// interval, so it IS the count for this tick — read directly, no baseline,\n// no reset detection.\n//\n// Table grain only (ADR-175): `normalized_sql` is carried in the raw row for\n// diagnostics but never parsed for columns — Insights aggregates per\n// fingerprint, so there is no honest column or row grain to emit.\n//\n// Like Neon (connectors.md §7's bounded exception), an Insights row is an\n// aggregate counter snapshot, not an individual invocation record, so this\n// layer emits only the counter ObservedSignal and no LogEntry — turning a\n// windowed fingerprint aggregate into a per-event log line would fabricate\n// events that never individually existed.\n\nimport type { ObservedSignal } from '../types.js'\nimport { PLANETSCALE_SQL_TABLE_TARGET_KIND, type PlanetscaleInsightRow } from './types.js'\n\n// The table set for one row: prefer the bare `tables`, fall back to the\n// keyspace-qualified names when `tables` is absent or empty. Filters to\n// non-empty strings so a garbage entry never becomes a signal.\nfunction tablesFor(row: PlanetscaleInsightRow): string[] {\n const bare = Array.isArray(row.tables) ? row.tables.filter((t) => typeof t === 'string' && t.length > 0) : []\n if (bare.length > 0) return bare\n return Array.isArray(row.qualified_tables)\n ? row.qualified_tables.filter((t) => typeof t === 'string' && t.length > 0)\n : []\n}\n\n/**\n * Map one page-set of Insights rows to signals. `observedAtIso` is the poll's\n * own `to` time, used only as the fallback event time when a row's own\n * `last_run_at` is absent — the provider's event time is preferred whenever it\n * carries one (README.md §Poll cadence and backfill).\n */\nexport function mapInsightsToSignals(\n rows: PlanetscaleInsightRow[],\n observedAtIso: string,\n): ObservedSignal[] {\n const signals: ObservedSignal[] = []\n if (!Array.isArray(rows)) return signals\n\n for (const row of rows) {\n if (!row || typeof row !== 'object') continue\n\n const callCount = Number(row.query_count)\n // A non-finite or non-positive count is not an observation — drop it\n // rather than minting a phantom zero-call edge (connectors.md §4).\n if (!Number.isFinite(callCount) || callCount < 1) continue\n\n const rawErrors = Number(row.error_count)\n const errorCount = Number.isFinite(rawErrors) && rawErrors > 0 ? Math.trunc(rawErrors) : 0\n\n // The provider's own last-run time is the observation instant; fall back\n // to the poll's `to` only when the row carries none.\n const lastObservedIso =\n typeof row.last_run_at === 'string' && row.last_run_at.length > 0 ? row.last_run_at : observedAtIso\n\n for (const table of tablesFor(row)) {\n signals.push({\n targetKind: PLANETSCALE_SQL_TABLE_TARGET_KIND,\n targetName: table,\n callCount: Math.trunc(callCount),\n errorCount,\n lastObservedIso,\n })\n }\n }\n\n return signals\n}\n","// Target resolution — the PlanetScale-specific half of the pull/map/fuse split\n// (connectors.md §Authority): turning a signal's (targetKind, targetName) into\n// a NEAT node id. A pure lookup against the graph; no mutation authority of its\n// own (ADR-030), so the honest fallback is *declared* via `ensureInfraNode`\n// (connectors.md §4a, ADR-133) for the shared pipeline to enact.\n//\n// The fusion target is the `sql-table` node the static SQL/ORM extractor\n// already mints (`infraId('sql-table', <table>)`) — the exact id the Neon and\n// Supabase table-grain connectors resolve onto, and the id the SQLAlchemy /\n// Django / Drizzle / Prisma table extractors build. Landing here means the\n// OBSERVED edge sharpens to file grain automatically wherever that node\n// carries a real file:line static call site (the shared pipeline's\n// staticCallSiteFor), the ADR-124 compounding payoff — no connector-side\n// change when the extractor grows.\n//\n// Honest miss (connectors.md §4a): when no `sql-table` node exists yet — the\n// static extractor hasn't run against this code, or doesn't parse the shape —\n// the signal must NOT fabricate a table node. Instead it stays honestly at the\n// PROVIDER level: a `planetscale-database` InfraNode declared observed-but-\n// undeclared, so the traffic surfaces as a `missing-extracted` divergence\n// rather than a silent drop. A future PlanetScale schema extractor that mints\n// the `sql-table` node makes these edges sharpen onto it with no change here.\n\nimport { EdgeType, infraId } from '@neat.is/types'\nimport type { NeatGraph } from '../../graph.js'\nimport type { ResolveConnectorTarget, ResolvedConnectorTarget } from '../index.js'\nimport type { ConnectorContext, ObservedSignal } from '../types.js'\nimport { PLANETSCALE_SQL_TABLE_TARGET_KIND, type PlanetscaleConnectorConfig } from './types.js'\n\n// The provider-level fallback node's stable identity: the org/database pair the\n// connector polls. Never a table — a database (connectors.md §4a's honest\n// provider-node fallback, like Cloud Run's `cloud-run-service`).\nconst PLANETSCALE_DATABASE_KIND = 'planetscale-database'\n\nexport function createPlanetscaleResolveTarget(\n graph: NeatGraph,\n config: PlanetscaleConnectorConfig,\n): ResolveConnectorTarget {\n const databaseName = `${config.organization}/${config.database}`\n return (signal: ObservedSignal, _ctx: ConnectorContext): ResolvedConnectorTarget | null => {\n if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null\n\n // Prefer the existing canonical sql-table node the static extractor minted.\n // Landing on it lets the observation fuse onto the file→table static call\n // site for file grain, rather than collapsing to the provider node.\n const tableId = infraId('sql-table', signal.targetName)\n if (graph.hasNode(tableId)) {\n return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: EdgeType.CALLS }\n }\n\n // No such table node — honest miss. Fall back to the provider-level\n // database node (never a fabricated table), declared for the shared\n // pipeline to ensure (ADR-030 / §4a). `targetNodeId` MUST equal\n // `infraId(kind, name)` when `ensureInfraNode` is set.\n const providerId = infraId(PLANETSCALE_DATABASE_KIND, databaseName)\n return {\n targetNodeId: providerId,\n serviceName: config.serviceName,\n edgeType: EdgeType.CALLS,\n ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: 'planetscale' },\n }\n }\n}\n","// PlanetScale connector (ADR-175, docs/connectors/planetscale.md) — a data-grain\n// pull provider on the connectors plane (ADR-124, connectors/index.ts). Reads\n// PlanetScale's own Query Insights telemetry and fuses per-table query stats\n// onto the `sql-table` nodes the static SQL/ORM extractor already builds, the\n// same fusion target Neon and Supabase's table-grain connectors use.\n//\n// The connector owns only poll() (fetch + map); the graph-reading target\n// resolution is a separate `resolveTarget` built with the graph, mirroring the\n// Supabase pairing the registry normalizes.\n\nimport type { NeatGraph } from '../../graph.js'\nimport type { ResolveConnectorTarget } from '../index.js'\nimport type { ConnectorContext, ObservedConnector, ObservedSignal } from '../types.js'\nimport {\n DEFAULT_MAX_LOOKBACK_MS,\n boundedPlanetscaleStartTime,\n fetchPlanetscaleInsights,\n} from './client.js'\nimport { mapInsightsToSignals } from './map.js'\nimport { createPlanetscaleResolveTarget } from './resolve.js'\nimport { readPlanetscaleCredentials, type PlanetscaleConnectorConfig } from './types.js'\n\nexport * from './client.js'\nexport * from './map.js'\nexport * from './resolve.js'\nexport * from './types.js'\n\nexport interface PlanetscaleConnectorDeps {\n fetchImpl?: typeof fetch\n now?: () => Date\n}\n\nexport class PlanetscaleConnector implements ObservedConnector {\n readonly provider = 'planetscale'\n\n constructor(\n private readonly config: PlanetscaleConnectorConfig,\n private readonly deps: PlanetscaleConnectorDeps = {},\n ) {}\n\n async poll(ctx: ConnectorContext): Promise<ObservedSignal[]> {\n const credentials = readPlanetscaleCredentials(ctx.credentials)\n const now = this.deps.now?.() ?? new Date()\n const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS\n const from = boundedPlanetscaleStartTime(ctx.since, now, maxLookbackMs)\n const to = now.toISOString()\n const rows = await fetchPlanetscaleInsights(this.config, credentials, from, to, this.deps.fetchImpl)\n return mapInsightsToSignals(rows, to)\n }\n}\n\nexport function createPlanetscaleConnector(\n graph: NeatGraph,\n config: PlanetscaleConnectorConfig,\n deps: PlanetscaleConnectorDeps = {},\n): { connector: ObservedConnector; resolveTarget: ResolveConnectorTarget } {\n return {\n connector: new PlanetscaleConnector(config, deps),\n resolveTarget: createPlanetscaleResolveTarget(graph, config),\n }\n}\n","// Provider dispatch table — the one place a connector provider registers\n// (docs/contracts/connector-config.md §5, ADR-130).\n//\n// The shipped provider factories deliberately differ in shape: Supabase and\n// Firebase hand back `{ connector, resolveTarget }` from one call, Railway\n// pairs `createRailwayConnector(graph, config)` with a separate\n// `createRailwayResolveTarget(config)`, and Cloudflare constructs its\n// connector class directly alongside `createCloudflareResolveTarget(config)`.\n// This table is the normalization seam that adapts all of them into the one\n// uniform `ConnectorRegistration` the daemon's poll loop consumes — the same\n// data-driven discipline `compat.json` holds for driver logic (CLAUDE.md\n// § Don't do). A new provider adds one entry here; daemon.ts and cli.ts never\n// grow a per-provider branch.\n//\n// `buildRegistration` turns one resolved config entry into a registration;\n// `loadConnectorRegistrations` reads the whole file (via connectors-config.ts)\n// and builds every entry that matches a project. The daemon calls the latter\n// at slot bootstrap.\n\nimport type { NeatGraph } from '../graph.js'\nimport type { ConnectorRegistration, ObservedConnector, ResolveConnectorTarget } from './index.js'\nimport { startConnectorPollLoop } from './index.js'\nimport { bearerAuthHeader, junctionFetch } from './junction.js'\nimport { createSupabaseConnector, DEFAULT_SUPABASE_MANAGEMENT_API_URL, type SupabaseConnectorConfig } from './supabase/index.js'\nimport {\n createRailwayConnector,\n createRailwayResolveTarget,\n resolveLatestRailwayDeploymentId,\n type RailwayConnectorConfig,\n} from './railway/index.js'\nimport { createFirebaseConnector, type FirebaseServiceMap } from './firebase/index.js'\nimport {\n CloudflareConnector,\n createCloudflareResolveTarget,\n type CloudflareConnectorConfig,\n} from './cloudflare/index.js'\nimport {\n createVercelDrain,\n deleteVercelDrain,\n testVercelDrainDelivery,\n type VercelConnectorConfig,\n type VercelCredentials,\n} from './vercel/index.js'\nimport { createNeonConnector, fetchNeonStatements, type NeonConnectorConfig } from './neon/index.js'\nimport { createCloudRunConnector, type CloudRunConnectorConfig } from './cloud-run/index.js'\nimport {\n createRenderConnector,\n createRenderResolveTarget,\n DEFAULT_RENDER_API_URL,\n type RenderConnectorConfig,\n} from './render/index.js'\nimport {\n createPlanetscaleConnector,\n DEFAULT_PLANETSCALE_API_URL,\n planetscaleAuthHeader,\n type PlanetscaleConnectorConfig,\n} from './planetscale/index.js'\nimport {\n connectorMatchesProject,\n EnvRefUnsetError,\n readConnectorsConfig,\n resolveCredential,\n type ConnectorEntry,\n} from '../connectors-config.js'\n\n/** What a provider's factory pairing produces once normalized. */\ninterface BuiltConnector {\n connector: ObservedConnector\n resolveTarget: ResolveConnectorTarget\n}\n\n/**\n * The verdict of a provider's cheap auth round-trip (contract §4). `ok` means\n * the resolved credential authenticated against the provider; a failure names\n * why in plain terms the CLI can print verbatim — never echoing the credential\n * itself.\n */\nexport type ConnectorValidation = { ok: true } | { ok: false; reason: string }\n\n/** What a dispatch-table validator receives — already-resolved credentials. */\nexport interface ValidateInput {\n // env-refs already resolved to their in-memory values (contract §2). Keyed\n // the way this provider's `poll()` reads them.\n credentials: Record<string, unknown>\n // Non-secret provider config from the entry's `options`.\n options: Record<string, unknown>\n // Dependency-injection seam for tests — a fake `fetch` stands in for the live\n // provider so validate-on-add never needs a real account (contract §4's\n // round-trip is exercised against a stub, mirroring each connector's own\n // `fetchImpl` test seam).\n fetchImpl?: typeof fetch\n // Database-backed providers use this test seam for their auth round-trip.\n // Production leaves it absent and executes the real bounded junction read.\n dbProbe?: (connectionString: string, accountKey: string) => Promise<void>\n}\n\n// The Cloudflare API host, defaulted here rather than importing a private const\n// out of the connector's client — the same value cloudflare/client.ts uses.\nconst CLOUDFLARE_API_BASE_URL = 'https://api.cloudflare.com/client/v4'\n\n/**\n * One authenticated GET/POST through the shared junction (ADR-131), interpreted\n * as a validation verdict. A 2xx means the credential authenticated; a 401/403\n * means the provider rejected it (the \"creds present but wrong\" case the\n * contract keeps distinct from an unset env-ref); any other status or a\n * transport failure is reported as an inability to confirm, never a false\n * \"valid\". The bearer token flows into the Authorization header and nowhere\n * else — the reason strings below carry only the provider name and HTTP status,\n * never the secret (contract §2, connectors.md §6).\n */\nasync function authProbe(input: {\n provider: string\n accountKey: string\n url: string | URL\n token: string\n // Most providers carry `Authorization: Bearer <token>` (the default built\n // from `token`). A provider whose auth header is a different shape — e.g.\n // PlanetScale's non-Bearer `id:token` service-token form — passes it here,\n // and the round-trip's 2xx/401/403 verdict logic is otherwise identical.\n authHeader?: Record<string, string>\n init?: RequestInit\n fetchImpl?: typeof fetch\n}): Promise<ConnectorValidation> {\n const { provider, accountKey, url, token, authHeader, init, fetchImpl } = input\n try {\n const res = await junctionFetch(\n url,\n {\n ...(init ?? {}),\n headers: {\n ...(authHeader ?? bearerAuthHeader(token)),\n ...((init?.headers as Record<string, string>) ?? {}),\n },\n },\n { provider, accountKey, ...(fetchImpl ? { fetchImpl } : {}) },\n )\n if (res.ok) return { ok: true }\n if (res.status === 401 || res.status === 403) {\n return { ok: false, reason: `${provider} rejected the credential (HTTP ${res.status})` }\n }\n return {\n ok: false,\n reason: `${provider} auth check returned HTTP ${res.status} ${res.statusText} — could not confirm the credential`,\n }\n } catch (err) {\n return {\n ok: false,\n reason: `${provider} auth check could not reach the provider: ${(err as Error).message}`,\n }\n }\n}\n\n/**\n * The field-schema surface `neat connector add` reads to know what to prompt\n * for and validate structurally (contract §5). Shared by pull providers\n * ({@link ProviderDispatch}) and push providers ({@link PushProviderDispatch})\n * so the CLI's credential/option handling never branches on provider shape —\n * it reads this and dispatches the rest.\n */\nexport interface ProviderFieldSchema {\n provider: string\n // The credential-record key a single-string credential resolves into (this\n // provider's primary secret field). A multi-field credential carries its\n // own keys and ignores this.\n primaryCredentialKey: string\n // Credential-record keys this provider requires. Checked before any network\n // call so a missing field fails honestly rather than silently downstream.\n requiredCredentialFields: readonly string[]\n // `options` keys this provider requires.\n requiredOptionFields: readonly string[]\n}\n\n/**\n * One *pull* provider's dispatch-table entry. Beyond the field schema it\n * carries the factory the daemon's poll loop consumes (contract §5) — declared\n * here so provider specifics live in data the table reads, never scattered\n * across the CLI and daemon.\n */\nexport interface ProviderDispatch extends ProviderFieldSchema {\n // Adapt (graph, non-secret options) into the uniform connector pairing.\n build(graph: NeatGraph, options: Record<string, unknown>): BuiltConnector\n // The cheap auth round-trip `neat connector add`/`test` run before writing\n // (contract §4, §5). Data-driven like everything else on this entry: the CLI\n // never hand-rolls a per-provider auth check, it dispatches here.\n validate(input: ValidateInput): Promise<ConnectorValidation>\n}\n\n/**\n * One *push* provider's dispatch-table entry (connectors.md §9, ADR-146). A\n * push provider has no `poll()` — its telemetry arrives at the daemon's OTLP\n * receiver because `provision` configured the provider to forward it. Beyond\n * the shared field schema and `validate`, it carries a provision/deprovision\n * lifecycle the pull shape has no need for. Registered in\n * {@link PUSH_PROVIDER_DISPATCH}, parallel to {@link PROVIDER_DISPATCH}.\n */\nexport interface PushProviderDispatch extends ProviderFieldSchema {\n // The same cheap round-trip §4 names — for a drain, it authenticates the\n // provider credential *and* confirms the daemon's OTLP endpoint is reachable\n // and accepts the drain's bearer. Run by `add` (pre-provision) and `test`.\n validate(input: ValidateInput): Promise<ConnectorValidation>\n // Create the provider-side resource (the drain). On success returns an opaque\n // handle merged into the entry's `options` (e.g. `{ drainId }`) so a later\n // `remove` can tear it down, plus an optional operator-facing note.\n provision(input: ValidateInput): Promise<ProvisionResult>\n // Delete the provider-side resource. Idempotent — an already-gone resource is\n // a success, not an error (connectors.md §9).\n deprovision(input: ValidateInput): Promise<DeprovisionResult>\n}\n\nexport type ProvisionResult =\n | { ok: true; options?: Record<string, unknown>; note?: string }\n | { ok: false; reason: string }\n\nexport type DeprovisionResult = { ok: true; note?: string } | { ok: false; reason: string }\n\n// ── The table. Five providers are designed; four are built and register\n// here. Vercel's Drains connector (#724) is a push connector still awaiting a\n// `receive()` amendment to connectors.md — it adds its entry when it lands.\nexport const PROVIDER_DISPATCH: Record<string, ProviderDispatch> = {\n supabase: {\n provider: 'supabase',\n primaryCredentialKey: 'managementToken',\n requiredCredentialFields: ['managementToken'],\n requiredOptionFields: ['apiProjectRef', 'nodeRef', 'serviceName'],\n build(graph, options) {\n // createSupabaseConnector already returns the pairing.\n return createSupabaseConnector(graph, options as unknown as SupabaseConnectorConfig)\n },\n // GET /v1/projects — the Management API's own auth-gated list endpoint, the\n // cheapest confirmation the management token is live (the same surface\n // client.ts polls, minus the heavy log query).\n validate({ credentials, options, fetchImpl }) {\n const cfg = options as Partial<SupabaseConnectorConfig>\n const baseUrl = cfg.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL\n return authProbe({\n provider: 'supabase',\n accountKey: cfg.apiProjectRef ?? 'validate',\n url: `${baseUrl}/v1/projects`,\n token: String(credentials.managementToken ?? ''),\n ...(fetchImpl ? { fetchImpl } : {}),\n })\n },\n },\n railway: {\n provider: 'railway',\n primaryCredentialKey: 'token',\n requiredCredentialFields: ['token'],\n requiredOptionFields: ['environmentId', 'serviceId', 'serviceNameById'],\n build(graph, options) {\n const config = options as unknown as RailwayConnectorConfig\n // Split factories — connector and resolveTarget are built separately.\n return {\n connector: createRailwayConnector(graph, config),\n resolveTarget: createRailwayResolveTarget(config),\n }\n },\n // Runs the same `deployments` lookup the poller itself needs\n // (railway/client.ts's resolveLatestRailwayDeploymentId) rather than a\n // trivial `{ __typename }` probe. That trivial form is a false positive\n // for this provider: `Authorization: Bearer <token>` authenticates at\n // Railway's HTTP gateway (any well-formed token gets a 2xx on a query\n // that touches no real data) but is not authorized for the connector's\n // actual queries, which come back as an HTTP-200 response carrying a\n // GraphQL-level \"Not Authorized\" error — invisible to authProbe's\n // status-code-only check. Probing with the real lookup, through the same\n // Project-Access-Token header client.ts sends, catches both failure\n // modes: an HTTP-level rejection (thrown as a fetch/status error) and a\n // GraphQL-level one (thrown by railwayGraphQL's own body.errors check).\n async validate({ credentials, options, fetchImpl }) {\n const cfg = options as Partial<RailwayConnectorConfig>\n if (!cfg.environmentId || !cfg.serviceId) {\n return { ok: false, reason: 'railway: environmentId and serviceId are required to validate' }\n }\n const config: RailwayConnectorConfig = {\n environmentId: cfg.environmentId,\n serviceId: cfg.serviceId,\n serviceNameById: cfg.serviceNameById ?? {},\n ...(cfg.apiUrl ? { apiUrl: cfg.apiUrl } : {}),\n }\n try {\n await resolveLatestRailwayDeploymentId(config, String(credentials.token ?? ''), fetchImpl)\n return { ok: true }\n } catch (err) {\n return { ok: false, reason: `railway auth check failed: ${(err as Error).message}` }\n }\n },\n },\n firebase: {\n provider: 'firebase',\n // Firebase reads both projectId and accessToken from the credential; the\n // single-string form maps to the secret, and the required-fields check\n // below catches a projectId that was never supplied.\n primaryCredentialKey: 'accessToken',\n requiredCredentialFields: ['projectId', 'accessToken'],\n requiredOptionFields: [],\n build(graph, options) {\n // options is the FirebaseServiceMap; createFirebaseConnector returns the\n // pairing.\n return createFirebaseConnector(graph, options as unknown as FirebaseServiceMap)\n },\n // GET the project's Cloud Logging log-name list (pageSize 1) — within the\n // same `roles/logging.viewer` grant the connector polls under, and the\n // lightest call that still fails 401/403 on a bad or wrong-scoped token.\n validate({ credentials, fetchImpl }) {\n const projectId = String(credentials.projectId ?? '')\n return authProbe({\n provider: 'firebase',\n accountKey: projectId || 'validate',\n url: `https://logging.googleapis.com/v2/projects/${projectId}/logs?pageSize=1`,\n token: String(credentials.accessToken ?? ''),\n ...(fetchImpl ? { fetchImpl } : {}),\n })\n },\n },\n cloudflare: {\n provider: 'cloudflare',\n primaryCredentialKey: 'apiToken',\n requiredCredentialFields: ['apiToken'],\n // `workers` dropped as a required field (ADR-133) — the mapping is now\n // derived from the extracted graph's platform tag; an `options.workers`\n // entry still works as an explicit override\n // (CloudflareConnectorConfig.workers).\n requiredOptionFields: ['accountId'],\n build(graph, options) {\n const config = options as unknown as CloudflareConnectorConfig\n // Class + separate resolveTarget factory; resolveTarget now closes over\n // the graph to resolve against the platform tag (ADR-133).\n return {\n connector: new CloudflareConnector(config),\n resolveTarget: createCloudflareResolveTarget(config, graph),\n }\n },\n // GET /accounts/{accountId}/tokens/verify — the *account-scoped* token-verify\n // endpoint. A Workers connector token is scoped to the account, and the\n // user-level `GET /user/tokens/verify` returns 401 \"Invalid API Token\" for\n // such a token even though it authenticates fine against the account's own\n // resources (confirmed live). Probing the account-scoped verify endpoint —\n // `accountId` is already required for this provider — returns 200\n // `{status:\"active\"}` for a working token and 401 for a bad one, so a valid\n // Workers token is no longer falsely rejected at `neat connector add`.\n validate({ credentials, options, fetchImpl }) {\n const cfg = options as Partial<CloudflareConnectorConfig>\n const baseUrl = cfg.baseUrl ?? CLOUDFLARE_API_BASE_URL\n return authProbe({\n provider: 'cloudflare',\n accountKey: cfg.accountId ?? 'validate',\n url: `${baseUrl}/accounts/${cfg.accountId ?? ''}/tokens/verify`,\n token: String(credentials.apiToken ?? ''),\n ...(fetchImpl ? { fetchImpl } : {}),\n })\n },\n },\n neon: {\n provider: 'neon',\n primaryCredentialKey: 'connectionString',\n requiredCredentialFields: ['connectionString'],\n requiredOptionFields: ['projectId', 'serviceName'],\n build(_graph, options) {\n return createNeonConnector(options as unknown as NeonConnectorConfig)\n },\n async validate({ credentials, options, dbProbe }) {\n const projectId = String(options.projectId ?? '')\n const connectionString = String(credentials.connectionString ?? '')\n try {\n if (dbProbe) await dbProbe(connectionString, projectId)\n else await fetchNeonStatements(connectionString, projectId, 1)\n return { ok: true }\n } catch (err) {\n return { ok: false, reason: `neon telemetry read failed: ${(err as Error).message}` }\n }\n },\n },\n 'cloud-run': {\n provider: 'cloud-run',\n // Cloud Run reads both projectId and accessToken from the credential; the\n // single-string form maps to the secret (the token), and the required-fields\n // check below catches a projectId that was never supplied.\n primaryCredentialKey: 'accessToken',\n requiredCredentialFields: ['projectId', 'accessToken'],\n requiredOptionFields: [],\n build(graph, options) {\n return createCloudRunConnector(graph, options as unknown as CloudRunConnectorConfig)\n },\n // POST entries:list with pageSize 1 — the exact surface poll() reads, so the\n // probe checks the actual `logging.logEntries.list` permission the connector\n // needs. A GET on the lighter logs.list endpoint (as Firebase probes) would\n // instead check `logging.logs.list`, falsely rejecting a correctly-scoped\n // custom role that carries only `logging.logEntries.list` (the narrowest\n // grant docs/connectors/cloud-run.md documents) — the same false-negative\n // trap Railway's validate avoids by probing its real query. A 2xx means the\n // token can list log entries; 401/403 means the provider rejected it.\n validate({ credentials, fetchImpl }) {\n const projectId = String(credentials.projectId ?? '')\n return authProbe({\n provider: 'cloud-run',\n accountKey: projectId || 'validate',\n url: 'https://logging.googleapis.com/v2/entries:list',\n token: String(credentials.accessToken ?? ''),\n init: {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ resourceNames: [`projects/${projectId}`], pageSize: 1 }),\n },\n ...(fetchImpl ? { fetchImpl } : {}),\n })\n },\n },\n render: {\n provider: 'render',\n primaryCredentialKey: 'token',\n requiredCredentialFields: ['token'],\n requiredOptionFields: ['ownerId', 'resourceId', 'serviceName'],\n build(graph, options) {\n const config = options as unknown as RenderConnectorConfig\n // Split factories — connector and resolveTarget are built separately.\n return {\n connector: createRenderConnector(graph, config),\n resolveTarget: createRenderResolveTarget(config),\n }\n },\n // GET /v1/services?limit=1 — the cheapest read the Render API key\n // authenticates against (render.com/docs/api). Unlike Railway's GraphQL\n // gateway, Render is a plain REST API: a live key returns 2xx, a bad one a\n // 401/403, so authProbe's status-code check is a true verdict here. The\n // logs query itself also needs an ownerId + resource; `services` needs\n // neither and still fails 401 on a bad token, so it's the honest probe.\n validate({ credentials, options, fetchImpl }) {\n const cfg = options as Partial<RenderConnectorConfig>\n const baseUrl = cfg.apiUrl ?? DEFAULT_RENDER_API_URL\n return authProbe({\n provider: 'render',\n accountKey: cfg.ownerId ?? 'validate',\n url: `${baseUrl}/services?limit=1`,\n token: String(credentials.token ?? ''),\n ...(fetchImpl ? { fetchImpl } : {}),\n })\n },\n },\n planetscale: {\n provider: 'planetscale',\n // The secret is the service token; a single-string credential maps to it,\n // and the required-fields check below catches a serviceTokenId that was\n // never supplied (the same two-part shape cloud-run uses for\n // projectId/accessToken).\n primaryCredentialKey: 'serviceToken',\n requiredCredentialFields: ['serviceTokenId', 'serviceToken'],\n requiredOptionFields: ['organization', 'database', 'branch', 'serviceName'],\n build(graph, options) {\n // createPlanetscaleConnector already returns the pairing.\n return createPlanetscaleConnector(graph, options as unknown as PlanetscaleConnectorConfig)\n },\n // GET .../insights?per_page=1 over a short window — the exact read poll()\n // performs, minus the pagination. A live `read_database` token returns\n // 2xx; a bad one a 401/403, so authProbe's status-code check is a true\n // verdict. PlanetScale's auth header is the non-Bearer `id:token` form\n // (planetscale.com/docs/api/reference/service-tokens), passed as an\n // explicit `authHeader` rather than the default bearer.\n validate({ credentials, options, fetchImpl }) {\n const cfg = options as Partial<PlanetscaleConnectorConfig>\n const baseUrl = cfg.apiUrl ?? DEFAULT_PLANETSCALE_API_URL\n const to = new Date()\n const from = new Date(to.getTime() - 60 * 60 * 1000)\n const url = new URL(\n `${baseUrl}/organizations/${encodeURIComponent(cfg.organization ?? '')}/databases/${encodeURIComponent(\n cfg.database ?? '',\n )}/branches/${encodeURIComponent(cfg.branch ?? '')}/insights`,\n )\n url.searchParams.set('from', from.toISOString())\n url.searchParams.set('to', to.toISOString())\n url.searchParams.set('per_page', '1')\n return authProbe({\n provider: 'planetscale',\n accountKey: cfg.database ?? 'validate',\n url,\n token: '',\n authHeader: planetscaleAuthHeader(\n String(credentials.serviceTokenId ?? ''),\n String(credentials.serviceToken ?? ''),\n ),\n ...(fetchImpl ? { fetchImpl } : {}),\n })\n },\n },\n}\n\nexport function getProviderDispatch(provider: string): ProviderDispatch | undefined {\n return PROVIDER_DISPATCH[provider]\n}\n\n// Read the two secrets a Vercel drain needs out of an already-resolved\n// credential record (registry keeps them env-refs until this point, §2/§6).\nfunction vercelCredsFrom(credentials: Record<string, unknown>): VercelCredentials {\n return { token: String(credentials.token ?? ''), otelToken: String(credentials.otelToken ?? '') }\n}\n\n// Rebuild the typed Vercel config from an entry's opaque `options`. `projectIds`\n// tolerates both an array and a comma-separated string (the shape a `--project-\n// ids` CLI flag lands as); everything absent stays absent.\nfunction vercelConfigFromOptions(options: Record<string, unknown>): VercelConnectorConfig {\n const raw = options.projectIds\n let projectIds: string[] | undefined\n if (Array.isArray(raw)) projectIds = raw.filter((p): p is string => typeof p === 'string')\n else if (typeof raw === 'string' && raw.trim().length > 0) {\n projectIds = raw.split(',').map((s) => s.trim()).filter((s) => s.length > 0)\n }\n return {\n teamId: String(options.teamId ?? ''),\n endpoint: String(options.endpoint ?? ''),\n ...(projectIds && projectIds.length > 0 ? { projectIds } : {}),\n ...(typeof options.drainId === 'string' ? { drainId: options.drainId } : {}),\n ...(typeof options.drainName === 'string' ? { drainName: options.drainName } : {}),\n ...(typeof options.apiBaseUrl === 'string' ? { apiBaseUrl: options.apiBaseUrl } : {}),\n ...(typeof options.secret === 'string' ? { secret: options.secret } : {}),\n }\n}\n\n// ── The push table. One provider so far — Vercel (ADR-146). A push provider\n// registers here instead of PROVIDER_DISPATCH: it provisions a drain rather\n// than exposing a poll(), so it carries a provision/deprovision lifecycle and\n// no `build`. `neat connector add/remove/test` dispatch through it exactly as\n// they dispatch pull providers through PROVIDER_DISPATCH.\nexport const PUSH_PROVIDER_DISPATCH: Record<string, PushProviderDispatch> = {\n vercel: {\n provider: 'vercel',\n // The Vercel access token is the \"primary\" secret a single `--token`\n // populates; `otelToken` (the daemon's OTLP bearer) is the second field.\n primaryCredentialKey: 'token',\n requiredCredentialFields: ['token', 'otelToken'],\n // teamId scopes every Drains call; endpoint is where the drain delivers.\n // projectIds is optional (absent → the drain covers the whole team).\n requiredOptionFields: ['teamId', 'endpoint'],\n // POST /v1/drains/test — authenticates the token and pings the endpoint\n // with a sample event, so `success` means the credential is live *and* the\n // daemon's OTLP endpoint is reachable and accepted the drain's bearer.\n async validate({ credentials, options, fetchImpl }) {\n const result = await testVercelDrainDelivery(\n vercelConfigFromOptions(options),\n vercelCredsFrom(credentials),\n fetchImpl,\n )\n if (result.status === 'success') return { ok: true }\n return {\n ok: false,\n reason: result.error ?? `vercel drain delivery test returned \"${result.status ?? 'no status'}\"`,\n }\n },\n // POST /v1/drains — creates the trace drain, returns its id to store in\n // `options.drainId`. A created-but-not-enabled drain is surfaced as a note,\n // not a failure (the entry still points at a real drain).\n async provision({ credentials, options, fetchImpl }) {\n try {\n const created = await createVercelDrain(\n vercelConfigFromOptions(options),\n vercelCredsFrom(credentials),\n fetchImpl,\n )\n const note =\n created.status && created.status !== 'enabled'\n ? `the drain was created but its status is \"${created.status}\"${created.disabledReason ? ` (${created.disabledReason})` : ''} — check the Vercel dashboard`\n : undefined\n return { ok: true, options: { drainId: created.id }, ...(note ? { note } : {}) }\n } catch (err) {\n return { ok: false, reason: (err as Error).message }\n }\n },\n // DELETE /v1/drains/{id} — idempotent (deleteVercelDrain treats 404 as\n // success). No recorded drainId → nothing to delete, still a success.\n async deprovision({ credentials, options, fetchImpl }) {\n const drainId = typeof options.drainId === 'string' ? options.drainId : ''\n if (!drainId) {\n return { ok: true, note: 'no drain id was recorded — nothing to delete on the Vercel side' }\n }\n try {\n await deleteVercelDrain(\n vercelConfigFromOptions(options),\n drainId,\n vercelCredsFrom(credentials),\n fetchImpl,\n )\n return { ok: true }\n } catch (err) {\n return { ok: false, reason: (err as Error).message }\n }\n },\n },\n}\n\nexport function getPushProviderDispatch(provider: string): PushProviderDispatch | undefined {\n return PUSH_PROVIDER_DISPATCH[provider]\n}\n\n/** Whether a provider provisions a drain (push) rather than being polled (pull). */\nexport function isPushProvider(provider: string): boolean {\n return provider in PUSH_PROVIDER_DISPATCH\n}\n\n/**\n * The field schema for a provider from *either* table — what the CLI reads to\n * prompt for and structurally check credentials/options without caring whether\n * the provider is pull or push.\n */\nexport function getProviderFieldSchema(provider: string): ProviderFieldSchema | undefined {\n return PROVIDER_DISPATCH[provider] ?? PUSH_PROVIDER_DISPATCH[provider]\n}\n\n/** Every registered provider name, pull and push — for CLI \"known providers\" hints. */\nexport function knownProviderNames(): string[] {\n return [...Object.keys(PROVIDER_DISPATCH), ...Object.keys(PUSH_PROVIDER_DISPATCH)].sort()\n}\n\nexport type BuildResult =\n | { ok: true; registration: ConnectorRegistration }\n // `push: true` marks a benign skip — the entry is a push provider with no\n // poll registration to build (connectors.md §9), not a broken entry.\n | { ok: false; reason: string; push?: boolean }\n\n/**\n * Resolve an entry's env-ref credential to in-memory values keyed the way this\n * provider reads them (contract §2, §6), and confirm every required credential\n * field is present. The `unset-env` failure is kept distinct from every other\n * kind so a caller can tell \"you forgot to `export`\" apart from \"your token is\n * malformed\" (contract §4). Shared by `buildRegistration` (daemon read) and\n * `validateConnectorEntry` (the CLI) so both resolve credentials identically.\n */\ntype CredentialResolution =\n | { ok: true; credentials: Record<string, unknown> }\n | { ok: false; kind: 'unset-env' | 'error' | 'missing-field'; reason: string }\n\nfunction resolveEntryCredentials(\n dispatch: ProviderFieldSchema,\n entry: ConnectorEntry,\n env: NodeJS.ProcessEnv,\n): CredentialResolution {\n let credentials: Record<string, unknown>\n try {\n const resolved = resolveCredential(entry.credential, env)\n credentials =\n resolved.kind === 'single'\n ? { [dispatch.primaryCredentialKey]: resolved.value }\n : { ...resolved.fields }\n } catch (err) {\n if (err instanceof EnvRefUnsetError) return { ok: false, kind: 'unset-env', reason: err.message }\n return { ok: false, kind: 'error', reason: (err as Error).message }\n }\n const missingCreds = dispatch.requiredCredentialFields.filter((k) => !credentials[k])\n if (missingCreds.length > 0) {\n return {\n ok: false,\n kind: 'missing-field',\n reason: `credential missing required field(s): ${missingCreds.join(', ')}`,\n }\n }\n return { ok: true, credentials }\n}\n\n/**\n * Turn one resolved config entry into a `ConnectorRegistration`, or a skip\n * reason. Never throws — a bad entry (unknown provider, unset env-ref,\n * missing required field, provider factory rejecting its config) resolves to\n * `{ ok: false, reason }` so one broken connector never takes the daemon slot\n * down with it (contract §6).\n */\nexport function buildRegistration(\n entry: ConnectorEntry,\n graph: NeatGraph,\n env: NodeJS.ProcessEnv = process.env,\n): BuildResult {\n const dispatch = PROVIDER_DISPATCH[entry.provider]\n if (!dispatch) {\n // A push provider (ADR-146) has no poll registration — it provisioned a\n // drain and its data arrives via the OTLP receiver. Flag that as a benign,\n // expected skip, distinct from a genuinely unknown provider, so the daemon\n // never treats a configured Vercel connector as a broken entry\n // (connectors.md §9).\n if (isPushProvider(entry.provider)) {\n return {\n ok: false,\n push: true,\n reason: `push provider \"${entry.provider}\" ingests via the OTLP receiver — nothing to poll`,\n }\n }\n return { ok: false, reason: `unknown provider \"${entry.provider}\"` }\n }\n\n const creds = resolveEntryCredentials(dispatch, entry, env)\n if (!creds.ok) return { ok: false, reason: creds.reason }\n const credentials = creds.credentials\n\n const options = entry.options ?? {}\n const missingOpts = dispatch.requiredOptionFields.filter((k) => !(k in options))\n if (missingOpts.length > 0) {\n return {\n ok: false,\n reason: `options missing required field(s): ${missingOpts.join(', ')}`,\n }\n }\n\n let built: BuiltConnector\n try {\n built = dispatch.build(graph, options)\n } catch (err) {\n return { ok: false, reason: (err as Error).message }\n }\n\n const intervalMs = typeof options.intervalMs === 'number' ? options.intervalMs : undefined\n return {\n ok: true,\n registration: {\n // Carry the entry id so the daemon can key this connector's poll-status\n // records to it (ADR-136).\n id: entry.id,\n connector: built.connector,\n credentials,\n resolveTarget: built.resolveTarget,\n ...(intervalMs !== undefined ? { intervalMs } : {}),\n },\n }\n}\n\n/**\n * The verdict `neat connector add` (validate-on-add) and `neat connector test`\n * both report (contract §4). Five distinct outcomes so the CLI can speak\n * plainly: `ok` (authenticated), `unset-env` (a `$VAR` credential's variable\n * isn't set — resolution failure, *not* the provider rejecting a token),\n * `rejected` (creds resolved but the provider's auth path turned them down),\n * `unknown-provider`, and `missing-field` (a required credential/option is\n * absent — caught before any network call).\n */\nexport type ValidateOutcome =\n | { status: 'ok' }\n | { status: 'unset-env'; reason: string }\n | { status: 'rejected'; reason: string }\n | { status: 'unknown-provider'; reason: string }\n | { status: 'missing-field'; reason: string }\n\n/**\n * Run the provider's cheap auth round-trip against an entry — resolving its\n * env-ref credential first, so an unset variable short-circuits as `unset-env`\n * before any request goes out (contract §4). Dispatches the actual round-trip\n * through the table's `validate` (§5), so no per-provider auth logic lives in\n * the CLI. `fetchImpl` is the test seam that stands a fake provider in for the\n * live one.\n */\nexport async function validateConnectorEntry(\n entry: ConnectorEntry,\n env: NodeJS.ProcessEnv = process.env,\n fetchImpl?: typeof fetch,\n): Promise<ValidateOutcome> {\n // Either table — a pull provider's auth probe and a push provider's\n // drain-delivery test are the same `validate` shape (contract §4, §9), so\n // `neat connector test` reads one and dispatches without branching.\n const dispatch = PROVIDER_DISPATCH[entry.provider] ?? PUSH_PROVIDER_DISPATCH[entry.provider]\n if (!dispatch) {\n return { status: 'unknown-provider', reason: `unknown provider \"${entry.provider}\"` }\n }\n const creds = resolveEntryCredentials(dispatch, entry, env)\n if (!creds.ok) {\n if (creds.kind === 'unset-env') return { status: 'unset-env', reason: creds.reason }\n return { status: 'missing-field', reason: creds.reason }\n }\n const options = entry.options ?? {}\n const missingOpts = dispatch.requiredOptionFields.filter((k) => !(k in options))\n if (missingOpts.length > 0) {\n return {\n status: 'missing-field',\n reason: `options missing required field(s): ${missingOpts.join(', ')}`,\n }\n }\n const result = await dispatch.validate({\n credentials: creds.credentials,\n options,\n ...(fetchImpl ? { fetchImpl } : {}),\n })\n return result.ok ? { status: 'ok' } : { status: 'rejected', reason: result.reason }\n}\n\nexport interface LoadConnectorsInput {\n // The project this daemon slot is bootstrapping — only entries that match\n // it (or omit `project`) load.\n project: string\n // The slot's live graph; resolveTarget closes over it.\n graph: NeatGraph\n // Resolved NEAT_HOME; defaults to the env-based resolution in\n // connectors-config.ts.\n home?: string\n env?: NodeJS.ProcessEnv\n // Called once per skipped entry (unknown provider, unset env-ref, missing\n // field, ...) so the daemon can log it. A skip is never fatal.\n onSkip?: (entry: ConnectorEntry, reason: string) => void\n}\n\n/**\n * Read `~/.neat/connectors.json` and build a registration for every entry\n * that matches `project`. The daemon calls this at slot bootstrap and hands\n * the result to `startConnectorPollLoop`. A missing file yields an empty\n * list; a malformed file yields an empty list plus one skip callback, never a\n * throw — the daemon must survive a hand-edited config.\n */\nexport async function loadConnectorRegistrations(\n input: LoadConnectorsInput,\n): Promise<ConnectorRegistration[]> {\n const { project, graph, home, env = process.env, onSkip } = input\n let connectors: ConnectorEntry[]\n try {\n connectors = (await readConnectorsConfig(home)).connectors\n } catch (err) {\n onSkip?.(\n { id: '(file)', provider: '(all)', credential: '' },\n `connectors.json unreadable — ${(err as Error).message}`,\n )\n return []\n }\n\n const registrations: ConnectorRegistration[] = []\n for (const entry of connectors) {\n if (!connectorMatchesProject(entry, project)) continue\n const result = buildRegistration(entry, graph, env)\n if (result.ok) registrations.push(result.registration)\n // A push provider (ADR-146) is not a poll connector — its absence from the\n // registration list is expected, not a fault, so it never fires the\n // error-oriented onSkip (connectors.md §9). Its drain delivers via the OTLP\n // receiver regardless of this loop.\n else if (!result.push) onSkip?.(entry, result.reason)\n }\n return registrations\n}\n\nexport interface StartConnectorPollingInput {\n project: string\n graph: NeatGraph\n // The project's working directory, handed to each poll as ctx.projectDir.\n projectDir: string\n // Resolved NEAT_HOME. Absent → the connectors file isn't read (only `extra`\n // is polled), which is how a caller with no home opts out of file connectors.\n home?: string\n // Registrations a programmatic caller supplies directly (the daemon's\n // opts.connectors seam), polled ahead of the file-configured ones to match\n // the daemon's original merge order.\n extra?: ConnectorRegistration[]\n onSkip?: (entry: ConnectorEntry, reason: string) => void\n}\n\n/**\n * Load every project-matched connector from `~/.neat/connectors.json` and start\n * a poll loop for each, returning one stop that tears them all down. This is the\n * single place a daemon entrypoint turns stored connectors into running polls:\n * both the multi-project daemon (`daemon.ts`) and `neat watch` (`watch.ts`) go\n * through it, so a CLI-added connector polls the same way whichever way the\n * project is served. Before #871, `neat watch` wired no connectors at all — the\n * loading lived only in `daemon.ts`, so a connector added against a `neat watch`\n * project loaded, validated, and then sat idle forever.\n */\nexport async function startConnectorPolling(input: StartConnectorPollingInput): Promise<() => void> {\n const fileConnectors = input.home\n ? await loadConnectorRegistrations({\n project: input.project,\n graph: input.graph,\n home: input.home,\n ...(input.onSkip ? { onSkip: input.onSkip } : {}),\n })\n : []\n const all = [...(input.extra ?? []), ...fileConnectors]\n const stopFns = all.map((registration) =>\n startConnectorPollLoop(\n registration.connector,\n { projectDir: input.projectDir, credentials: registration.credentials },\n input.graph,\n registration.resolveTarget,\n { intervalMs: registration.intervalMs, connectorId: registration.id },\n ),\n )\n return () => {\n for (const stop of stopFns) stop()\n }\n}\n\n/**\n * The outcome of a push provider's provision/deprovision (connectors.md §9),\n * shaped like {@link ValidateOutcome} so `neat connector add`/`remove` report\n * both with one vocabulary. `ok` optionally carries `options` — an opaque\n * handle to merge into the entry (e.g. `{ drainId }`) — and an operator `note`.\n * `not-push` names the caller error of asking a pull provider to provision.\n */\nexport type PushActionOutcome =\n | { status: 'ok'; options?: Record<string, unknown>; note?: string }\n | { status: 'unset-env'; reason: string }\n | { status: 'missing-field'; reason: string }\n | { status: 'failed'; reason: string }\n | { status: 'unknown-provider'; reason: string }\n | { status: 'not-push'; reason: string }\n\ntype PushResolved =\n | { ok: true; dispatch: PushProviderDispatch; credentials: Record<string, unknown>; options: Record<string, unknown> }\n | { ok: false; outcome: PushActionOutcome }\n\n// Shared front half of provision/deprovision: find the push dispatch, resolve\n// the entry's env-ref credential, and confirm required options are present —\n// the same pre-flight `validateConnectorEntry` runs, kept identical so a push\n// `add`/`remove` fails on a missing field exactly where `test` would.\nfunction resolvePushEntry(entry: ConnectorEntry, env: NodeJS.ProcessEnv): PushResolved {\n const dispatch = PUSH_PROVIDER_DISPATCH[entry.provider]\n if (!dispatch) {\n return PROVIDER_DISPATCH[entry.provider]\n ? {\n ok: false,\n outcome: {\n status: 'not-push',\n reason: `provider \"${entry.provider}\" is polled, not provisioned — there is no drain to manage`,\n },\n }\n : { ok: false, outcome: { status: 'unknown-provider', reason: `unknown provider \"${entry.provider}\"` } }\n }\n const creds = resolveEntryCredentials(dispatch, entry, env)\n if (!creds.ok) {\n const status = creds.kind === 'unset-env' ? 'unset-env' : creds.kind === 'missing-field' ? 'missing-field' : 'failed'\n return { ok: false, outcome: { status, reason: creds.reason } }\n }\n const options = entry.options ?? {}\n const missingOpts = dispatch.requiredOptionFields.filter((k) => !(k in options))\n if (missingOpts.length > 0) {\n return {\n ok: false,\n outcome: { status: 'missing-field', reason: `options missing required field(s): ${missingOpts.join(', ')}` },\n }\n }\n return { ok: true, dispatch, credentials: creds.credentials, options }\n}\n\n/**\n * Provision a push provider's drain (connectors.md §9) — run by `neat connector\n * add` after validation, before the entry is written. On success the returned\n * `options` (the drain handle) is merged into the entry so `remove` can undo it.\n */\nexport async function provisionConnector(\n entry: ConnectorEntry,\n env: NodeJS.ProcessEnv = process.env,\n fetchImpl?: typeof fetch,\n): Promise<PushActionOutcome> {\n const resolved = resolvePushEntry(entry, env)\n if (!resolved.ok) return resolved.outcome\n const result = await resolved.dispatch.provision({\n credentials: resolved.credentials,\n options: resolved.options,\n ...(fetchImpl ? { fetchImpl } : {}),\n })\n if (!result.ok) return { status: 'failed', reason: result.reason }\n return { status: 'ok', ...(result.options ? { options: result.options } : {}), ...(result.note ? { note: result.note } : {}) }\n}\n\n/**\n * Deprovision a push provider's drain — run by `neat connector remove` before\n * the entry is dropped, so a stored entry never outlives its live drain. The\n * provider's `deprovision` is idempotent (an already-gone drain is a success).\n */\nexport async function deprovisionConnector(\n entry: ConnectorEntry,\n env: NodeJS.ProcessEnv = process.env,\n fetchImpl?: typeof fetch,\n): Promise<PushActionOutcome> {\n const resolved = resolvePushEntry(entry, env)\n if (!resolved.ok) return resolved.outcome\n const result = await resolved.dispatch.deprovision({\n credentials: resolved.credentials,\n options: resolved.options,\n ...(fetchImpl ? { fetchImpl } : {}),\n })\n if (!result.ok) return { status: 'failed', reason: result.reason }\n return { status: 'ok', ...(result.note ? { note: result.note } : {}) }\n}\n"],"mappings":"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","names":["fs","path","fs","path","frontierId","fs","path","EdgeType","NodeType","Provenance","extractedEdgeId","fileId","path","Parser","JavaScript","Python","Go","EdgeType","NodeType","Provenance","confidenceForExtracted","extractedEdgeId","fs","path","fs","path","fs","path","fs","path","fs","path","EdgeType","NodeType","extractedEdgeId","path","walk","fs","NodeType","extractedEdgeId","EdgeType","path","fs","EdgeType","Provenance","confidenceForExtracted","extractedEdgeId","fileId","firstArg","fs","path","snippet","fileId","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","PARSE_CHUNK","parseSource","makeJsParser","Parser","JavaScript","makePyParser","Python","makeGoParser","Go","fn","leaf","path","parseSource","path","makeJsParser","makePyParser","makeGoParser","NodeType","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","Provenance","toPosix","path","fileId","NodeType","EdgeType","Provenance","NodeType","Provenance","NodeType","EdgeType","Provenance","fs","path","fileId","NodeType","frontierId","serviceId","Provenance","extractedEdgeId","fs","path","EdgeType","NodeType","Provenance","NodeType","EdgeType","path","walk","serviceId","Provenance","fs","path","minimatch","NodeType","serviceId","fs","path","path","fs","fs","path","NodeType","serviceId","fs","path","NodeType","serviceId","path","NodeType","serviceId","path","serviceId","NodeType","fs","path","path","fs","path","fs","minimatch","serviceId","NodeType","path","fs","NodeType","serviceId","NodeType","path","fs","path","path","Parser","JavaScript","EdgeType","NodeType","Provenance","confidenceForExtracted","extractedEdgeId","symbolId","PARSE_CHUNK","JavaScript","parseSource","parserForExt","Parser","path","symbolId","NodeType","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","path","Parser","EdgeType","NodeType","Provenance","confidenceForExtracted","extractedEdgeId","symbolId","parserForExt","Parser","path","parseSource","symbolId","NodeType","walk","EdgeType","extractedEdgeId","Provenance","confidenceForExtracted","path","Parser","EdgeType","NodeType","Provenance","confidenceForExtracted","extractedEdgeId","stringLiteralText","parserForExt","Parser","path","parseSource","fn","NodeType","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","path","EdgeType","NodeType","Provenance","databaseId","confidenceForExtracted","path","path","fs","path","fs","path","parse","fs","path","path","parse","path","parse","parse","path","parse","path","parse","path","parse","path","path","parse","path","databaseId","NodeType","path","EdgeType","Provenance","confidenceForExtracted","fs","path","EdgeType","NodeType","Provenance","configId","confidenceForExtracted","walk","fs","path","configId","NodeType","EdgeType","Provenance","confidenceForExtracted","fs","path","EdgeType","NodeType","Provenance","confidenceForExtracted","extractedEdgeId","grpcMethodId","walk","fs","path","grpcMethodId","NodeType","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","EdgeType","NodeType","Provenance","confidenceForExtracted","passesExtractedFloor","path","Parser","JavaScript","Python","EdgeType","Provenance","confidenceForExtracted","PARSE_CHUNK","parseSource","makeJsParser","Parser","JavaScript","makePyParser","Python","path","confidenceForExtracted","EdgeType","Provenance","path","Parser","JavaScript","EdgeType","NodeType","Provenance","confidenceForExtracted","passesExtractedFloor","serviceId","PARSE_CHUNK","parseSource","makeJsParser","Parser","JavaScript","walk","NodeType","serviceId","path","confidenceForExtracted","passesExtractedFloor","EdgeType","Provenance","path","infraId","infraId","path","path","infraId","infraId","path","path","infraId","findAll","infraId","path","path","infraId","infraId","path","path","infraId","readImports","infraId","path","path","Parser","JavaScript","infraId","Parser","JavaScript","stringLiteralText","walk","parseSource","path","infraId","path","infraId","infraId","path","path","Parser","Python","infraId","PARSE_CHUNK","makePyParser","Parser","Python","parseSource","namedChildren","pyStaticStringText","walk","path","infraId","path","Parser","Python","infraId","PARSE_CHUNK","makePyParser","Parser","Python","parseSource","namedChildren","pyStaticStringText","walk","path","infraId","path","Parser","JavaScript","infraId","parserForExt","Parser","JavaScript","namedChildren","stringLiteralText","keyName","parseSource","path","walk","infraId","path","infraId","infraId","path","path","Parser","Ruby","infraId","PARSE_CHUNK","makeRubyParser","Parser","Ruby","parseSource","walk","rubyLiteral","rubyArgs","rubyPositional","rubyKwarg","infraId","path","path","Parser","Php","infraId","PARSE_CHUNK","makePhpParser","Parser","Php","parseSource","walk","phpStaticString","phpArgumentValues","phpFirstString","pluralize","tableize","infraId","path","modelTable","path","Parser","Go","infraId","PARSE_CHUNK","walk","path","Parser","Go","infraId","path","Parser","Go","infraId","PARSE_CHUNK","makeGoParser","Parser","Go","parseSource","walk","pluralize","path","infraId","EdgeType","NodeType","Provenance","confidenceForExtracted","passesExtractedFloor","EdgeType","NodeType","Provenance","confidenceForExtracted","extractedEdgeId","infraId","infraId","extractedEdgeId","EdgeType","Provenance","confidenceForExtracted","NodeType","path","EdgeType","Provenance","confidenceForExtracted","NodeType","Provenance","confidenceForExtracted","infraId","infraId","NodeType","Provenance","confidenceForExtracted","path","EdgeType","Provenance","confidenceForExtracted","path","fs","EdgeType","Provenance","confidenceForExtracted","path","fs","EdgeType","Provenance","confidenceForExtracted","fs","path","EdgeType","Provenance","confidenceForExtracted","fs","path","lineAt","EdgeType","Provenance","confidenceForExtracted","fs","path","parseAllDocuments","walkYamlFiles","fs","path","parseAllDocuments","fs","path","parseToml","EdgeType","Provenance","confidenceForExtracted","path","fs","parseToml","lineContaining","Provenance","confidenceForExtracted","EdgeType","fs","path","EdgeType","path","fs","EdgeType","fs","path","parseToml","EdgeType","path","fs","parseToml","EdgeType","fs","path","parseToml","EdgeType","path","fs","parseToml","EdgeType","path","Parser","JavaScript","EdgeType","NodeType","Provenance","confidenceForExtracted","extractedEdgeId","infraId","parserForExt","Parser","JavaScript","namedChildren","stringLiteralText","keyName","parseSource","path","infraId","NodeType","Provenance","confidenceForExtracted","extractedEdgeId","EdgeType","NodeType","NodeType","path","existsSync","path","NodeType","Provenance","path","databaseId","EdgeType","NodeType","Provenance","serviceId","EdgeType","NodeType","serviceId","buckets","Provenance","databaseId","fs","path","NodeType","Provenance","observedEdgeId","fs","path","path","fs","os","path","fs","path","DAY_MS","fs","path","os","fs","path","exists","fileExists","fs","path","walk","snippet","os","os","path","fs","neatHome","NodeType","parseFileId","Provenance","Provenance","parseFileId","NodeType","path","buckets","nowIso","EdgeType","infraId","infraId","EdgeType","EdgeType","NodeType","serviceId","DEFAULT_MAX_LOOKBACK_MS","DEFAULT_LOG_LIMIT","DEFAULT_LOG_LIMIT","NodeType","bucketKey","buckets","EdgeType","serviceId","DEFAULT_MAX_LOOKBACK_MS","path","ERROR_STATUS_THRESHOLD","NodeType","EdgeType","NodeType","findRoute","EdgeType","EdgeType","NodeType","fileId","infraId","ERROR_STATUS_THRESHOLD","path","DEFAULT_MAX_LOOKBACK_MS","NodeType","path","fileId","EdgeType","infraId","pg","Client","pg","EdgeType","infraId","infraId","EdgeType","DEFAULT_LOOKBACK_MS","ENTRIES_LIST_URL","PAGE_SIZE","MAX_PAGES","FIELD_SEP","path","CLOUD_RUN_RESOURCE_TYPE","pathFromRequestUrl","ERROR_STATUS_THRESHOLD","mapLogEntryToSignal","path","mapLogEntriesToSignals","EdgeType","NodeType","infraId","findMatchingRouteNode","NodeType","path","EdgeType","infraId","DEFAULT_LOOKBACK_MS","mapLogEntriesToSignals","EdgeType","NodeType","DEFAULT_MAX_LOOKBACK_MS","ROUTE_TARGET_KIND","UNMATCHED_ROUTE_TARGET_KIND","NodeType","bucketKey","isHttpErrorStatus","upsertBucket","buckets","EdgeType","DEFAULT_MAX_LOOKBACK_MS","DEFAULT_MAX_LOOKBACK_MS","EdgeType","infraId","infraId","EdgeType","DEFAULT_MAX_LOOKBACK_MS","violations","blocking"]}
|