What this system keeps in Grafana
Every dashboard the crew builds, every panel on it, every metric series and log stream it writes, the alert rules that wake the Remediator, and how each write actually travels. Read from the definitions the Publisher provisions from, not from a list somebody typed.
9 dashboards 40 panels 9 metric series 3 log streams 3 alert rules 8 write operations
| What | Where | Holds |
|---|---|---|
| https://dreamystairs2355.grafana.net | Folder Customs uid customs, which every dashboard and all three alert rules are provisioned into. | |
| grafanacloud-prom | the four metric series above, pushed over OTLP via OTLP HTTP, not the API |
|
| grafanacloud-logs | the three line kinds above via the Loki push API |
|
| Public views | 2 shared |
customs-overview,
customs-timeline
Grafana Cloud answers every embed
attempt with an enforcing frame-ancestors policy, so panels inside this
console are server-rendered PNGs unless the viewer below is deployed.
|
| Embeddable viewer | deployed | A grafana-oss instance holding no data of its own: its Loki and Mimir datasources read the same stores. Where it is running, the console frames live panels instead of rendering pictures of them. |
Customs: Findings Every finding the adjudicators wrote, as raw log lines and as a table. The line body is the finding JSON, so rule, class, severity, citation and the sourced flag are all queryable without a join back to the run store. customs-findings 2
| Panel | Kind | Reads | Query |
|---|---|---|---|
| Finding stream | logs | {app="customs", kind!="observation", asset=~"$asset"} |
|
| Findings | table | {app="customs", kind!="observation", asset=~"$asset"} | json severity="severity", sourced="sourced", citation="citation_ref", rationale="rationale", status="status", t_start="t_start" |
Seen in Not embedded: the raw finding stream, for reading in Grafana itself
The grid Squares, not bars: the same observation stream the lanes dashboard draws, as a status history -- one square per dimension per interval, coloured by worst severity. Built to sit under the console's own grid, whose axes carry the scene stills and the taxonomy icons Grafana cannot draw. customs-grid 1
| Panel | Kind | Reads | Query |
|---|---|---|---|
| Occurrences per scene | status-history | max by (dimension) (max_over_time({app="customs", kind=~"observation|watched", asset="$asset", dimension=~"$dim"} | json | run_id="$run" | unwrap max_severity [$__interval])) |
Seen in Timeline tab, as the scene-by-dimension grid the console draws its own axes around
Customs: Campaign History Across assets and time. Which assets tripped which rules, and how often, over the last thirty days. customs-history 3
| Panel | Kind | Reads | Query |
|---|---|---|---|
| Findings per asset, rolling 24 hours | timeseries | sum by (asset) (count_over_time({app="customs", kind!="observation", asset=~".+"}[1d])) |
|
| Every finding, newest first | table | {app="customs", kind!="observation"} | json severity="severity", sourced="sourced", citation="citation_ref", rationale="rationale", status="status", t_start="t_start" |
|
| The read path | text | panel text |
Seen in Archive, one live panel for the whole instance
Customs: Intelligence Every clearance this instance has performed, read across runs rather than one at a time. customs-insight 17
| Panel | Kind | Reads | Query |
|---|---|---|---|
| Every commercial, at its worst moment | bargauge | sort_desc(max by (asset) (max_over_time({app="customs", kind="finding"} | json | unwrap severity [$__range]))) |
|
| Findings | stat | sum(count_over_time({app="customs", kind="finding"}[$__range])) |
|
| Commercials judged | stat | count(sum by (asset) (count_over_time({app="customs", kind="observation"}[$__range]))) |
|
| Markets exercised | stat | count(sum by (market) (count_over_time({app="customs", kind="finding"}[$__range]))) |
|
| Rules tripped | stat | count(sum by (rule_id) (count_over_time({app="customs", kind="finding"}[$__range]))) |
|
| Frames observed | stat | sum(count_over_time({app="customs", kind="observation"}[$__range])) |
|
| Adjudications | stat | sum(count_over_time({app="customs", kind="verdict"}[$__range])) |
|
| What gets objected to, every run | barchart | sort_desc(sum by (dimension) (count_over_time({app="customs", kind="finding"}[$__range]))) |
|
| Which markets object most | barchart | sort_desc(sum by (market) (count_over_time({app="customs", kind="finding"}[$__range]))) |
|
| Dimension against market: where the friction is | table | sum by (dimension, market) (count_over_time({app="customs", kind="finding"}[$__range])) |
|
| How severe, across every finding | histogram | sum by (rule_id, market, asset) (max_over_time({app="customs", kind="finding"} | json | unwrap severity [$__range])) |
|
| Worst severity per market, against the blocking line | bargauge | sort_desc(max by (market) (max_over_time({app="customs", kind="finding"} | json | unwrap severity [$__range]))) |
|
| Market status over time, from Mimir | state-timeline | max by (market) (customs_market_status) |
|
| Objections per day, by class | timeseries | sum by (klass) (count_over_time({app="customs", kind="finding"}[1d])) |
|
| Adjudications: triggered against cleared | barchart | sort_desc(sum by (verdict) (count_over_time({app="customs", kind="verdict"}[$__range]))) |
|
| The rules that fire everywhere | table | topk(15, sum by (rule_id, klass, market) (count_over_time({app="customs", kind="finding"}[$__range]))) |
|
| The finding stream itself | logs | {app="customs", kind="finding"} | json |
Seen in Intelligence tab: the whole board in one frame, plus its dimension and market rankings as solo panels with the console's own icon axis down the side
Where the problems are Every observation dimension across a run's own mapped clock. Coloured by the worst severity any market attached to it, so a flat lane is a category that was looked at and cleared. customs-lanes 1
| Panel | Kind | Reads | Query |
|---|---|---|---|
| Every category the analyst can see, across the film's own clock | state-timeline | max by (dimension) (max_over_time({app="customs", kind="observation", asset="$asset"} | json | run_id="$run" | unwrap max_severity [$__interval])) |
Seen in Launch board's lane strip, and every archive card's squares (rendered to PNG when the viewer is not deployed)
Customs: Market Detail One market, in full: its current clearance status, which rules are holding it, every finding with its citation, and what the finding classes mean. customs-market 4
| Panel | Kind | Reads | Query |
|---|---|---|---|
| Clearance status | stat | customs_market_status{asset=~"$asset", market="$market"} |
|
| Rules holding this market | stat | max by (rule_id) (customs_blocking{asset=~"$asset", market="$market"}) |
|
| Findings in this market | table | {app="customs", kind!="observation", asset=~"$asset", market="$market"} | json severity="severity", sourced="sourced", citation="citation_ref", rationale="rationale", status="status", t_start="t_start" |
|
| Class semantics and the guard | text | panel text |
Seen in Not embedded: one market in full, for reading in Grafana itself
Customs: Clearance Overview Is this ad cleared to air, and where is it not. One tile per market, read from the current clock series customs_market_status. public customs-overview 5
| Panel | Kind | Reads | Query |
|---|---|---|---|
| Market clearance status | stat | customs_market_status{asset=~"$asset"} |
|
| Blocking findings by market | bargauge | count by (market) (customs_blocking{asset=~"$asset"}) |
|
| Markets cleared | stat | count(customs_market_status{asset=~"$asset"} == 0) |
|
| Markets blocked | stat | count(customs_market_status{asset=~"$asset"} == 2) |
|
| Stage errors (a clearance tool that silently skips a shot is worse than one that admits it) | stat | sum(customs_stage_error{asset=~"$asset"}) |
Seen in Launch board, as a rendered panel and a live embed; public link on every board · open the public view
Customs: Remediation What was changed and what it closed. Findings and their remediations as Grafana annotations, plus the blocking metric falling away as fixes land. customs-remediation 3
| Panel | Kind | Reads | Query |
|---|---|---|---|
| Findings and remediations | annolist | panel text | |
| How a remediation closes the loop | text | panel text | |
| Blocking severity as fixes land | timeseries | max by (market, rule_id) (customs_blocking{asset=~"$asset"}) |
Seen in Not embedded: what was changed and what it closed, with the annotations on it
Customs: Timeline The centrepiece. A commercial is a time series and the timecode is the x-axis: video second n is written at wall clock t0 + n, so a panel pinned to [t0, t0 + duration] reads as the ad's own timecode. public customs-timeline 4
| Panel | Kind | Reads | Query |
|---|---|---|---|
| Risk by market across the timecode | state-timeline | max by (market) (last_over_time(customs_risk{asset=~"$asset"}[$__interval])) |
|
| Risk by observation dimension | state-timeline | max by (dimension) (last_over_time(customs_risk{asset=~"$asset", dimension!="none"}[$__interval])) |
|
| The same risk, second by second | timeseries | max by (market) (last_over_time(customs_risk{asset=~"$asset"}[$__interval])) |
|
| Reading this page | text | panel text |
Seen in Launch board and the run's Timeline tab; public link · open the public view
A commercial is a time series and the timecode is the x axis, so one of these is written on the film's own clock: video second n lands at wall second t0+n. The other three are stamped at the real clock, because an alert rule's evaluation window has to mean what it says. Alerting reads only the current-clock three; the timecode axis reads only the mapped one.
| Series | Clock | Labels | Read by |
|---|---|---|---|
customs_risk |
mapped | asset, market, dimension | Timeline dashboard, the lane strip, the grid |
| one sample isthe worst severity in force at video second n, one sample per second of film, written at t0+n so the panel's x axis IS the timecode | |||
customs_market_status |
current | asset, market | Clearance overview, and the alert that watches for a market going down |
| one sample is0 cleared, 1 at risk, 2 blocked: one market's answer right now | |||
customs_blocking |
current | asset, market, rule_id | Overview, Market detail, Remediation, and the blocking alert |
| one sample isthe severity of one open, sourced, blocking finding; it drops to zero when a remediation is verified, which is what resolves the alert | |||
customs_stage_error |
current | asset, stage | Overview, and the stage-error alert |
| one sample ishow many times one stage failed on this asset: a tool that silently skips a shot is worse than one that admits it | |||
customs_collateral_drift |
current | asset, market, rule_id, method | the change record in the cutting room, and the mission feed |
| one sample isPSNR in dB inside an edited span, with the boxes the edit was about painted out of both files: how much of the shot changed that nobody asked to change. A patch leaves this high; an Omni rewrite re-renders the shot and leaves it far lower, which is the honest cost of a method that can fix a scene a patch cannot reach | |||
gen_ai_client_token_usage |
current | gen_ai.system, gen_ai.request.model, gen_ai.operation.name, gen_ai.token.type | nothing in these dashboards yet: it is there for Grafana Cloud's AI Observability, and for the question 'what did this run cost in tokens' |
| one sample istokens in and out of one Gemini call, labelled by the job it was doing: analyst, adjudicator, citation. OpenTelemetry's GenAI conventions, written through the same OTLP path as everything else here | |||
gen_ai_client_operation_duration_seconds |
current | gen_ai.system, gen_ai.request.model, gen_ai.operation.name | the same |
| one sample ishow long one model call took, by job | |||
customs_spend_eur_total |
current | the budget alert | |
| one sample iswhat today's generative fixes have cost, in euro, written after every charge | |||
customs_budget_remaining_eur |
current | the budget alert, which pauses automatic remediation | |
| one sample iswhat is left of the day's allowance: a staircase down, and the series the third alert rule watches so the loop that spends can be told to stop | |||
| Stream | Labels | One line is |
|---|---|---|
kind="finding" |
app, asset, market, klass, rule_id, dimension, kind | the whole finding as JSON: rule, statute, citation, severity, the window it covers and whether the guard blocked it |
kind="observation" |
app, asset, dimension, flagged, kind | what the analyst saw at that second, whether any market minded, and the rules that fired if one did |
kind="verdict" |
app, asset, market, dimension, verdict, kind | every market's answer about every observation, including the noes: a finding says France objected, a verdict says Germany looked at the same frame and did not |
| Written | Tags | Note |
|---|---|---|
| one per finding | customs, asset, market, rule_id, id | drawn across the span the finding covers, on the run's mapped clock |
| one per remediation | customs, asset, market, rule_id, id, resolved | written when a fix lands and the verifier confirms it |
An alert firing in Grafana is what starts a fix. The rule evaluates every 30 seconds in group customs, routes on team=customs to the contact point customs-webhook, and that contact point is a webhook pointed back at this service. The Remediator wakes on the POST. When the fix is verified the series drops to zero and Grafana resolves its own alert.
| Rule | Fires on | For | Instance per |
|---|---|---|---|
| customs_blocking_finding customs-blocking-finding |
max by (asset, market, rule_id) (customs_blocking) >= 70Blocking finding: asset {{ $labels.asset }} in market {{ $labels.market }} trips rule {{ $labels.rule_id }} at severity {{ $value }} |
0s | asset, market, rule_id |
| customs_market_at_risk customs-market-at-risk |
max by (asset, market) (customs_market_status) >= 1Market not cleared: asset {{ $labels.asset }} in market {{ $labels.market }} is at {{ $value }} (1 at risk, 2 blocked) |
0s | asset, market |
| customs_budget_low customs-budget-low |
min(customs_budget_remaining_eur) <= 9.0Generation budget down to {{ $value }} EUR: automatic remediation paused for the rest of the day |
0s |
The crew provisions its own folder, dashboards and alert rules at runtime through the official mcp-grafana server. Where release 1.1.0 has no write tool for an operation, it goes over the provisioning API instead and says so: an honest inventory beats a flattering one.
| Operation | MCP tool | Over REST |
|---|---|---|
ensure_folder |
create_folder | POST /api/folders |
ensure_dashboards |
update_dashboard | POST /api/dashboards/db |
ensure_alert_rules |
alerting_manage_rules | GET|POST|PUT /api/v1/provisioning/alert-rules
the rule group's 30s evaluation interval always goes over REST (PUT /api/v1/provisioning/folder/{uid}/rule-groups/customs): no MCP tool in 1.1.0 sets it
|
ensure_contact_point |
no tool in 1.1.0 | GET|POST|PUT /api/v1/provisioning/contact-points
alerting_manage_routing is read-only in 1.1.0
|
ensure_notification_policy |
no tool in 1.1.0 | GET|PUT /api/v1/provisioning/policies
alerting_manage_routing is read-only in 1.1.0
|
enable_public |
no tool in 1.1.0 | POST|GET|PATCH /api/dashboards/uid/{uid}/public-dashboards
no public-dashboard tool exists in mcp-grafana 1.1.0
|
render_png |
get_panel_image | GET /render/d-solo/{uid} |
query_history |
query_loki_logs | GET /api/datasources/proxy/uid/grafanacloud-logs/loki/api/v1/query_range |
| Call | What it does | Used for |
|---|---|---|
prom_range / prom_window / prom_instant |
PromQL against Mimir through the datasource proxy | the board's own sparklines and the market room's lanes when the app draws them itself |
loki_lines / loki_instant |
LogQL against Loki through the datasource proxy | the archive's history, and the agent's `query` tool |
query_history |
one rule's history for one brand, over thirty days | 'has this brand tripped this rule before?', asked by the agent and shown in the market room |
render_png |
server-side panel render at /render/d-solo/{uid} | every full-width Grafana panel in the console, because Grafana Cloud refuses to be framed |
embed_url |
builds the windowed URL of a panel or dashboard | the links out, and the iframes when the viewer is deployed |