ADR-013: Grafana dashboard templates live in dark-factory, not gyrum-go
Status: Accepted Date: 2026-04-21
Context
The Phase 1 observability library lives in github.com/gyrum-labs/gyrum-go/pkg/observ (ADR-008). A natural question is whether the Grafana dashboard templates — the Tier-0 set that every service's scaffold produces — should live next to the library, so the entire observability stack ships as one versioned artefact.
The counter-location is dark-factory, the repo that already owns the deploy-facing pieces: deploy.sh, Ansible playbooks, Caddy config, the compose file that stands up Loki / Prometheus / Alloy / Grafana, and the server inventory.
Constraints:
- Dashboards are consumed at deploy time, not compile time. The consumer is
deploy.sh(Phase 3) and the scaffold CLI (devtools), not any Go code. - Dashboards change on a different cadence than the library. Library releases are gated by the three-persona review; dashboards change when incidents teach us what panels we needed.
- Dashboards reference operational details (data source UIDs, Grafana server paths) that belong to the deploy environment, not to a reusable library.
- The library must stay small and dependency-free: its export is a Go package, not a bag of files at arbitrary paths.
Decision
Grafana dashboard templates — both service templates (Tier-0, service-*.json.tmpl) and platform dashboards (Tier-1, platform-*.json) — live in dark-factory/infrastructure/grafana-templates/. The gyrum-go library does not ship dashboard files.
The scaffold CLI in gyrum-labs/devtools reads templates from this directory (via a configurable path, defaulting to ~/work/gyrum-labs/dark-factory/infrastructure/grafana-templates/). The Phase 3 deploy stage will read from the same directory on the deploy host.
Consequences
- Dashboards ship with the deploy surface they configure.
dark-factoryalready owns the things dashboards point at (the Grafana instance, the Loki data source, the Prometheus data source). Co-locating dashboards with the infra they configure keeps the ownership story consistent — one PR changes deploy behaviour and the dashboards that reflect it. - The library stays narrow.
gyrum-go/pkg/observexports Go symbols. It does not export a directory of JSON files the Go build system doesn't understand. A service importing the library pulls only Go; the dashboard artefacts are a separate concern. - Dashboard authors don't need library release cadence. A dashboard PR in
dark-factoryreviews and merges on its own rhythm. If dashboards lived ingyrum-go, tweaking a panel would need a library release, ago get -uin every service, and redeploys — overkill for a JSON edit. - The scaffold CLI needs a path to find templates. It reads
GYRUM_TEMPLATES_PATHor falls back to a sibling checkout ofdark-factory. A developer withoutdark-factorycloned can still run the scaffold by setting the env var. Documented in the scaffold's--help. - Services don't depend on
dark-factory. Services depend ongyrum-go(library) and not ondark-factory. The scaffold is a developer/deploy tool; its output JSON lands in the service repo, but the service itself has no runtime dependency on the template source. The deploy-stage provisioning in Phase 3 will be triggered fromdark-factoryagainst the service's output files. - We accept a slight discovery cost. A new developer asks "where do I edit a dashboard?" and the answer is "the repo that does deploys, not the one with the logging library". The README in
infrastructure/grafana-templates/signals this, and the library's own README doesn't pretend it owns dashboards. - Moving is cheap if we change our minds. Templates are JSON files with one consumer (the scaffold CLI, via a configurable path). Relocating the directory is a
git mvand a scaffold-CLI default change. No downstream service cares.
Alternatives considered
- Templates in
gyrum-go/pkg/observ/dashboards/. Single place for the whole Phase 1+ story. Rejected: dashboard changes are deploy-adjacent, not library-adjacent, and coupling them to library release cadence is friction we feel every time we want to tweak a panel. Also couples services' library upgrade to "pick up the new dashboards", which inverts the dependency — dashboards should follow services, not lead them. - A dedicated repo:
gyrum-labs/grafana-templates. Clean separation of concerns. Rejected for now at our scale — another repo is another CI setup, anothergh pr createtarget, another context switch. We can extract later if the directory grows past casually-browsable size. - In each service repo individually. Per-service ownership. Rejected: the Tier-0 set is identical by design, and having five copies of
service-overview.jsonacross five services is the drift problem ADR-008 already named. One source of truth, materialised per service by the scaffold. - Dashboards as Go embeds in gyrum-go (via
embed). Would let Go code ship templates. Rejected as a worst-of-both: dashboards in the library repo (library cadence), but not importable as Go (only at runtime via a path the service writes to). Complicates the library for no user gain.
Supersedes: none Superseded by: