Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Writing a collector

A collector is anything that observes some state — a server, a database, a cloud account, a config file — and produces a Ledvar snapshot. The protocol guarantees the hashing; how you turn the real world into nodes is your modeling decision, and a handful of choices decide whether your drift is clean or noisy.

This is the hands-on companion to the normative modeling guide.

The shape of a fact

Each node is identity + content + labels + refs:

  • path — the node’s identity. Must be stable (doesn’t change when irrelevant things change) and unique. A bad identity produces churn: don’t key a process by PID, or a firewall rule by line number.
  • content — the observed values, as sets of strings. This is what, when it changes, is drift.
  • labels — human notes. Never hashed, never drift.
  • refs — annotation edges to other nodes. Never hashed.

The four rules that matter most

  1. Choose a stable identity. “If this thing is unchanged but the world around it shifts, does its path stay the same?” If not, pick a different path.
  2. Everything is a string — normalize it. "0.5""0.50"; "ACCEPT""accept". Pick one canonical form per attribute and emit it the same way every time.
  3. Sets are unordered and de-duplicated. If order matters, encode the position into the value — a firewall chain evaluated top-to-bottom becomes ["0:allow tcp:22", "1:allow tcp:80", "2:deny all"]; a bare set would hash the same in any order, hiding a reorder.
  4. Be complete for your scope. A missing node reads as removed. Emit the whole state of your scope, or narrow the scope so “complete” is something you can guarantee.

A minimal example

Observing two files and emitting a snapshot:

{
  "protocol_version": "0.1.0",
  "origin_id": "web-01",
  "provider_name": "files",
  "timestamp": 1718800000,
  "tree": [
    { "path": ["files", "/etc/ssh/sshd_config"],
      "content": { "mode": ["0600"], "owner": ["root"] } },
    { "path": ["files", "/etc/hosts"],
      "content": { "mode": ["0644"], "owner": ["root"] } }
  ]
}

This page is a starting point — a fuller, worked walkthrough (a real iptables / cloud collector) is coming. For the complete set of modeling rules, read the modeling guide.