> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://rivet.chrisboulton.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://rivet.chrisboulton.com/_mcp/server.

# Facts and custom collectors

> Structured host inventory, static HCL paths, and custom JSON facts.

`rivet facts` prints a JSON snapshot of a host with three parts:

* `target`: distribution, version, architecture, init system, hashed machine identity, and [capabilities](/reference/capabilities). Compilation and apply use these to check the bundle fits the host.
* `collected_at_ms`: when the snapshot was taken.
* `facts`: inventory, described below, which configuration reads as `facts.<group>...`.

```sh
rivet facts --local > facts.json             # on the host
rivet facts --target app-1 > app-1.json      # over SSH
```

Collection only reads. Groups whose sources aren't available are left out, as are individual fields that can't be read. A failure to read the OS or machine identity is an error. Older snapshots without a `facts` section, and bare target files like `examples/file/target.json`, still work.

## Built-in facts

| Group            | Contents                                                                                                                                                                                                                                                                   |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cloud`          | `provider` and normalized `attributes`: instance ID, region, availability zone, hostname, and instance type/image/account/project identifiers when present.                                                                                                                |
| `disks`          | Whole block devices keyed by kernel name, with byte size, model, vendor, serial, WWN, and rotational type when available.                                                                                                                                                  |
| `identity`       | Hashed `machine_id`; effective caller UID/GID, local passwd and group names, and `privileged`.                                                                                                                                                                             |
| `memory`         | System and swap `total_bytes`, `available_bytes`, and `used_bytes`.                                                                                                                                                                                                        |
| `mounts`         | Mountpoints keyed by decoded path, with device, filesystem, options, and space in bytes when statvfs succeeds.                                                                                                                                                             |
| `networking`     | Hostname, domain/FQDN when the kernel hostname includes a domain, interface links, IPv4/IPv6 bindings, and the lowest-metric active IPv4 default interface.                                                                                                                |
| `os`             | Distribution ID, version, name, full name, architecture, init system, and kernel name/release.                                                                                                                                                                             |
| `partitions`     | Device paths with partition number, disk when identifiable, byte size and offset, and available filesystem/UUID/label information.                                                                                                                                         |
| `processors`     | Logical CPU count and models, physical socket and core counts when reported, architecture, and online CPU range. Counts describe the host, not cgroup quota.                                                                                                               |
| `gpus`           | PCI display controllers, including unloaded devices, keyed by PCI address; class, device and vendor IDs, known vendor name, and driver. NVIDIA devices also include model name, UUID, NVML index, driver version, architecture, and available telemetry under `metrics`.   |
| `security`       | Observed `selinux.mode` (enforcing/permissive/disabled), `apparmor.enabled`, `secure_boot.enabled`, and `fips.enabled`. Missing or unreadable signals are omitted, not reported as disabled.                                                                               |
| `numa`           | `nodes` keyed by node ID, with CPU arrays, total `memory_bytes`, distances keyed by destination node ID, and local `gpus` (PCI addresses) / `network_interfaces` when identifiable.                                                                                        |
| `dmi`            | Manufacturer, BIOS release date/vendor/version, board asset tag/manufacturer/product/serial number, chassis asset tag and type, and product name/version/serial number/UUID. Chassis type is the sysfs numeric code as a string. Permission-restricted fields are omitted. |
| `virtualization` | `is_virtual`, plus detected `hypervisor` and `container` names. VM and container layers are queried independently; unknown detection is omitted.                                                                                                                           |
| `uptime`         | Whole `seconds`, `hours`, and `days` since boot, formatted `uptime`, and optional `boot_id`.                                                                                                                                                                               |

These groups follow [Puppet's structured fact organization](https://help.puppet.com/core/current/Content/PuppetCore/Markdown/core_facts.htm#ModernFacts), using `virtualization` and `uptime` as group names. NUMA uses the kernel's [node sysfs interface](https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node); it describes host topology rather than process affinity or cgroup limits. Security reads runtime kernel controls and the EFI SecureBoot variable (after the [efivarfs attribute prefix](https://www.kernel.org/doc/html/v5.10/filesystems/efivarfs.html)). Virtualization uses `systemd-detect-virt --vm` / `--container`, with hypervisor and container marker files as fallbacks. Both detection commands must report no virtualization before `is_virtual` is false.

Cloud facts come from the instance metadata service at `169.254.169.254`: [AWS IMDSv2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html), [Azure IMDS](https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service), or [GCP metadata](https://docs.cloud.google.com/compute/docs/metadata/querying-metadata). Requests skip proxies, time out after one second, and the whole cloud lookup is limited to five seconds. Only inventory fields are collected, never credentials or user data.

The public `/run/cloud-init/instance-data.json` cache is a fallback for unavailable API fields or providers without a native collector, such as OpenStack. API values take precedence, and cached fields are merged only for the same provider. AWS/GCP/Azure also have DMI detection when metadata is unavailable; attributes can then be empty. The sensitive cloud-init cache is never read. See [cloud-init's standardized instance data](https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html).

Networking uses sysfs for links and the installed `ip` command for address bindings; without iproute2, link facts still appear and address fields are omitted. Disk/filesystem identifiers come from sysfs, the optional udev database, and mounted filesystem records.

NVIDIA telemetry is read through the installed NVIDIA driver library; `nvidia-smi` isn't needed. Without the driver library, GPUs still appear with their PCI and driver details, and readings that are unsupported or not permitted are left out. AMD and Intel GPUs currently show PCI and driver details only.

NVIDIA `metrics` follows the names in [AWS's GPU metric reference](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-NVIDIA-GPU.html):

| Fields                                                                                          | Units                                                                                                                              |
| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `utilization_gpu`, `utilization_memory`, `fan_speed`                                            | Percent; memory utilization measures activity, not allocated capacity. Fan speed uses fan 0.                                       |
| `temperature_gpu`                                                                               | Celsius                                                                                                                            |
| `power_draw`                                                                                    | Watts                                                                                                                              |
| `memory_total`, `memory_used`, `memory_free`                                                    | MiB, matching NVIDIA memory reporting; exact `memory_total_bytes`, `memory_used_bytes`, and `memory_free_bytes` are also included. |
| `pcie_link_gen_current`, `pcie_link_width_current`                                              | Generation and lane count                                                                                                          |
| `encoder_stats_session_count`, `encoder_stats_average_fps`, `encoder_stats_average_latency`     | Sessions, frames per second, microseconds                                                                                          |
| `clocks_current_graphics`, `clocks_current_sm`, `clocks_current_memory`, `clocks_current_video` | MHz                                                                                                                                |

GPU telemetry describes the collection instant, like available memory and mount space; it is not an apply-time target constraint.

## Use facts in HCL

Compile against a snapshot:

```sh
rivet facts --local > facts.json
rivet compile ./site --facts facts.json --out host.bundle
```

Nested paths, literal string keys, and literal array indices work:

```hcl
file "host_summary" {
  path = "/etc/host-summary"
  content = "${facts.os.name}: ${facts.processors.count} CPUs (${facts.processors.models[0]})"
}

output "root_filesystem" {
  type = string
  value = facts.mounts["/"].filesystem
}

output "cpu_count" {
  type = number
  value = facts.processors.count
}
```

`facts.distribution`, `facts.version`, `facts.architecture`, and `facts.init_system` are also available, including when compiling against a bare target file. Fact values keep their JSON types: whole numbers become integers, and other numbers stay fractional. Paths can use computed indices, splats, and `for` expressions, and integer facts can drive `count`. A missing fact or invalid index is a compile error.

Only facts that configuration actually uses affect the catalog digest. Values such as free memory, mount usage, and network addresses describe the moment of collection and aren't rechecked during apply, so prefer stable facts when making decisions.

## Custom JSON facts

Add your own facts by putting files in `/etc/rivet/facts.d`, or another directory passed with `--facts-dir DIR`:

```sh
mkdir -p ./facts.d
printf '%s\n' '{"variety":"russet","count":3}' > ./facts.d/potato.rb
rivet facts --local --facts-dir ./facts.d > facts.json
```

The final extension is removed: `potato.rb`, `potato.json`, or `potato` supplies `facts.potato`. An executable file is executed directly (use a valid shebang); its stdout must contain one complete JSON value. A non-executable file is read as JSON regardless of extension. Objects, arrays, scalars, booleans, and null retain their types. The example exposes `facts.potato.variety` and `facts.potato.count`.

Files are processed in sorted filename order, non-recursively. Hidden files and directories are skipped; symlinks to regular files are followed. A missing directory supplies no custom facts. Duplicate basenames and collisions with any built-in fact or the four legacy aliases fail before custom executables run. Non-UTF-8 basenames, invalid JSON, unreadable files, failed execution, and nonzero exits fail collection without emitting a partial snapshot or exposing JSON/stderr in the error.

Executables run as the invoking user, with the facts directory as their working directory, stdin closed, and stderr discarded. Each executable has a ten-second budget; timeout/output-limit failures terminate its process group. JSON from each file and combined custom input is bounded to 1 MiB; the complete serialized snapshot must also fit the compiler's 1 MiB input limit. Scripts must keep stdout free of diagnostic messages.

Custom executables run during `rivet facts` and each converging `run`, including noop/offline. Compile/validate and `run --prepare-only --facts` consume the saved snapshot. SSH compatibility probes and direct plan and apply collect only target identity and capability data, without full inventory collection or custom executables.

## Inventory and verified identity

Facts are reported by the host itself, so they can't prove who a host is. Cloud metadata and custom facts can be forged by anyone with root on the host. To classify hosts by verified identity, use the [compile service](/operations/compile-service) and read [`trusted` claims](/language/trusted-facts) instead.