<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nick Zitzer</title>
    <description>The latest articles on DEV Community by Nick Zitzer (@nickzitzer).</description>
    <link>https://dev.to/nickzitzer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3866542%2F28c5f359-e156-4e73-aca1-2b8628ad4d7d.png</url>
      <title>DEV Community: Nick Zitzer</title>
      <link>https://dev.to/nickzitzer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nickzitzer"/>
    <language>en</language>
    <item>
      <title>How we built a 43-connector CMDB with LLM pattern-learning discovery</title>
      <dc:creator>Nick Zitzer</dc:creator>
      <pubDate>Wed, 08 Apr 2026 16:40:04 +0000</pubDate>
      <link>https://dev.to/nickzitzer/how-we-built-a-43-connector-cmdb-with-llm-pattern-learning-discovery-5cg7</link>
      <guid>https://dev.to/nickzitzer/how-we-built-a-43-connector-cmdb-with-llm-pattern-learning-discovery-5cg7</guid>
      <description>&lt;p&gt;&lt;em&gt;Repo: &lt;a href="https://github.com/Happy-Technologies-LLC/configbuddy" rel="noopener noreferrer"&gt;https://github.com/Happy-Technologies-LLC/configbuddy&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;A few years back, I was working on infrastructure where nobody trusted the CMDB. The data was perpetually stale. Connectors broke after every API update. Nobody used it for decisions — it was a ritual: we had a CMDB because enterprises have CMDBs.&lt;/p&gt;

&lt;p&gt;This post is about what I built to solve that problem, why I made the architectural choices I made, and what I'd do differently if I started over. The project is called ConfigBuddy. It's now open-source under Apache 2.0, and I'm writing this less as an announcement and more as an architecture deep-dive for other people working on infrastructure tooling.&lt;/p&gt;

&lt;p&gt;Skip to the parts you care about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The problem with traditional CMDB discovery&lt;/li&gt;
&lt;li&gt;Why Neo4j over PostgreSQL for the primary store&lt;/li&gt;
&lt;li&gt;The connector split: 17 TypeScript + 26 JSON&lt;/li&gt;
&lt;li&gt;Pattern-learning discovery: discover once, replay forever&lt;/li&gt;
&lt;li&gt;The identity resolution problem nobody talks about&lt;/li&gt;
&lt;li&gt;Unified credentials with protocol affinity&lt;/li&gt;
&lt;li&gt;The enrichment pipeline (ITIL + TBM + BSM)&lt;/li&gt;
&lt;li&gt;Failure modes and what I'd do differently&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;1. The problem with traditional CMDB discovery
&lt;/h2&gt;

&lt;p&gt;If you've worked with a CMDB at enterprise scale, you know the failure modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stale data.&lt;/strong&gt; Discovery runs nightly, weekly, sometimes monthly. By the time anyone looks at the CMDB during an incident, half the records are wrong. The graph of relationships you trust during a P1 is the graph from three weeks ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connector breakage.&lt;/strong&gt; Every external system has an API. Every API changes. Connectors break silently — sometimes for weeks before anyone notices the gap in the data. The maintenance burden of keeping 30+ connectors current is enormous, and the work is invisible until something fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-asset pricing.&lt;/strong&gt; Most commercial CMDB platforms charge per discovered CI. This creates perverse incentives — teams turn off discovery for "low-value" assets, which means the CMDB only sees the things finance approved, not the things that actually exist. Shadow IT thrives in the gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No ownership.&lt;/strong&gt; The CMDB is everybody's data and nobody's job. Without continuous validation, it decays. Without ownership, validation never happens.&lt;/p&gt;

&lt;p&gt;The technology to solve this exists. Discovery tooling is mature. Graph databases are mature. LLM APIs are widely available. The problem isn't missing technology — it's that nobody has put them together with the right &lt;em&gt;economic model&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The economic model that ConfigBuddy is built around: &lt;strong&gt;discovery should be cheap to run and cheap to extend.&lt;/strong&gt; If it's cheap to run, you run it more often, and the data stays fresh. If it's cheap to extend, you cover more systems, and the gaps shrink. Cost is the variable that drives quality, not the other way around.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;2. Why Neo4j over PostgreSQL for the primary store
&lt;/h2&gt;

&lt;p&gt;This was the first major architectural decision and the one I get the most questions about.&lt;/p&gt;

&lt;p&gt;CMDB data is graph-shaped by nature. A configuration item (a server, a database, an application) has relationships to other CIs — runs on, depends on, hosted in, owned by, communicates with. The classic CMDB question — "if this database goes down, what services are affected?" — is a graph traversal problem.&lt;/p&gt;

&lt;p&gt;In a relational model, that traversal looks like a multi-table join. For a moderately complex environment, blast radius analysis can require 10-15 table joins, plus recursive CTEs to handle transitive dependencies. The query is slow, the SQL is unreadable, and the database planner has to make hard choices about index usage.&lt;/p&gt;

&lt;p&gt;In Neo4j with Cypher, the same query is 3-4 hops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;db:&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="s1"&gt;'prod-customers-01'&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:DEPENDS_ON&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;svc:&lt;/span&gt;&lt;span class="n"&gt;BusinessService&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;svc.name&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;svc.criticality&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;svc.criticality&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Three lines. The query planner is built for graph traversal, so it's fast even with millions of nodes. And the query is readable to anyone who understands the data model — you can hand it to an SRE during an incident and they can modify it on the fly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tradeoff:&lt;/strong&gt; Operational overhead. Neo4j is a real database with real ops requirements. Backups are different. Causal clustering for HA is a non-trivial setup. The team learning curve for Cypher is real, even though the language is small. If your team has deep PostgreSQL expertise and zero graph database experience, you'll feel the friction.&lt;/p&gt;

&lt;p&gt;ConfigBuddy uses Neo4j as the primary store but syncs to PostgreSQL (with TimescaleDB) for the analytics data mart. Neo4j is the source of truth for relationships. PostgreSQL is the source of truth for time-series analytics, historical reporting, and the kind of aggregation that graph databases handle poorly. The two stores are kept in sync via an ETL pipeline.&lt;/p&gt;

&lt;p&gt;I'd make the same choice again. The graph traversal economics are decisive for the primary use case (impact analysis), and the PostgreSQL data mart catches the cases where graph queries are the wrong tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;3. The connector split: 17 TypeScript + 26 JSON
&lt;/h2&gt;

&lt;p&gt;ConfigBuddy ships with 43 connectors. They fall into two architectural categories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;17 TypeScript connectors&lt;/strong&gt; for systems where discovery requires real business logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS, Azure, GCP — multi-account/multi-subscription enumeration, IAM walking, region iteration&lt;/li&gt;
&lt;li&gt;ServiceNow, Jira — incremental sync with watermarking, complex auth flows&lt;/li&gt;
&lt;li&gt;VMware vSphere, Kubernetes — cluster traversal, parent-child relationship inference&lt;/li&gt;
&lt;li&gt;SCCM, Active Directory — protocol-specific quirks (LDAP paging, SCCM WMI)&lt;/li&gt;
&lt;li&gt;A few more in the same category&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the systems where a JSON config can't capture what discovery actually requires. You need real code to handle pagination quirks, retry logic for rate limits, multi-step OAuth flows, and the messy realities of production APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;26 JSON declarative connectors&lt;/strong&gt; for systems with well-documented REST/GraphQL APIs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"credentialKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"github_token"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endpoints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repositories"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/user/repos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pagination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"link_header"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ciType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"code-repository"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fieldMapping"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.full_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.owner.login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.language"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a working GitHub connector in ~20 lines of JSON. The framework handles authentication, pagination, rate limiting, error handling, retry logic, and field mapping. Adding a new JSON connector for a system I haven't covered yet takes hours, not days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the split?&lt;/strong&gt; Because pretending all connectors are the same is how connector libraries become unmaintainable. The systems that need code, need code. The systems that don't, shouldn't. By drawing the line explicitly, the maintenance burden of the JSON connectors collapses to almost nothing — they're configuration, not software.&lt;/p&gt;

&lt;p&gt;The JSON declarative framework is the part of the codebase I'm most likely to extract into a standalone repo. It's reusable beyond ConfigBuddy and other projects could benefit.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;4. Pattern-learning discovery: discover once, replay forever
&lt;/h2&gt;

&lt;p&gt;This is the part of ConfigBuddy that I think is genuinely novel, and the part that took the longest to get right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The setup:&lt;/strong&gt; Imagine you want to discover everything in a customer's AWS account, but you don't know what's in there. Traditional discovery runs a fixed strategy — call EC2 DescribeInstances, then S3 ListBuckets, then RDS DescribeDBInstances, etc. That strategy works for AWS, but it's hand-coded by an engineer who knew what to look for.&lt;/p&gt;

&lt;p&gt;What if you didn't know what to look for? What if the system was something custom, or a niche cloud, or a homegrown internal platform? The traditional answer is "write a new connector," which takes engineering time you don't have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern-learning approach:&lt;/strong&gt; On first discovery of an unknown system, ConfigBuddy hands the problem to an LLM (Anthropic Claude or OpenAI, configurable). The LLM is given the system's API documentation (or, failing that, the API responses themselves) and asked to figure out a discovery strategy.&lt;/p&gt;

&lt;p&gt;The LLM produces a strategy — &lt;em&gt;"call this endpoint, paginate this way, extract these fields, map them to these CI types."&lt;/em&gt; This is captured as a &lt;strong&gt;pattern&lt;/strong&gt;: an actual TypeScript code string, stored in PostgreSQL, executed in a sandboxed VM (vm2) on subsequent runs.&lt;/p&gt;

&lt;p&gt;The economics:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;LLM cost&lt;/th&gt;
&lt;th&gt;Discovery cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First discovery&lt;/td&gt;
&lt;td&gt;~$0.40 (one-time)&lt;/td&gt;
&lt;td&gt;LLM API call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Second discovery&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Pattern replay (sandboxed code execution)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Third discovery&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Pattern replay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nth discovery&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Pattern replay&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You pay once to learn how to discover a system type, then run forever for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; Most discovery operations are highly repetitive. The same AWS account looks the same on Tuesday as it did on Monday. The same Kubernetes cluster has the same shape. The LLM isn't doing novel reasoning every run — it's doing novel reasoning &lt;em&gt;once&lt;/em&gt; and then we cache the result as executable code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's not just caching:&lt;/strong&gt; A naive cache stores the &lt;em&gt;results&lt;/em&gt; of discovery. Pattern learning stores the &lt;em&gt;strategy&lt;/em&gt;. When the underlying system changes (new resources, new accounts, new namespaces), pattern replay still works because the strategy is parameterized — it's discovering the current state, not replaying the previous state. Only when the API itself changes does the pattern need to be regenerated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pattern versioning:&lt;/strong&gt; When an API change breaks a pattern replay, the system detects the failure (sandboxed execution fails or returns malformed data), regenerates the pattern via LLM, and stores it as a new version. Old patterns are kept for rollback.&lt;/p&gt;

&lt;p&gt;In my own usage, after the initial learning period, the vast majority of discovery runs are pattern replays with zero LLM cost. The first month of a new environment might cost $20-50 in LLM calls. Every subsequent month is essentially free.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;5. The identity resolution problem nobody talks about
&lt;/h2&gt;

&lt;p&gt;This was 30% of v2 development time. It should have been day-one architecture. Most CMDB conversations focus on discovery; identity resolution is where production deployments actually break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Your AWS connector finds an EC2 instance with ID &lt;code&gt;i-0a1b2c3d&lt;/code&gt;. Your ServiceNow connector finds a CI with serial number &lt;code&gt;ABC123XYZ&lt;/code&gt;. Your network scanner finds a host at &lt;code&gt;10.20.30.40&lt;/code&gt; with hostname &lt;code&gt;web-prod-01&lt;/code&gt;. These are all the same server. How does the CMDB know that?&lt;/p&gt;

&lt;p&gt;If you don't solve this, you get duplicate CIs everywhere. Three records for one server. Twelve records for one application. Impact analysis becomes meaningless because the graph thinks these are different things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ConfigBuddy's approach: a six-tier matching cascade.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a new CI arrives from a connector, the identity resolver checks for matches in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;External ID match&lt;/strong&gt; — does the new CI's connector-assigned ID match an existing CI's external ID for the same source? (e.g., AWS instance ID &lt;code&gt;i-0a1b2c3d&lt;/code&gt; already known)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serial number match&lt;/strong&gt; — does the new CI's serial match an existing CI's serial? (Hardware serials are highly reliable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UUID match&lt;/strong&gt; — does the new CI's UUID match? (Common for VMs, less common for physical hardware)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MAC address match&lt;/strong&gt; — does any MAC on the new CI match any MAC on an existing CI? (MAC addresses are reasonably stable for physical NICs but can be ephemeral for cloud)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FQDN match&lt;/strong&gt; — does the fully-qualified domain name match? (Usually reliable but vulnerable to DNS misconfiguration)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composite fuzzy match&lt;/strong&gt; — does a combination of hostname, IP, OS, and other fields meet a similarity threshold? (Last resort, lowest confidence)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first match wins. If no tier matches, it's treated as a new CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source authority ranking:&lt;/strong&gt; When two sources disagree about the same field (AWS says the instance type is &lt;code&gt;t3.large&lt;/code&gt;, ServiceNow says &lt;code&gt;t3.medium&lt;/code&gt;), ConfigBuddy uses configurable source authority rules to decide which wins. By default: ServiceNow outranks Nmap, AWS outranks SSH, vendor-managed sources outrank inferred sources. Authority rules are per-field, so AWS can be authoritative for &lt;code&gt;instanceType&lt;/code&gt; while ServiceNow is authoritative for &lt;code&gt;owner&lt;/code&gt; and &lt;code&gt;costCenter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this is hard:&lt;/strong&gt; Every tier has edge cases. MAC addresses spoof. UUIDs collide across hypervisors. Hostnames are reused. DNS is wrong. Source authority rules conflict. The identity resolver has to handle all of this without losing data and without creating false matches.&lt;/p&gt;

&lt;p&gt;The thing I'd tell anyone building a CMDB: &lt;strong&gt;start with identity resolution, not discovery.&lt;/strong&gt; You can always add more discovery sources. You can never recover from bad identity resolution because the graph is permanently corrupted by bad joins.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;6. Unified credentials with protocol affinity
&lt;/h2&gt;

&lt;p&gt;A CMDB platform needs credentials for everything it discovers. AWS needs an IAM access key. ServiceNow needs OAuth or basic auth. Active Directory needs an LDAP bind user. SSH needs a private key. SNMP needs a community string. Multiply by 43 connectors and you have a credential management problem.&lt;/p&gt;

&lt;p&gt;ConfigBuddy uses a &lt;strong&gt;unified credential vault&lt;/strong&gt; with &lt;strong&gt;protocol affinity matching&lt;/strong&gt;. Instead of associating each credential with a specific connector, credentials are stored with a protocol type (&lt;code&gt;aws-iam&lt;/code&gt;, &lt;code&gt;oauth2&lt;/code&gt;, &lt;code&gt;bearer&lt;/code&gt;, &lt;code&gt;basic&lt;/code&gt;, &lt;code&gt;ssh-key&lt;/code&gt;, &lt;code&gt;ldap&lt;/code&gt;, &lt;code&gt;snmp-v3&lt;/code&gt;) and metadata about which systems they're authorized for.&lt;/p&gt;

&lt;p&gt;When a connector needs to authenticate, it asks the credential vault: &lt;em&gt;"Give me a credential of type &lt;code&gt;oauth2&lt;/code&gt; that's authorized for &lt;code&gt;servicenow.cleveland.example.com&lt;/code&gt;."&lt;/em&gt; The vault returns the appropriate credential. If multiple credentials match, the most-specific match wins (system-specific beats organization-wide).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Credential rotation. When a credential is rotated, you update it in one place — the vault — and every connector that uses it picks up the new value automatically. No connector code touches credentials directly. The vault is the only thing that knows the secret values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The honest disclosure:&lt;/strong&gt; Right now, the vault stores credentials as base64-encoded strings, not encrypted. This is the most embarrassing TODO in the codebase, and it's the top item on the v3.1 roadmap. For homelab use on a trusted network, base64 is technically sufficient (it's not human-readable, and if the database is compromised you have bigger problems). For production deployment beyond a trusted network, you should wait for the AES-256 implementation or implement your own encryption layer.&lt;/p&gt;

&lt;p&gt;I'm telling you this in the architecture post because if you decide to deploy ConfigBuddy and you find out about the credential issue from someone else, you'll feel misled. I'd rather you find out from me.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;7. The enrichment pipeline: ITIL + TBM + BSM at discovery time
&lt;/h2&gt;

&lt;p&gt;Most CMDBs treat discovery and enrichment as separate processes. Discovery finds the CIs. Enrichment runs later — sometimes much later — to add business context. By the time enrichment finishes, the CIs are already stale.&lt;/p&gt;

&lt;p&gt;ConfigBuddy runs three enrichment engines inline with discovery:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ITIL v4 service management.&lt;/strong&gt; Every CI is classified into ITIL service categories at ingestion. Configuration items, service assets, change records — all populated as the discovery pipeline runs, not as a batch job overnight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TBM v5 cost transparency.&lt;/strong&gt; Cost attribution happens at discovery time. AWS resources are tagged with cost data from Cost Explorer; on-prem resources are attributed to cost centers via the source authority rules. By the time a CI lands in the graph, you can query "what does this service cost per month?" without a separate cost analytics run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BSM impact analysis.&lt;/strong&gt; Business service relationships are inferred from the technical graph. If a database CI is tagged with &lt;code&gt;application=customer-portal&lt;/code&gt;, the enrichment engine creates a &lt;code&gt;SUPPORTS&lt;/code&gt; relationship from the database to the customer-portal business service. Blast radius is queryable from day one.&lt;/p&gt;

&lt;p&gt;The reason these run inline rather than as batch jobs: &lt;strong&gt;freshness compounds&lt;/strong&gt;. If discovery runs hourly and enrichment runs nightly, your business context is always 23 hours stale. If enrichment runs inline, business context is as fresh as discovery. For incident response, this is the difference between a useful CMDB and a decorative one.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;8. Failure modes and what I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What I'd do the same:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Neo4j as primary store. Graph traversal economics are decisive.&lt;/li&gt;
&lt;li&gt;Pattern-learning discovery. The economics are too good to ignore.&lt;/li&gt;
&lt;li&gt;The connector split. JSON declarative connectors are the only way to keep maintenance sustainable.&lt;/li&gt;
&lt;li&gt;Inline enrichment. Freshness compounds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I'd do differently:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity resolution first.&lt;/strong&gt; I built discovery first and bolted identity resolution on later. It's the wrong order. Start with identity resolution architecture, then layer discovery on top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credential encryption from day one.&lt;/strong&gt; I deferred this and it became the most embarrassing TODO in the codebase. AES-256 from the start would have taken half a day; retrofitting it now is a multi-week project because so many components touch credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus metrics from day one.&lt;/strong&gt; I emit structured JSON logs but no Prometheus endpoint. Anyone running ConfigBuddy in production with a Prometheus + Grafana stack has to write a custom exporter. This is on the v3 roadmap but it should have been v1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better pattern versioning UX.&lt;/strong&gt; Pattern versioning works but the UI for reviewing pattern diffs and approving new versions is minimal. It needs to be first-class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connector test harness.&lt;/strong&gt; Each connector has tests, but there's no standardized harness for testing connectors against synthetic API responses. Adding one would make community contributions much easier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Failure modes I've had to design around:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM pattern learning is probabilistic. First-run discoveries can vary slightly based on how the LLM interprets ambiguous API responses. Pattern replay (deterministic) is the primary run path after the learning phase for exactly this reason.&lt;/li&gt;
&lt;li&gt;Neo4j is the source of truth. If the graph goes down, discovery results buffer in Kafka until it recovers. Recovery is automatic but slow under sustained backpressure.&lt;/li&gt;
&lt;li&gt;Pattern versioning edge cases. When an API change breaks a pattern replay, "latest wins" isn't always correct — sometimes the change affects one field mapping, not the whole strategy. Manual review is sometimes needed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;ConfigBuddy is open-source under Apache 2.0. The repo is at &lt;strong&gt;&lt;a href="https://github.com/Happy-Technologies-LLC/configbuddy" rel="noopener noreferrer"&gt;https://github.com/Happy-Technologies-LLC/configbuddy&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm not building a company around it. I'm releasing it as a credibility artifact and a research contribution to the CMDB conversation. If it's useful to you, star it, fork it, contribute to it. If you want commercial support, custom connectors, or implementation help, reach out to &lt;a href="mailto:commercial@happy-tech.biz"&gt;commercial@happy-tech.biz&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The two areas I most want feedback on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identity resolution.&lt;/strong&gt; How are you handling multi-source CI matching? What edge cases have you hit that the six-tier cascade doesn't cover?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pattern learning.&lt;/strong&gt; Has anyone else tried this approach? I've read a lot of CMDB literature and haven't seen the LLM-compile-and-replay pattern documented elsewhere. If you've seen it or built something similar, I'd love to compare notes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Drop questions in &lt;a href="https://github.com/Happy-Technologies-LLC/configbuddy/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt; or find me on LinkedIn. Best ideas in ConfigBuddy came from people telling me what was wrong with the previous version.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;ConfigBuddy is built and maintained by Happy Technologies LLC. Apache 2.0 licensed. CLA required for contributions.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>cmdb</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
