Decisions

ADR-013: Grafana dashboard templates live in `dark-factory`, not `gyrum-go`

The Phase 1 observability library lives in `github.com/gyrum-labs/gyrum-go/pkg/observ` ([ADR-008](008-single-observ-library-vs-per-service-convention.md)). A natural question is whether the Grafana dashboard templates —…

#013

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-factory already 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/observ exports 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-factory reviews and merges on its own rhythm. If dashboards lived in gyrum-go, tweaking a panel would need a library release, a go get -u in every service, and redeploys — overkill for a JSON edit.
  • The scaffold CLI needs a path to find templates. It reads GYRUM_TEMPLATES_PATH or falls back to a sibling checkout of dark-factory. A developer without dark-factory cloned 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 on gyrum-go (library) and not on dark-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 from dark-factory against 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 mv and 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, another gh pr create target, 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.json across 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: