@mnemom/aip-otel-exporter 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +172 -0
  2. package/dashboards/README.md +92 -0
  3. package/dashboards/datadog-aip-overview.json +311 -0
  4. package/dashboards/grafana-aip-detail.json +403 -0
  5. package/dashboards/grafana-aip-overview.json +431 -0
  6. package/dist/attributes.d.ts +70 -0
  7. package/dist/attributes.d.ts.map +1 -0
  8. package/dist/attributes.js +81 -0
  9. package/dist/attributes.js.map +1 -0
  10. package/dist/auto/aap-instrumentation.d.ts +23 -0
  11. package/dist/auto/aap-instrumentation.d.ts.map +1 -0
  12. package/dist/auto/aap-instrumentation.js +112 -0
  13. package/dist/auto/aap-instrumentation.js.map +1 -0
  14. package/dist/auto/aip-instrumentation.d.ts +22 -0
  15. package/dist/auto/aip-instrumentation.d.ts.map +1 -0
  16. package/dist/auto/aip-instrumentation.js +87 -0
  17. package/dist/auto/aip-instrumentation.js.map +1 -0
  18. package/dist/auto/index.d.ts +20 -0
  19. package/dist/auto/index.d.ts.map +1 -0
  20. package/dist/auto/index.js +22 -0
  21. package/dist/auto/index.js.map +1 -0
  22. package/dist/index.d.ts +33 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +58 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/manual/record-coherence.d.ts +12 -0
  27. package/dist/manual/record-coherence.d.ts.map +1 -0
  28. package/dist/manual/record-coherence.js +21 -0
  29. package/dist/manual/record-coherence.js.map +1 -0
  30. package/dist/manual/record-drift.d.ts +14 -0
  31. package/dist/manual/record-drift.d.ts.map +1 -0
  32. package/dist/manual/record-drift.js +48 -0
  33. package/dist/manual/record-drift.js.map +1 -0
  34. package/dist/manual/record-integrity-check.d.ts +19 -0
  35. package/dist/manual/record-integrity-check.d.ts.map +1 -0
  36. package/dist/manual/record-integrity-check.js +92 -0
  37. package/dist/manual/record-integrity-check.js.map +1 -0
  38. package/dist/manual/record-verification.d.ts +14 -0
  39. package/dist/manual/record-verification.d.ts.map +1 -0
  40. package/dist/manual/record-verification.js +40 -0
  41. package/dist/manual/record-verification.js.map +1 -0
  42. package/dist/manual/span-builder.d.ts +28 -0
  43. package/dist/manual/span-builder.d.ts.map +1 -0
  44. package/dist/manual/span-builder.js +42 -0
  45. package/dist/manual/span-builder.js.map +1 -0
  46. package/dist/metrics/integrity-metrics.d.ts +44 -0
  47. package/dist/metrics/integrity-metrics.d.ts.map +1 -0
  48. package/dist/metrics/integrity-metrics.js +128 -0
  49. package/dist/metrics/integrity-metrics.js.map +1 -0
  50. package/dist/types.d.ts +205 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +8 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/workers/index.d.ts +8 -0
  55. package/dist/workers/index.d.ts.map +1 -0
  56. package/dist/workers/index.js +7 -0
  57. package/dist/workers/index.js.map +1 -0
  58. package/dist/workers/otlp-serializer.d.ts +90 -0
  59. package/dist/workers/otlp-serializer.d.ts.map +1 -0
  60. package/dist/workers/otlp-serializer.js +133 -0
  61. package/dist/workers/otlp-serializer.js.map +1 -0
  62. package/dist/workers/workers-exporter.d.ts +26 -0
  63. package/dist/workers/workers-exporter.d.ts.map +1 -0
  64. package/dist/workers/workers-exporter.js +228 -0
  65. package/dist/workers/workers-exporter.js.map +1 -0
  66. package/package.json +83 -0
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # @mnemom/aip-otel-exporter
2
+
3
+ [![npm](https://img.shields.io/npm/v/@mnemom/aip-otel-exporter)](https://www.npmjs.com/package/@mnemom/aip-otel-exporter)
4
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](../../LICENSE)
5
+
6
+ OpenTelemetry exporter for [AIP](https://github.com/mnemom/aip) integrity checkpoints and [AAP](https://github.com/mnemom/aap) verification results.
7
+
8
+ Send AIP/AAP telemetry to any OTel-compatible observability platform (Langfuse, Arize Phoenix, Datadog, Grafana) with zero custom code.
9
+
10
+ ## Three Layers
11
+
12
+ | Layer | Import | OTel SDK Required | Use Case |
13
+ |---|---|---|---|
14
+ | **Manual API** | `@mnemom/aip-otel-exporter` | Yes | Works everywhere with OTel SDK |
15
+ | **Auto-instrumentation** | `@mnemom/aip-otel-exporter/auto` | Yes | Wraps AIP/AAP calls automatically (Node.js) |
16
+ | **CF Workers adapter** | `@mnemom/aip-otel-exporter/workers` | No | Cloudflare Workers (no OTel SDK needed) |
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @mnemom/aip-otel-exporter
22
+ # Peer dependency (optional — required for Manual API and Auto-instrumentation):
23
+ npm install @opentelemetry/api
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ### Manual API
29
+
30
+ ```typescript
31
+ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-base";
32
+ import { createAIPOTelRecorder } from "@mnemom/aip-otel-exporter";
33
+
34
+ const provider = new NodeTracerProvider();
35
+ // ... configure your OTLP exporter (Langfuse, Datadog, etc.)
36
+ provider.register();
37
+
38
+ const recorder = createAIPOTelRecorder({ tracerProvider: provider });
39
+
40
+ // After an AIP integrity check:
41
+ recorder.recordIntegrityCheck(signal);
42
+
43
+ // After an AAP verification:
44
+ recorder.recordVerification(result);
45
+
46
+ // After an AAP coherence check:
47
+ recorder.recordCoherence(result);
48
+
49
+ // After AAP drift detection:
50
+ recorder.recordDrift(alerts, tracesAnalyzed);
51
+ ```
52
+
53
+ ### Auto-instrumentation (Node.js)
54
+
55
+ ```typescript
56
+ import { instrument } from "@mnemom/aip-otel-exporter/auto";
57
+
58
+ // Automatically wraps AIPClient.check() and verifyTrace()
59
+ instrument();
60
+ ```
61
+
62
+ ### Cloudflare Workers
63
+
64
+ ```typescript
65
+ import { createWorkersExporter } from "@mnemom/aip-otel-exporter/workers";
66
+
67
+ export default {
68
+ async fetch(request, env, ctx) {
69
+ const exporter = createWorkersExporter({
70
+ endpoint: env.OTLP_ENDPOINT,
71
+ authorization: `Bearer ${env.OTLP_TOKEN}`,
72
+ });
73
+
74
+ // ... your handler logic ...
75
+ exporter.recordIntegrityCheck(signal);
76
+
77
+ ctx.waitUntil(exporter.flush());
78
+ return new Response("OK");
79
+ },
80
+ };
81
+ ```
82
+
83
+ ## Span Hierarchy
84
+
85
+ Integrity/alignment spans are children of the current active span:
86
+
87
+ ```
88
+ invoke_agent (existing)
89
+ └── aip.integrity_check (our span)
90
+ ├── event: aip.concern (one per concern)
91
+ └── event: aip.drift_alert (conditional)
92
+ └── aap.verify_trace (our span)
93
+ └── event: aap.violation (one per violation)
94
+ ```
95
+
96
+ ## Attributes
97
+
98
+ ### `aip.integrity_check` span
99
+
100
+ | Attribute | Type | Description |
101
+ |---|---|---|
102
+ | `aip.integrity.checkpoint_id` | string | Checkpoint ID |
103
+ | `aip.integrity.verdict` | string | clear / review_needed / boundary_violation |
104
+ | `aip.integrity.proceed` | boolean | Whether to proceed |
105
+ | `aip.integrity.recommended_action` | string | Recommended action |
106
+ | `aip.integrity.concerns_count` | int | Number of concerns |
107
+ | `aip.integrity.agent_id` | string | Agent ID |
108
+ | `aip.integrity.card_id` | string | Alignment Card ID |
109
+ | `aip.integrity.session_id` | string | Session ID |
110
+ | `aip.integrity.thinking_hash` | string | SHA-256 of thinking block |
111
+ | `aip.integrity.analysis_model` | string | Analysis LLM model |
112
+ | `aip.integrity.analysis_duration_ms` | float | Analysis duration |
113
+ | `aip.integrity.thinking_tokens` | int | Original thinking tokens |
114
+ | `aip.integrity.truncated` | boolean | Whether thinking was truncated |
115
+ | `aip.integrity.extraction_confidence` | float | Extraction confidence |
116
+ | `aip.conscience.consultation_depth` | string | surface / standard / deep |
117
+ | `aip.conscience.values_checked_count` | int | Values checked count |
118
+ | `aip.conscience.conflicts_count` | int | Conflicts count |
119
+ | `aip.window.size` | int | Window size |
120
+ | `aip.window.integrity_ratio` | float | Integrity ratio (0.0-1.0) |
121
+ | `aip.window.drift_alert_active` | boolean | Drift alert active |
122
+ | `gen_ai.evaluation.verdict` | string | GenAI SIG alias |
123
+ | `gen_ai.evaluation.score` | float | GenAI SIG alias |
124
+
125
+ ### `aap.verify_trace` span
126
+
127
+ | Attribute | Type |
128
+ |---|---|
129
+ | `aap.verification.result` | boolean |
130
+ | `aap.verification.similarity_score` | float |
131
+ | `aap.verification.violations_count` | int |
132
+ | `aap.verification.warnings_count` | int |
133
+ | `aap.verification.trace_id` | string |
134
+ | `aap.verification.card_id` | string |
135
+ | `aap.verification.duration_ms` | float |
136
+ | `aap.verification.checks_performed` | string |
137
+
138
+ ## Metrics
139
+
140
+ | Metric | Type | Labels |
141
+ |---|---|---|
142
+ | `aip.integrity_checks.total` | Counter | verdict, agent_id |
143
+ | `aip.concerns.total` | Counter | category, severity |
144
+ | `aip.analysis.duration_ms` | Histogram | verdict |
145
+ | `aip.window.integrity_ratio` | Histogram | — |
146
+ | `aip.drift_alerts.total` | Counter | — |
147
+ | `aap.verifications.total` | Counter | verified |
148
+ | `aap.violations.total` | Counter | type, severity |
149
+ | `aap.verification.duration_ms` | Histogram | — |
150
+ | `aap.coherence.score` | Histogram | compatible |
151
+
152
+ ## Dashboard Templates
153
+
154
+ Pre-built dashboards are included in the `dashboards/` directory:
155
+
156
+ - `grafana-aip-overview.json` — Grafana system overview
157
+ - `grafana-aip-detail.json` — Grafana per-agent deep-dive
158
+ - `datadog-aip-overview.json` — Datadog importable dashboard
159
+
160
+ See `dashboards/README.md` for import instructions.
161
+
162
+ ## Performance
163
+
164
+ | Operation | Target | Actual |
165
+ |---|---|---|
166
+ | `recordIntegrityCheck()` | <1ms | Run `npm run bench` |
167
+ | `recordVerification()` | <1ms | Run `npm run bench` |
168
+ | Workers OTLP serialize | <0.5ms | Run `npm run bench` |
169
+
170
+ ## License
171
+
172
+ [Apache 2.0](../../LICENSE)
@@ -0,0 +1,92 @@
1
+ # Dashboard Templates
2
+
3
+ Pre-built dashboard templates for visualizing AIP integrity checks and AAP verification results exported via `@mnemom/aip-otel-exporter`.
4
+
5
+ ## Available Dashboards
6
+
7
+ | File | Platform | Description |
8
+ |------|----------|-------------|
9
+ | `grafana-aip-overview.json` | Grafana | Fleet-wide overview: verdict distribution, check rate, integrity ratio, concerns, AAP pass rate |
10
+ | `grafana-aip-detail.json` | Grafana | Per-agent deep dive: integrity ratio timeline, concern breakdown, verdict history, top concerns |
11
+ | `datadog-aip-overview.json` | Datadog | Fleet-wide overview using Datadog widget types: sunburst, timeseries, toplist, query_value |
12
+
13
+ ## Importing into Grafana
14
+
15
+ ### Via the Grafana UI
16
+
17
+ 1. Open your Grafana instance.
18
+ 2. Navigate to **Dashboards > New > Import**.
19
+ 3. Click **Upload dashboard JSON file** and select the desired `.json` file.
20
+ 4. Select your Prometheus data source when prompted (the dashboards use a `DS_PROMETHEUS` input variable).
21
+ 5. Click **Import**.
22
+
23
+ ### Via the Grafana API
24
+
25
+ ```bash
26
+ curl -X POST \
27
+ -H "Content-Type: application/json" \
28
+ -H "Authorization: Bearer <your-grafana-api-key>" \
29
+ -d @grafana-aip-overview.json \
30
+ https://<your-grafana-host>/api/dashboards/import
31
+ ```
32
+
33
+ Wrap the dashboard JSON in the import envelope if your Grafana version requires it:
34
+
35
+ ```bash
36
+ jq '{ dashboard: ., overwrite: true, inputs: [{ name: "DS_PROMETHEUS", type: "datasource", pluginId: "prometheus", value: "your-prometheus-uid" }] }' \
37
+ grafana-aip-overview.json | \
38
+ curl -X POST \
39
+ -H "Content-Type: application/json" \
40
+ -H "Authorization: Bearer <your-grafana-api-key>" \
41
+ -d @- \
42
+ https://<your-grafana-host>/api/dashboards/import
43
+ ```
44
+
45
+ ## Importing into Datadog
46
+
47
+ ### Via the Datadog API
48
+
49
+ ```bash
50
+ curl -X POST \
51
+ -H "Content-Type: application/json" \
52
+ -H "DD-API-KEY: <your-datadog-api-key>" \
53
+ -H "DD-APPLICATION-KEY: <your-datadog-app-key>" \
54
+ -d @datadog-aip-overview.json \
55
+ https://api.datadoghq.com/api/v1/dashboard
56
+ ```
57
+
58
+ For EU-region accounts, use `api.datadoghq.eu` instead of `api.datadoghq.com`.
59
+
60
+ ## Metric Name Mapping
61
+
62
+ The `@mnemom/aip-otel-exporter` package emits metrics with dotted names (e.g., `aip.integrity_checks.total`). How these appear in your observability backend depends on the backend:
63
+
64
+ | Backend | Metric name format | Example |
65
+ |---------|-------------------|---------|
66
+ | **Prometheus** / **Grafana** | Dots replaced with underscores | `aip_integrity_checks_total` |
67
+ | **Datadog** | Dots preserved | `aip.integrity_checks.total` |
68
+ | **Langfuse** | Trace attributes only (no metrics) | `aip.integrity.verdict` span attribute |
69
+
70
+ The Grafana dashboards use underscore-separated names (`aip_integrity_checks_total`) to match Prometheus conventions. The Datadog dashboard uses dot-separated names (`aip.integrity_checks.total`) to match Datadog conventions.
71
+
72
+ ## Metrics Reference
73
+
74
+ | Metric | Type | Labels | Description |
75
+ |--------|------|--------|-------------|
76
+ | `aip.integrity_checks.total` | Counter | `verdict`, `agent_id` | Total AIP integrity checks |
77
+ | `aip.concerns.total` | Counter | `category`, `severity` | Total integrity concerns raised |
78
+ | `aip.analysis.duration_ms` | Histogram | | Analysis duration in milliseconds |
79
+ | `aip.window.integrity_ratio` | Histogram | | Sliding window integrity ratio (0-1) |
80
+ | `aip.drift_alerts.total` | Counter | `severity` | AIP drift alerts |
81
+ | `aap.verifications.total` | Counter | `verified` | Total AAP trace verifications |
82
+ | `aap.violations.total` | Counter | `type`, `severity` | Total AAP violations |
83
+ | `aap.verification.duration_ms` | Histogram | | AAP verification duration in milliseconds |
84
+ | `aap.coherence.score` | Histogram | | AAP coherence scores |
85
+
86
+ ## Customization
87
+
88
+ These dashboards are starting points. Common modifications:
89
+
90
+ - **Add alerting rules**: Create Grafana alerts on integrity ratio dropping below a threshold, or Datadog monitors on violation spikes.
91
+ - **Filter by environment**: Add a `$environment` template variable and filter metrics by an `environment` label if you tag your OTel resources with deployment environment.
92
+ - **Add additional agents**: The Grafana detail dashboard uses a single `$agent_id` selector. Switch it to multi-select if you want to compare agents side by side.
@@ -0,0 +1,311 @@
1
+ {
2
+ "title": "AIP Integrity Overview",
3
+ "description": "Overview dashboard for AIP integrity checks and AAP verification results exported via @mnemom/aip-otel-exporter.",
4
+ "layout_type": "ordered",
5
+ "is_read_only": false,
6
+ "notify_list": [],
7
+ "reflow_type": "fixed",
8
+ "tags": ["aip", "integrity", "otel"],
9
+ "widgets": [
10
+ {
11
+ "definition": {
12
+ "title": "Integrity Checks",
13
+ "type": "group",
14
+ "layout_type": "ordered",
15
+ "widgets": [
16
+ {
17
+ "definition": {
18
+ "title": "Integrity Verdict Distribution",
19
+ "type": "sunburst",
20
+ "requests": [
21
+ {
22
+ "formulas": [{ "formula": "query1" }],
23
+ "queries": [
24
+ {
25
+ "data_source": "metrics",
26
+ "name": "query1",
27
+ "query": "sum:aip.integrity_checks.total{*} by {verdict}",
28
+ "aggregator": "sum"
29
+ }
30
+ ],
31
+ "response_format": "scalar"
32
+ }
33
+ ]
34
+ },
35
+ "layout": { "x": 0, "y": 0, "width": 4, "height": 3 }
36
+ },
37
+ {
38
+ "definition": {
39
+ "title": "Integrity Checks per Minute",
40
+ "type": "timeseries",
41
+ "requests": [
42
+ {
43
+ "formulas": [{ "formula": "query1" }],
44
+ "queries": [
45
+ {
46
+ "data_source": "metrics",
47
+ "name": "query1",
48
+ "query": "sum:aip.integrity_checks.total{*} by {verdict}.as_rate()",
49
+ "aggregator": "avg"
50
+ }
51
+ ],
52
+ "response_format": "timeseries",
53
+ "display_type": "line"
54
+ }
55
+ ],
56
+ "yaxis": { "label": "checks/min", "include_zero": true }
57
+ },
58
+ "layout": { "x": 4, "y": 0, "width": 4, "height": 3 }
59
+ },
60
+ {
61
+ "definition": {
62
+ "title": "Current Integrity Ratio",
63
+ "type": "query_value",
64
+ "requests": [
65
+ {
66
+ "formulas": [
67
+ {
68
+ "formula": "query1 / (query1 + query2 + query3)",
69
+ "limit": { "count": 1, "order": "desc" }
70
+ }
71
+ ],
72
+ "queries": [
73
+ {
74
+ "data_source": "metrics",
75
+ "name": "query1",
76
+ "query": "sum:aip.integrity_checks.total{verdict:clear}",
77
+ "aggregator": "sum"
78
+ },
79
+ {
80
+ "data_source": "metrics",
81
+ "name": "query2",
82
+ "query": "sum:aip.integrity_checks.total{verdict:review_needed}",
83
+ "aggregator": "sum"
84
+ },
85
+ {
86
+ "data_source": "metrics",
87
+ "name": "query3",
88
+ "query": "sum:aip.integrity_checks.total{verdict:boundary_violation}",
89
+ "aggregator": "sum"
90
+ }
91
+ ],
92
+ "response_format": "scalar"
93
+ }
94
+ ],
95
+ "autoscale": false,
96
+ "precision": 2,
97
+ "custom_unit": "%",
98
+ "conditional_formats": [
99
+ { "comparator": ">=", "value": 0.95, "palette": "white_on_green" },
100
+ { "comparator": ">=", "value": 0.85, "palette": "white_on_yellow" },
101
+ { "comparator": "<", "value": 0.85, "palette": "white_on_red" }
102
+ ]
103
+ },
104
+ "layout": { "x": 8, "y": 0, "width": 4, "height": 3 }
105
+ }
106
+ ]
107
+ },
108
+ "layout": { "x": 0, "y": 0, "width": 12, "height": 4 }
109
+ },
110
+ {
111
+ "definition": {
112
+ "title": "Concerns & Analysis",
113
+ "type": "group",
114
+ "layout_type": "ordered",
115
+ "widgets": [
116
+ {
117
+ "definition": {
118
+ "title": "Concerns by Category",
119
+ "type": "toplist",
120
+ "requests": [
121
+ {
122
+ "formulas": [
123
+ {
124
+ "formula": "query1",
125
+ "limit": { "count": 10, "order": "desc" }
126
+ }
127
+ ],
128
+ "queries": [
129
+ {
130
+ "data_source": "metrics",
131
+ "name": "query1",
132
+ "query": "sum:aip.concerns.total{*} by {category}",
133
+ "aggregator": "sum"
134
+ }
135
+ ],
136
+ "response_format": "scalar"
137
+ }
138
+ ]
139
+ },
140
+ "layout": { "x": 0, "y": 0, "width": 4, "height": 3 }
141
+ },
142
+ {
143
+ "definition": {
144
+ "title": "Concerns by Severity",
145
+ "type": "toplist",
146
+ "requests": [
147
+ {
148
+ "formulas": [
149
+ {
150
+ "formula": "query1",
151
+ "limit": { "count": 10, "order": "desc" }
152
+ }
153
+ ],
154
+ "queries": [
155
+ {
156
+ "data_source": "metrics",
157
+ "name": "query1",
158
+ "query": "sum:aip.concerns.total{*} by {severity}",
159
+ "aggregator": "sum"
160
+ }
161
+ ],
162
+ "response_format": "scalar"
163
+ }
164
+ ]
165
+ },
166
+ "layout": { "x": 4, "y": 0, "width": 4, "height": 3 }
167
+ },
168
+ {
169
+ "definition": {
170
+ "title": "Analysis Duration (p50 / p95 / p99)",
171
+ "type": "timeseries",
172
+ "requests": [
173
+ {
174
+ "formulas": [
175
+ { "formula": "query1", "alias": "p50" },
176
+ { "formula": "query2", "alias": "p95" },
177
+ { "formula": "query3", "alias": "p99" }
178
+ ],
179
+ "queries": [
180
+ {
181
+ "data_source": "metrics",
182
+ "name": "query1",
183
+ "query": "p50:aip.analysis.duration_ms{*}",
184
+ "aggregator": "avg"
185
+ },
186
+ {
187
+ "data_source": "metrics",
188
+ "name": "query2",
189
+ "query": "p95:aip.analysis.duration_ms{*}",
190
+ "aggregator": "avg"
191
+ },
192
+ {
193
+ "data_source": "metrics",
194
+ "name": "query3",
195
+ "query": "p99:aip.analysis.duration_ms{*}",
196
+ "aggregator": "avg"
197
+ }
198
+ ],
199
+ "response_format": "timeseries",
200
+ "display_type": "line"
201
+ }
202
+ ],
203
+ "yaxis": { "label": "ms", "include_zero": true }
204
+ },
205
+ "layout": { "x": 8, "y": 0, "width": 4, "height": 3 }
206
+ }
207
+ ]
208
+ },
209
+ "layout": { "x": 0, "y": 4, "width": 12, "height": 4 }
210
+ },
211
+ {
212
+ "definition": {
213
+ "title": "AAP Verification",
214
+ "type": "group",
215
+ "layout_type": "ordered",
216
+ "widgets": [
217
+ {
218
+ "definition": {
219
+ "title": "Verification Pass Rate",
220
+ "type": "query_value",
221
+ "requests": [
222
+ {
223
+ "formulas": [
224
+ {
225
+ "formula": "query1 / (query1 + query2)",
226
+ "limit": { "count": 1, "order": "desc" }
227
+ }
228
+ ],
229
+ "queries": [
230
+ {
231
+ "data_source": "metrics",
232
+ "name": "query1",
233
+ "query": "sum:aap.verifications.total{verified:true}",
234
+ "aggregator": "sum"
235
+ },
236
+ {
237
+ "data_source": "metrics",
238
+ "name": "query2",
239
+ "query": "sum:aap.verifications.total{verified:false}",
240
+ "aggregator": "sum"
241
+ }
242
+ ],
243
+ "response_format": "scalar"
244
+ }
245
+ ],
246
+ "autoscale": false,
247
+ "precision": 2,
248
+ "custom_unit": "%",
249
+ "conditional_formats": [
250
+ { "comparator": ">=", "value": 0.98, "palette": "white_on_green" },
251
+ { "comparator": ">=", "value": 0.9, "palette": "white_on_yellow" },
252
+ { "comparator": "<", "value": 0.9, "palette": "white_on_red" }
253
+ ]
254
+ },
255
+ "layout": { "x": 0, "y": 0, "width": 4, "height": 3 }
256
+ },
257
+ {
258
+ "definition": {
259
+ "title": "Violations by Type",
260
+ "type": "toplist",
261
+ "requests": [
262
+ {
263
+ "formulas": [
264
+ {
265
+ "formula": "query1",
266
+ "limit": { "count": 10, "order": "desc" }
267
+ }
268
+ ],
269
+ "queries": [
270
+ {
271
+ "data_source": "metrics",
272
+ "name": "query1",
273
+ "query": "sum:aap.violations.total{*} by {type}",
274
+ "aggregator": "sum"
275
+ }
276
+ ],
277
+ "response_format": "scalar"
278
+ }
279
+ ]
280
+ },
281
+ "layout": { "x": 4, "y": 0, "width": 4, "height": 3 }
282
+ },
283
+ {
284
+ "definition": {
285
+ "title": "Violations Over Time by Severity",
286
+ "type": "timeseries",
287
+ "requests": [
288
+ {
289
+ "formulas": [{ "formula": "query1" }],
290
+ "queries": [
291
+ {
292
+ "data_source": "metrics",
293
+ "name": "query1",
294
+ "query": "sum:aap.violations.total{*} by {severity}.as_count()",
295
+ "aggregator": "sum"
296
+ }
297
+ ],
298
+ "response_format": "timeseries",
299
+ "display_type": "bars"
300
+ }
301
+ ],
302
+ "yaxis": { "label": "violations", "include_zero": true }
303
+ },
304
+ "layout": { "x": 8, "y": 0, "width": 4, "height": 3 }
305
+ }
306
+ ]
307
+ },
308
+ "layout": { "x": 0, "y": 8, "width": 12, "height": 4 }
309
+ }
310
+ ]
311
+ }