analytics_ops 0.2.0 → 0.3.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -0
  3. data/CONTRIBUTING.md +1 -0
  4. data/README.md +141 -13
  5. data/SECURITY.md +11 -2
  6. data/docs/ai-connections.md +192 -0
  7. data/docs/api-support-matrix.md +25 -8
  8. data/docs/architecture.md +78 -17
  9. data/docs/authentication.md +53 -3
  10. data/docs/bigquery.md +128 -0
  11. data/docs/commands.md +133 -12
  12. data/docs/configuration-schema-v1.json +608 -50
  13. data/docs/configuration.md +118 -5
  14. data/docs/google-client-compatibility.md +21 -1
  15. data/docs/governance.md +93 -0
  16. data/docs/health.md +82 -0
  17. data/docs/live-smoke-test.md +13 -10
  18. data/docs/rails.md +25 -9
  19. data/docs/reports.md +165 -3
  20. data/docs/safety.md +37 -5
  21. data/docs/troubleshooting.md +81 -7
  22. data/lib/analytics_ops/bigquery/definition.rb +173 -0
  23. data/lib/analytics_ops/bigquery.rb +4 -0
  24. data/lib/analytics_ops/cli/local_commands.rb +80 -0
  25. data/lib/analytics_ops/cli/options.rb +282 -9
  26. data/lib/analytics_ops/cli/presenter.rb +263 -28
  27. data/lib/analytics_ops/cli/presenter_csv.rb +92 -0
  28. data/lib/analytics_ops/cli/reporting_commands.rb +129 -0
  29. data/lib/analytics_ops/cli/setup_commands.rb +91 -0
  30. data/lib/analytics_ops/cli.rb +121 -44
  31. data/lib/analytics_ops/clients/admin.rb +28 -2
  32. data/lib/analytics_ops/clients/admin_governance.rb +95 -0
  33. data/lib/analytics_ops/clients/admin_governance_normalization.rb +90 -0
  34. data/lib/analytics_ops/clients/admin_resource_normalization.rb +61 -0
  35. data/lib/analytics_ops/clients/bigquery.rb +313 -0
  36. data/lib/analytics_ops/clients/data.rb +147 -54
  37. data/lib/analytics_ops/clients/data_result_normalization.rb +55 -0
  38. data/lib/analytics_ops/clients/error_translation.rb +13 -1
  39. data/lib/analytics_ops/configuration/schema.rb +175 -1
  40. data/lib/analytics_ops/configuration/validator.rb +45 -7
  41. data/lib/analytics_ops/configuration/validator_experimental.rb +136 -0
  42. data/lib/analytics_ops/configuration/validator_reporting.rb +93 -0
  43. data/lib/analytics_ops/configuration/writer.rb +112 -7
  44. data/lib/analytics_ops/desired_state.rb +28 -3
  45. data/lib/analytics_ops/errors.rb +33 -1
  46. data/lib/analytics_ops/funnels.rb +58 -0
  47. data/lib/analytics_ops/governance.rb +363 -0
  48. data/lib/analytics_ops/health.rb +310 -0
  49. data/lib/analytics_ops/mcp_server/custom_report_tool.rb +101 -0
  50. data/lib/analytics_ops/mcp_server/discovery_tools.rb +79 -0
  51. data/lib/analytics_ops/mcp_server/experimental_tools.rb +89 -0
  52. data/lib/analytics_ops/mcp_server/health_tools.rb +80 -0
  53. data/lib/analytics_ops/mcp_server/overview_tools.rb +49 -0
  54. data/lib/analytics_ops/mcp_server/standard_report_tool.rb +53 -0
  55. data/lib/analytics_ops/mcp_server.rb +402 -0
  56. data/lib/analytics_ops/portfolio.rb +216 -0
  57. data/lib/analytics_ops/rails/railtie.rb +19 -8
  58. data/lib/analytics_ops/redaction.rb +10 -6
  59. data/lib/analytics_ops/reports/csv_export.rb +91 -0
  60. data/lib/analytics_ops/reports/definition.rb +152 -14
  61. data/lib/analytics_ops/reports/metadata.rb +158 -0
  62. data/lib/analytics_ops/reports/period.rb +104 -0
  63. data/lib/analytics_ops/reports/result.rb +8 -3
  64. data/lib/analytics_ops/reports.rb +3 -0
  65. data/lib/analytics_ops/service_account.rb +321 -29
  66. data/lib/analytics_ops/setup.rb +31 -7
  67. data/lib/analytics_ops/version.rb +1 -1
  68. data/lib/analytics_ops/workspace.rb +222 -10
  69. data/lib/analytics_ops.rb +7 -2
  70. data/lib/generators/analytics_ops/templates/analytics_ops.yml +1 -1
  71. data/sig/analytics_ops.rbs +494 -9
  72. metadata +60 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f71655d305efa516ee61d1d1059078f6e0bd562949a542e764dceff69eb58017
4
- data.tar.gz: d73a4e6cb8aa1de4ec019104bcc28ab259be236dd41612f73be4124a0f4bae56
3
+ metadata.gz: a081c51447259f1fd4755d6758cadc76878668a5851e96408e286c64885fdcb7
4
+ data.tar.gz: 6e5724c104b4946735b9967d1de1a10e70892828901d7eb7dd0190023af2ee92
5
5
  SHA512:
6
- metadata.gz: ff58c5f35e8573755d4dab309163602124fda905aa2e452568e04ceef4d12d8b370e2d573b7783ab150b6083c2657dbf9cf03e6c4124ca8931cfe4ef01966679
7
- data.tar.gz: 5495494e5cae8ba1a786644752038a5749cf4aa2cdb05cbfc12da0923c8558596be1391b3f1806cf7739ea6ad13ea57a01150ff0e69e2f4e57a86232ab14df86
6
+ metadata.gz: 8d422c66448843f84a387fdd39526d7fe9e6545f16760fcedaa840250a8356e3f6612be151cd115ee0120b1074b406d39d115546ce460571039c8653b2cdd59d
7
+ data.tar.gz: 0c42714dbfc96aa557344f8930d55e4a1087bce24eb2f3d22db3d904fc83ce8c3803ad122daae2a6a45ffeb8aa5e7d6b304c4e3e3c882192ebe77e6851671339
data/CHANGELOG.md CHANGED
@@ -3,6 +3,114 @@
3
3
  Analytics Ops follows Semantic Versioning for its public Ruby, CLI,
4
4
  configuration, and plan contracts.
5
5
 
6
+ ## [Unreleased]
7
+
8
+ ## [0.3.0] - 2026-07-23
9
+
10
+ ### Added
11
+
12
+ - Named service-account connections with per-configuration profile mappings,
13
+ `connections`, `profiles`, and `use` commands, plus automatic migration from
14
+ the version-1 single-key pointer.
15
+ - Collision-safe automatic connection names when separate applications both
16
+ use the default `production` profile with different credentials.
17
+ - Safe additive setup for a second property profile without hand-editing YAML.
18
+ - `--last`, `--from`, `--to`, and `--compare` for standard reports and
19
+ overviews, with bounded equal preceding-period comparisons.
20
+ - A read-only `portfolio` summary across every configured property and its
21
+ associated Google connection.
22
+ - A local stdio `mcp` server for ChatGPT desktop, Codex, and Claude Code. Its
23
+ strict tool allowlist covers the read-only capabilities described below and
24
+ contains no mutation operation.
25
+ - Service-account file permission and Git-repository placement warnings in
26
+ setup and doctor.
27
+ - A beginner-friendly AI connection guide with the exact local commands,
28
+ privacy boundary, and optional OpenAI Secure MCP Tunnel explanation.
29
+ - Property-aware report field discovery and compatibility checks, saved YAML
30
+ recipes, bounded one-off custom reports, recursive boolean/list/null/numeric
31
+ filters, and atomic streaming CSV exports up to an explicit hard limit.
32
+ - Deterministic `health` checks for traffic and key-event drops, expected
33
+ events, acquisition quality, Google thresholding/sampling, quota, and
34
+ configuration drift.
35
+ - Generalized portfolio reports and health across all profiles with bounded
36
+ concurrency, profile labels, stable ordering, and isolated per-property
37
+ failures.
38
+ - Experimental read-only governance snapshots and policy checks for Enhanced
39
+ Measurement, stream redaction, reporting identity, attribution, channel
40
+ groups, calculated metrics, event rules, and BigQuery links.
41
+ - Experimental bounded, de-identified change history and portable aggregate
42
+ Data API quota-access reporting.
43
+ - Optional fixed GA4 BigQuery Export recipes and aggregate ordered funnels
44
+ using the official client, a mandatory dry run and byte cap, parameterized
45
+ SQL, and bounded normalized results.
46
+ - Expanded the local MCP allowlist to 21 strictly read-only tools, including
47
+ field discovery, compatible custom reports, health, generalized portfolio,
48
+ governance, and optional cost-capped BigQuery/funnel reads.
49
+ - New Rails tasks and dedicated health, governance, and BigQuery/funnel
50
+ operator guides.
51
+
52
+ ### Changed
53
+
54
+ - Verify future RubyGems releases from the published artifact instead of
55
+ waiting for the legacy full index, which can lag behind a successful
56
+ Trusted Publishing upload.
57
+ - Rails generator and CLI setup now share the `production` default. Setup can
58
+ fill the untouched generated placeholder, and Rails tasks use the locally
59
+ selected profile unless `ANALYTICS_OPS_PROFILE` explicitly overrides it.
60
+ - The mode-`0600` user connection file now stores multiple named key paths and
61
+ profile selections while still storing no key material.
62
+ - Google transport errors preserve bounded structured reason, metadata, and
63
+ status values so disabled-API guidance does not depend on English wording.
64
+ - Saved and direct custom reports validate live property metadata and Google's
65
+ compatibility result before running.
66
+ - Portfolio human and CSV output now includes property identity and optional
67
+ local labels.
68
+
69
+ ### Fixed
70
+
71
+ - Declare CSV as an explicit runtime dependency for Ruby 3.4 and newer, where
72
+ it is no longer bundled as a default gem.
73
+ - Show complete redacted before/after values during interactive apply instead
74
+ of truncating the exact operation being approved.
75
+ - Require `--non-interactive --yes` for JSON apply so prompts can never corrupt
76
+ machine-readable standard output.
77
+ - Reject duplicate stream IDs during configuration loading instead of failing
78
+ later during planning.
79
+ - Preserve both the beginning and ending of long human report cells so similar
80
+ URLs remain visibly distinct.
81
+ - Validate saved connection mappings against existing connection names.
82
+ - Use each web stream—not the property root—for event create/edit rule reads,
83
+ and supply the required parent account when requesting change history.
84
+ - Use non-360 aggregate Data Access fields so the default access query works
85
+ on ordinary GA4 properties.
86
+ - Preserve a report definition's starting offset during paginated export.
87
+ - Avoid false health findings when a configured percentage threshold is zero.
88
+ - Reject malformed public BigQuery results and irrelevant null-filter options.
89
+ - Validate configured realtime fields without sending them through the
90
+ standard-only compatibility endpoint.
91
+ - Reject date flags on compatibility checks instead of accepting options the
92
+ Google compatibility request cannot use.
93
+ - Refuse partial portfolio CSV output and reject BigQuery results whose actual
94
+ processed bytes exceed the configured cap.
95
+ - Reject oversized Data API cells and responses that exceed the requested row
96
+ limit so malformed remote output cannot bypass a bounded report or export.
97
+ - Reject compatibility responses that omit, duplicate, or substitute requested
98
+ fields instead of treating an incomplete Google response as compatible.
99
+
100
+ ### Security
101
+
102
+ - Every MCP tool is explicitly annotated read-only and non-destructive; the
103
+ server contains no plan, apply, create, update, delete, archive, credential,
104
+ or key-path tool.
105
+ - Starting MCP, listing local profiles, and switching profiles perform no
106
+ Google network access or credential loading.
107
+ - MCP input schemas are strict and bounded, tool errors are redacted, and
108
+ unexpected exceptions do not expose internal details.
109
+ - Change-history output omits actor email and full before/after resources.
110
+ - Optional BigQuery support has no arbitrary-SQL entry point, uses only the
111
+ read-only OAuth scope, never returns funnel user identifiers, and applies
112
+ the configured billing cap to both dry-run and real query jobs.
113
+
6
114
  ## [0.2.0] - 2026-07-22
7
115
 
8
116
  ### Added
data/CONTRIBUTING.md CHANGED
@@ -32,6 +32,7 @@ mounted read-only except for source edits.
32
32
  - Do not introduce telemetry, a database, browser analytics, or boot-time
33
33
  network access.
34
34
  - Preserve the read-only default and saved-plan confirmation boundary.
35
+ - Keep MCP tools read-only; AI-driven mutation is outside the server boundary.
35
36
  - Do not add ordinary delete/archive operations.
36
37
  - Use immutable gem-owned public values and typed errors.
37
38
 
data/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Analytics Ops
2
2
 
3
+ [![Gem Version](https://img.shields.io/gem/v/analytics_ops.svg)](https://rubygems.org/gems/analytics_ops)
4
+ [![CI](https://github.com/nelsonchaves/analytics_ops/actions/workflows/ci.yml/badge.svg)](https://github.com/nelsonchaves/analytics_ops/actions/workflows/ci.yml)
5
+ [![Documentation](https://img.shields.io/badge/docs-rubydoc.info-blue.svg)](https://rubydoc.info/gems/analytics_ops)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE.txt)
7
+
3
8
  **Google Analytics 4 configuration as code and reporting for Ruby and Rails.**
4
9
 
5
10
  Analytics Ops gives you a review-first way to inspect GA4, detect drift, run
@@ -19,7 +24,7 @@ analytics-ops overview
19
24
 
20
25
  `setup` lists the properties the service account can access, lets you choose
21
26
  one, and creates the smallest valid `config/analytics_ops.yml` for the default
22
- `production` profile. It securely remembers the key's path, so every later
27
+ `production` profile. It securely remembers the connection, so every later
23
28
  command works without another authentication option. You do not need
24
29
  `gcloud`, browser authorization, a property ID, or hand-written YAML.
25
30
 
@@ -27,6 +32,20 @@ command works without another authentication option. You do not need
27
32
  traffic acquisition, landing pages, and devices for the previous 28 complete
28
33
  days. Both commands are read-only in Google Analytics.
29
34
 
35
+ After setup, everyday use stays short:
36
+
37
+ ```bash
38
+ analytics-ops report traffic --last 7
39
+ analytics-ops overview --compare
40
+ analytics-ops portfolio
41
+ analytics-ops health
42
+ ```
43
+
44
+ `--last`, `--from`, `--to`, and `--compare` make date changes easy.
45
+ `portfolio` shows users, sessions, and key events across every configured
46
+ property. `health` checks traffic, expected events, attribution quality,
47
+ quota, and configuration drift without changing GA4.
48
+
30
49
  ## Google Analytics setup (first time only)
31
50
 
32
51
  The setup is easier when you keep these four separate things in mind:
@@ -148,7 +167,7 @@ For an application that uses Bundler, add:
148
167
 
149
168
  ```ruby
150
169
  # Gemfile
151
- gem "analytics_ops", "~> 0.2", group: :development
170
+ gem "analytics_ops", "~> 0.3", group: :development
152
171
  ```
153
172
 
154
173
  Then run:
@@ -166,7 +185,7 @@ Setup:
166
185
  - lists the GA4 properties the service account can access
167
186
  - lets you choose a property
168
187
  - creates `config/analytics_ops.yml`
169
- - remembers only the key's absolute path in
188
+ - remembers named connections and profile choices, using only key paths, in
170
189
  `~/.config/analytics_ops/connection.json`
171
190
 
172
191
  ### H. Verify the connection with read-only commands
@@ -174,7 +193,9 @@ Setup:
174
193
  ```bash
175
194
  bundle exec analytics-ops doctor
176
195
  bundle exec analytics-ops properties
196
+ bundle exec analytics-ops profiles
177
197
  bundle exec analytics-ops overview
198
+ bundle exec analytics-ops portfolio
178
199
  bundle exec analytics-ops report traffic --json
179
200
  bundle exec analytics-ops realtime
180
201
  bundle exec analytics-ops audit
@@ -203,7 +224,7 @@ reviewed a saved plan.
203
224
  - **Permission denied:** Grant Viewer or Editor inside GA4. A Google Cloud IAM
204
225
  role does not grant access to GA4 data.
205
226
  - **“This app is blocked” opens in a browser:** That is an obsolete OAuth
206
- flow. Analytics Ops 0.2 uses a service-account JSON key and does not launch
227
+ flow. Analytics Ops uses a service-account JSON key and does not launch
207
228
  browser authorization.
208
229
  - **Key unavailable:** Run setup again with the key's new absolute path.
209
230
  - **Key creation disabled:** An organization policy may prohibit downloaded
@@ -227,6 +248,75 @@ analytics-ops properties
227
248
  analytics-ops discover # includes data streams
228
249
  ```
229
250
 
251
+ ## More than one property or client
252
+
253
+ Setup can safely add another profile to the same configuration:
254
+
255
+ ```bash
256
+ analytics-ops setup \
257
+ --profile client_b \
258
+ --connection client_b \
259
+ --service-account /secure/client-b/service-account.json
260
+
261
+ analytics-ops profiles
262
+ analytics-ops connections
263
+ analytics-ops use client_b
264
+ analytics-ops overview
265
+ ```
266
+
267
+ The selected profile and its named Google connection are remembered for this
268
+ configuration. Nothing secret enters the YAML. Use `analytics-ops portfolio`
269
+ for one read-only totals table across all configured properties, or:
270
+
271
+ ```bash
272
+ analytics-ops portfolio report traffic
273
+ analytics-ops portfolio health
274
+ ```
275
+
276
+ Those commands isolate per-property failures and use bounded concurrency.
277
+ Optional profile labels make client and brand results easier to identify.
278
+
279
+ ## Custom reports without Ruby
280
+
281
+ Discover valid property-specific fields, then run a report:
282
+
283
+ ```bash
284
+ analytics-ops reports
285
+ analytics-ops fields event
286
+ analytics-ops report custom \
287
+ --dimension eventName \
288
+ --metric eventCount \
289
+ --where eventName=purchase \
290
+ --last 7
291
+ ```
292
+
293
+ Saved report recipes can include nested filters and ordering in
294
+ `config/analytics_ops.yml`. Large exports use explicit bounded pagination:
295
+
296
+ ```bash
297
+ analytics-ops report traffic \
298
+ --all \
299
+ --max-rows 100000 \
300
+ --output tmp/traffic.csv
301
+ ```
302
+
303
+ See [Reports](docs/reports.md) and [Analytics health](docs/health.md).
304
+
305
+ ## Ask ChatGPT, Codex, or Claude
306
+
307
+ Analytics Ops includes a local MCP server with read-only tools only:
308
+
309
+ ```bash
310
+ analytics-ops mcp --config /absolute/path/to/config/analytics_ops.yml
311
+ ```
312
+
313
+ It can list properties and report fields, audit configuration and governance,
314
+ check health, and read overviews, portfolios, saved or custom reports,
315
+ realtime events, and explicitly configured BigQuery/funnel results. It has no
316
+ `plan`, `apply`, create, update, or delete tool. See
317
+ [ChatGPT, Codex, and Claude](docs/ai-connections.md) for the copy-and-paste
318
+ connection commands and the data-sharing boundary.
319
+
230
320
  ## The safe change workflow
231
321
 
232
322
  ```text
@@ -257,21 +347,37 @@ what failed, and what remains.
257
347
  | Command | Purpose | Remote writes? |
258
348
  | --- | --- | --- |
259
349
  | `analytics-ops setup --service-account PATH` | Connect, choose a property, and create minimal configuration | No |
350
+ | `analytics-ops connections` | List saved connection names without exposing key paths | No |
351
+ | `analytics-ops profiles` | List configured properties and their connections | No |
352
+ | `analytics-ops use NAME` | Select a profile and connection locally | No |
260
353
  | `analytics-ops properties` | List accessible accounts and properties without configuration | No |
261
354
  | `analytics-ops doctor` | Check configuration, credentials, APIs, property access, clients, and clock | No |
262
355
  | `analytics-ops discover` | List accessible accounts, properties, and streams without configuration | No |
263
356
  | `analytics-ops overview` | Show five useful reports in one bounded batch | No |
357
+ | `analytics-ops portfolio` | Compare totals across every configured property | No |
358
+ | `analytics-ops portfolio report NAME` | Run one report across configured properties | No |
359
+ | `analytics-ops portfolio health` | Check every configured property with isolated failures | No |
360
+ | `analytics-ops health` | Check traffic, events, attribution quality, quota, and drift | No |
264
361
  | `analytics-ops snapshot` | Print normalized managed remote state | No |
265
362
  | `analytics-ops audit` | Show drift without writing a plan file | No |
266
363
  | `analytics-ops plan --output FILE` | Review and save deterministic changes | No |
267
364
  | `analytics-ops apply FILE` | Apply one reviewed, non-stale plan | **Yes** |
268
365
  | `analytics-ops verify` | Check convergence | No |
269
366
  | `analytics-ops report NAME` | Run a built-in standard report | No |
367
+ | `analytics-ops reports` | List built-in and saved report recipes | No |
368
+ | `analytics-ops fields SEARCH` | Find property-aware dimensions and metrics | No |
369
+ | `analytics-ops compatibility NAME` | Check whether a report's fields work together | No |
270
370
  | `analytics-ops realtime` | Run the realtime-events report | No |
371
+ | `analytics-ops governance` | Audit broader Alpha GA4 settings | No |
372
+ | `analytics-ops changes` | Read de-identified recent configuration changes | No |
373
+ | `analytics-ops access` | Read aggregate Data API access-quota usage | No |
374
+ | `analytics-ops raw NAME` | Run an optional fixed, cost-capped BigQuery recipe | No |
375
+ | `analytics-ops funnel NAME` | Run an optional aggregate ordered funnel | No |
376
+ | `analytics-ops mcp` | Start the strictly read-only local AI connection | No |
271
377
  | `analytics-ops schema --format json` | Print the configuration schema | No |
272
378
 
273
- Use `--json` or `--format json` for automation. CSV is available only for
274
- individual reports:
379
+ Use `--json` or `--format json` for automation. CSV is available for report
380
+ results, portfolio reports, and optional BigQuery/funnel results:
275
381
 
276
382
  ```bash
277
383
  bundle exec analytics-ops report landing-pages --csv
@@ -280,6 +386,14 @@ bundle exec analytics-ops report landing-pages --csv
280
386
  Friendly `traffic` and `landing-pages` aliases leave the original
281
387
  `traffic_acquisition` and `landing_pages` names fully supported.
282
388
 
389
+ Choose report dates without editing Ruby:
390
+
391
+ ```bash
392
+ analytics-ops report traffic --last 7
393
+ analytics-ops report traffic --from 2026-07-01 --to 2026-07-07
394
+ analytics-ops overview --last 30 --compare
395
+ ```
396
+
283
397
  See [Commands](docs/commands.md) for every option and exit status.
284
398
 
285
399
  ## IDs: the quick distinction
@@ -309,8 +423,14 @@ plan.write("tmp/ga4-plan.json")
309
423
  report = workspace.report("calculator_completions")
310
424
  report.rows.each { |row| puts row.fetch("eventCount") }
311
425
 
426
+ ranges = AnalyticsOps::Reports::Period.resolve(last_days: 7, compare: true)
427
+ comparison = workspace.report("traffic", date_ranges: ranges)
428
+
312
429
  overview = workspace.overview
313
430
  puts overview.report("overview_totals").rows
431
+
432
+ health = workspace.health
433
+ puts health.findings.map(&:message)
314
434
  ```
315
435
 
316
436
  Loading the gem, loading YAML, and booting Rails do not contact Google.
@@ -324,12 +444,14 @@ gem "analytics_ops", require: "analytics_ops/rails", group: :development
324
444
  ```
325
445
 
326
446
  ```bash
447
+ bin/rails generate analytics_ops:install
327
448
  bundle exec analytics-ops setup \
328
- --profile development \
329
449
  --service-account /absolute/path/to/service-account.json
330
- bin/rails generate analytics_ops:install
331
450
  bin/rake analytics:doctor
332
451
  bin/rake analytics:overview
452
+ bin/rake analytics:portfolio
453
+ bin/rake analytics:health
454
+ bin/rake analytics:governance
333
455
  bin/rake 'analytics:report[traffic_acquisition]'
334
456
  ```
335
457
 
@@ -340,12 +462,14 @@ boot-time network calls. See [Rails integration](docs/rails.md).
340
462
  ## What Analytics Ops does not do
341
463
 
342
464
  - It does not inject browser analytics or manage consent banners.
343
- - It stores only the service-account key's path in a mode-`0600` user file;
344
- it never copies the key, tokens, or report rows.
465
+ - It stores only named service-account key paths in a mode-`0600` user file;
466
+ it never copies keys, tokens, or report rows.
345
467
  - It does not delete accounts, properties, streams, or unmanaged resources.
346
468
  - It does not claim to manage settings that Google exposes only in the UI.
347
- - Experimental declarations are findings only in version 0.2.0; they are not
348
- silently applied through Alpha APIs.
469
+ - Experimental governance features are read-only and never enter ordinary
470
+ plans or apply.
471
+ - Optional BigQuery support runs only fixed `SELECT` recipes behind a required
472
+ dry run and byte cap; it does not accept arbitrary SQL.
349
473
 
350
474
  ## Documentation
351
475
 
@@ -355,7 +479,11 @@ boot-time network calls. See [Rails integration](docs/rails.md).
355
479
  | [Live smoke test](docs/live-smoke-test.md) | Real-app read-only verification and release gate |
356
480
  | [Configuration](docs/configuration.md) | Complete strict YAML contract |
357
481
  | [Commands](docs/commands.md) | CLI syntax, formats, and exit statuses |
358
- | [Reports](docs/reports.md) | Built-in recipes and GA reporting limitations |
482
+ | [Reports](docs/reports.md) | Built-in, saved, custom, and exported reports |
483
+ | [Analytics health](docs/health.md) | Deterministic traffic and data-quality checks |
484
+ | [Governance](docs/governance.md) | Experimental read-only Admin checks |
485
+ | [BigQuery and funnels](docs/bigquery.md) | Optional cost-capped raw-data recipes |
486
+ | [ChatGPT, Codex, and Claude](docs/ai-connections.md) | Strictly read-only local AI connection |
359
487
  | [Rails](docs/rails.md) | Generator and Rake tasks |
360
488
  | [Safety](docs/safety.md) | Plans, stale-state protection, rollback, and credentials |
361
489
  | [Plan format](docs/plan-format.md) | Version-1 JSON contract |
data/SECURITY.md CHANGED
@@ -40,12 +40,21 @@ Analytics Ops:
40
40
  - rejects stale and cross-property plans
41
41
  - excludes delete/archive operations from ordinary plans
42
42
  - never logs report results automatically
43
+ - exposes only read-only, non-destructive tools through MCP and has no AI
44
+ mutation tool
45
+ - validates custom report fields against live property metadata and
46
+ compatibility before execution
47
+ - omits actor email and full resource bodies from change-history output
48
+ - allows only fixed optional BigQuery SELECT recipes, with a dry run,
49
+ mandatory byte cap, bounded rows, and no arbitrary SQL input
43
50
  - has no telemetry, private-key/token store, database, or browser injection
44
51
  - publishes through RubyGems Trusted Publishing without a stored API key
45
52
 
46
53
  Plan files still describe operational configuration and are created with mode
47
- 0600. Report output may contain sensitive aggregate data and should be handled
48
- under the operator's data policy.
54
+ 0600. Report, governance, BigQuery, and funnel output may contain sensitive
55
+ aggregate data and should be handled under the operator's data policy. MCP
56
+ tool results are sent to the connected AI provider; credentials and saved key
57
+ paths are not.
49
58
 
50
59
  ## Operator responsibilities
51
60
 
@@ -0,0 +1,192 @@
1
+ # ChatGPT, Codex, and Claude
2
+
3
+ Analytics Ops can let an AI read your Google Analytics data through the Model
4
+ Context Protocol (MCP). The connection is deliberately read-only.
5
+
6
+ The simple idea is:
7
+
8
+ ```text
9
+ ChatGPT, Codex, or Claude
10
+ → local Analytics Ops MCP process
11
+ → Google Analytics read-only APIs
12
+ ```
13
+
14
+ The AI can inspect and explain data. It cannot change GA4 through this
15
+ connection.
16
+
17
+ ## Before connecting
18
+
19
+ Finish normal Analytics Ops setup first:
20
+
21
+ ```bash
22
+ analytics-ops doctor
23
+ analytics-ops overview
24
+ ```
25
+
26
+ Both should work. Then identify:
27
+
28
+ - the absolute Analytics Ops config path
29
+ - the command that starts Analytics Ops
30
+
31
+ For a Rails or Bundler application, the generated binstub is easiest:
32
+
33
+ ```text
34
+ /Users/example/sites/my_app/bin/analytics-ops
35
+ ```
36
+
37
+ All paths below are fake. Replace them with your own absolute paths.
38
+
39
+ ## ChatGPT desktop and Codex
40
+
41
+ The ChatGPT desktop app, Codex CLI, and Codex IDE extension share local MCP
42
+ configuration on the same Codex host.
43
+
44
+ The fastest setup is:
45
+
46
+ ```bash
47
+ codex mcp add analytics-ops -- \
48
+ /Users/example/sites/my_app/bin/analytics-ops \
49
+ mcp \
50
+ --config /Users/example/sites/my_app/config/analytics_ops.yml
51
+ ```
52
+
53
+ Then restart ChatGPT desktop or Codex. In ChatGPT desktop, you can also open
54
+ **Settings → MCP servers → Add server**, choose **STDIO**, and enter the same
55
+ command and arguments.
56
+
57
+ Check the saved server with:
58
+
59
+ ```bash
60
+ codex mcp list
61
+ ```
62
+
63
+ ## Claude Code
64
+
65
+ Add the same local stdio server:
66
+
67
+ ```bash
68
+ claude mcp add --transport stdio --scope user analytics-ops -- \
69
+ /Users/example/sites/my_app/bin/analytics-ops \
70
+ mcp \
71
+ --config /Users/example/sites/my_app/config/analytics_ops.yml
72
+ ```
73
+
74
+ Restart Claude Code, then use `/mcp` to check the connection.
75
+
76
+ ## What you can ask
77
+
78
+ Examples:
79
+
80
+ - “Show my Analytics overview for the last 7 complete days.”
81
+ - “Compare traffic with the preceding 30 days.”
82
+ - “Which landing pages have the most sessions?”
83
+ - “Show totals across all configured properties.”
84
+ - “Check every property for a large traffic drop.”
85
+ - “Find report fields related to revenue.”
86
+ - “Run an event-count report filtered to purchases.”
87
+ - “Check whether my required data-redaction settings are enabled.”
88
+ - “Audit my GA4 configuration and explain any drift.”
89
+ - “Are events arriving in realtime?”
90
+
91
+ The AI can call these tools:
92
+
93
+ - list local profiles and connection names
94
+ - list accessible GA4 properties
95
+ - run doctor
96
+ - read a normalized configuration snapshot
97
+ - audit configuration drift
98
+ - list built-in and configured report recipes
99
+ - search property-aware dimensions and metrics
100
+ - check report-field compatibility
101
+ - run bounded built-in, configured, or one-off custom reports
102
+ - run overview, generalized portfolio reports, and portfolio health
103
+ - run deterministic traffic and data-quality health checks
104
+ - audit experimental governance settings
105
+ - read an aggregate Data API quota-access report without user email or IP
106
+ - read realtime event counts
107
+ - run a fixed cost-capped BigQuery recipe or configured aggregate funnel when
108
+ that optional feature is present
109
+
110
+ There are 21 tools. Every one is explicitly marked read-only and
111
+ non-destructive. BigQuery tools accept only fixed recipe or funnel names; they
112
+ do not accept SQL.
113
+
114
+ ## What the AI cannot do
115
+
116
+ The MCP server has no tools for:
117
+
118
+ - `plan`
119
+ - `apply`
120
+ - create or update
121
+ - delete or archive
122
+ - reading a credential file or key path
123
+ - reading user-email change history
124
+ - arbitrary SQL
125
+
126
+ Every tool is marked read-only and non-destructive in MCP metadata. Starting
127
+ the server does not load a credential or contact Google. Google access begins
128
+ only after the AI calls a read tool.
129
+
130
+ If a recommended change looks useful, leave the AI connection and use the
131
+ normal human-reviewed CLI workflow:
132
+
133
+ ```bash
134
+ analytics-ops plan --output tmp/ga4-plan.json
135
+ less tmp/ga4-plan.json
136
+ analytics-ops apply tmp/ga4-plan.json
137
+ ```
138
+
139
+ The AI cannot perform that final command through MCP.
140
+
141
+ ## Privacy boundary
142
+
143
+ The service-account JSON key, access token, authorization header, and saved
144
+ key path stay local and are never returned by an MCP tool.
145
+
146
+ Requested tool results do go to the connected AI product. Those results can
147
+ include:
148
+
149
+ - GA4 property and account names
150
+ - property IDs
151
+ - configuration values
152
+ - traffic, event, and landing-page report rows
153
+ - optional BigQuery recipe and aggregate funnel rows
154
+
155
+ Connect only an AI account or workspace whose data policy is appropriate for
156
+ your analytics information. Do not ask the model to repeat or store sensitive
157
+ report data unnecessarily.
158
+
159
+ ## ChatGPT on the web
160
+
161
+ Local stdio is the easiest route for ChatGPT desktop, Codex, and Claude Code.
162
+ ChatGPT on the web does not read your local MCP configuration directly.
163
+
164
+ If web access is required, OpenAI's official
165
+ [Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels)
166
+ can forward an OpenAI-hosted tunnel to the same local stdio command over an
167
+ outbound-only connection. It requires a Platform tunnel ID, a runtime API key,
168
+ the `tunnel-client`, and the appropriate ChatGPT workspace permissions. This
169
+ is an optional advanced deployment; Analytics Ops does not need a public
170
+ server or inbound firewall port.
171
+
172
+ ## Troubleshooting
173
+
174
+ - **Server starts and appears to hang:** Correct. Stdio MCP waits silently for
175
+ protocol messages. The AI client starts and controls it.
176
+ - **Config not found:** Use an absolute `--config` path.
177
+ - **Gem not found:** Use the application's absolute `bin/analytics-ops`
178
+ binstub, or install the gem globally.
179
+ - **No properties:** Add the exact service-account email to the correct GA4
180
+ account or property.
181
+ - **More than one connection:** Run `analytics-ops profiles` and
182
+ `analytics-ops use PROFILE --connection NAME`.
183
+ - **Changed MCP settings:** Restart the AI client.
184
+ - **Need to inspect protocol tools:** `codex mcp list` or Claude Code `/mcp`
185
+ shows the configured local server.
186
+
187
+ Official client references:
188
+
189
+ - [OpenAI MCP documentation](https://learn.chatgpt.com/docs/extend/mcp)
190
+ - [OpenAI Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels)
191
+ - [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp)
192
+ - [Model Context Protocol transports](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports)