---
title: "Dashboards Architecture"
url: "https://nsta1.github.io/Orleans.Lattice/docs/lattice.dashboards/architecture.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/docs/lattice.dashboards/architecture.md"
package: "Orleans.Lattice.Dashboards"
version: "9.9.0"
documents: "Orleans.Lattice 9.9.0 (release line 9.9)"
built: "2026-10-04"
all-pages: "https://nsta1.github.io/Orleans.Lattice/llms.txt"
bundle: "https://nsta1.github.io/Orleans.Lattice/docs/lattice.dashboards/llms-full.txt"
---
# Dashboards Architecture

Part of the [Dashboards documentation](README.md).

This document describes how the dashboards package is built and kept honest. It covers behaviour and packaging, not the names of internal types - the only public surface is described in [the API reference](api.md).

## Embedded JSON resources

Each dashboard is authored once as a Grafana dashboard model and compiled into the assembly as an embedded resource. The public accessor maps a `LatticeDashboardKind` to the matching resource and returns its bytes as a UTF-8 string. Because the JSON travels inside the assembly:

- Retrieval is a synchronous in-process read - no network call, no file-system dependency, and no external service to provision before a dashboard can be fetched.
- The dashboards are version-pinned to the library. A given package version always returns dashboards whose panels reference only instruments that version emits.

The panels reference instruments by the Prometheus series name that a host exporting through `.AddPrometheusExporter()` derives from each instrument name (for example `orleans.lattice.leaf.write.duration` is read as `orleans_lattice_leaf_write_duration_milliseconds_bucket`), not by any internal handle, so a dashboard works against any Prometheus-compatible backend that scrapes that exposition. An exposition that names series differently - the repository-context container's own, which appends no unit suffix and publishes no histogram buckets - leaves the affected panels empty; see [What these dashboards assume about the exposition](README.md#what-these-dashboards-assume-about-the-exposition).

## The bidirectional drift guard

The risk with bundled dashboards is silent drift: a renamed instrument leaves a panel charting nothing, or a newly added instrument ships with no panel. A CI test closes both directions:

1. **Every referenced metric exists.** Each metric name a panel on any bundled dashboard queries must resolve to an instrument declared in the library source, on whichever meter owns it. The `orleans_lattice_*` tokens on the `Overview`, `CommitPath`, `AtomicWrites` and `MaterialisedViews` dashboards are further confined to the `orleans.lattice` meter, and those on `Replication` to `orleans.lattice.replication` plus the core `orleans.lattice` meter, whose WAL instruments it charts. That confinement reads only `orleans_lattice_*` tokens, so it does not cover the `Overview` dashboard's three exact-KNN panels, which read the repository-context `Orleans.Lattice.Api.Mcp.RepoContext` meter; a separate test requires every `repocontext_*` token a panel names to belong to an instrument documented in the metric-to-panel map, and each of that meter's rows to agree with the panels about whether it is charted. A renamed or removed instrument fails the build.
2. **Every *observable* instrument is charted.** Each live instrument the guard can discover on the `orleans.lattice` and `orleans.lattice.replication` meters must be referenced by at least one panel, unless it is on the guard's short list of instruments deliberately left uncharted (each marked *(not charted)* in the [metric-to-panel map](metrics-to-panel-map.md)). A new instrument with no panel fails the build. The add-on meters' forward direction is enforced from their owning packages' tests instead, and so is the grain-index package's: it publishes its instruments on the core `orleans.lattice` meter but declares them on its own public `GrainIndexMetrics` type, so its package tests assert that the `GrainIndex` dashboard charts every one of them.

Direction 2 carries one caveat worth knowing. The guard discovers instruments by forcing the type initialisers of `LatticeMetrics` and `LatticeReplicationMetrics` and listening for what they publish, plus the instrument-name constants those two classes declare. An instrument created in a field initialiser on some *other* type is not constructed at test time, so the guard cannot see it and will not demand a panel for it - the `orleans.lattice.tag_index.reconcile.*` family is a live example of an unpaneled instrument with a green build. Declaring a new instrument on `LatticeMetrics` (or `LatticeReplicationMetrics`) keeps it inside the guard's reach; declaring it elsewhere means its panel is yours to add by hand. Its [metric-to-panel map](metrics-to-panel-map.md) row is still demanded: the documentation-coverage fixtures scan source for instrument-name literals rather than listening to the meter, so they fail the build until the row exists, but no fixture demands the panel.

The authoritative human-readable view of this pairing is the [metric-to-panel map](metrics-to-panel-map.md); the test is the enforcement. Together they keep the bundled dashboards from referencing a stale metric, and from silently omitting a new instrument the guard can observe.

## No replication link dependency

The package depends on the core library only. The `Replication` dashboard's panels query instruments on the `orleans.lattice.replication` meter, but those names are embedded as plain strings in the JSON - the package does not reference the replication assembly. This keeps the dashboards installable in local-only deployments without dragging in the replication package; the replication meter simply produces no data unless that package is separately registered on the silo.

## Provisioning templates

Alongside the embedded JSON, the package carries a `Provisioning/` folder with a baseline Grafana data-source template and a file-provider dashboards template. These are static YAML assets meant to be copied and adjusted, not code - they let an operator stand up file-system provisioning without hand-writing the boilerplate. See [Configuration](configuration.md) for how they fit together.

## Coverage at a glance

```mermaid
flowchart LR
  meters["orleans.lattice + orleans.lattice.replication instruments"]
  panels["Embedded dashboard JSON panels"]
  guard["CI drift guard"]
  meters -- "every instrument charted" --> guard
  panels -- "every metric exists" --> guard
  panels --> accessor["LatticeDashboards.GetGrafanaDashboardJson"]
  accessor --> grafana["Grafana (import or provisioning)"]
```

## See also

- [API Reference](api.md) - the public accessor and kinds.
- [Configuration](configuration.md) - meter registration and provisioning templates.
- [Metric-to-panel map](metrics-to-panel-map.md) - the enforced per-instrument coverage table.
