analytics_ops 0.1.0 → 0.2.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/CONTRIBUTING.md +2 -1
  4. data/README.md +232 -41
  5. data/SECURITY.md +8 -8
  6. data/docs/api-support-matrix.md +9 -8
  7. data/docs/architecture.md +22 -4
  8. data/docs/authentication.md +112 -51
  9. data/docs/commands.md +71 -17
  10. data/docs/configuration-schema-v1.json +53 -7
  11. data/docs/configuration.md +33 -9
  12. data/docs/google-client-compatibility.md +10 -6
  13. data/docs/live-smoke-test.md +159 -0
  14. data/docs/plan-format.md +4 -0
  15. data/docs/plan-schema-v1.json +113 -22
  16. data/docs/rails.md +12 -3
  17. data/docs/reports.md +44 -6
  18. data/docs/safety.md +18 -6
  19. data/docs/troubleshooting.md +42 -13
  20. data/lib/analytics_ops/applier.rb +2 -1
  21. data/lib/analytics_ops/cli/options.rb +157 -0
  22. data/lib/analytics_ops/cli/presenter.rb +221 -0
  23. data/lib/analytics_ops/cli.rb +172 -201
  24. data/lib/analytics_ops/clients/admin.rb +130 -129
  25. data/lib/analytics_ops/clients/admin_normalization.rb +95 -0
  26. data/lib/analytics_ops/clients/data.rb +120 -63
  27. data/lib/analytics_ops/clients/error_translation.rb +45 -0
  28. data/lib/analytics_ops/configuration/document.rb +1 -1
  29. data/lib/analytics_ops/configuration/loader.rb +38 -5
  30. data/lib/analytics_ops/configuration/schema.rb +51 -8
  31. data/lib/analytics_ops/configuration/validator.rb +43 -8
  32. data/lib/analytics_ops/configuration/writer.rb +105 -0
  33. data/lib/analytics_ops/configuration.rb +1 -0
  34. data/lib/analytics_ops/connection.rb +93 -0
  35. data/lib/analytics_ops/desired_state.rb +2 -2
  36. data/lib/analytics_ops/plan.rb +94 -33
  37. data/lib/analytics_ops/planner.rb +10 -2
  38. data/lib/analytics_ops/rails/railtie.rb +13 -18
  39. data/lib/analytics_ops/redaction.rb +14 -9
  40. data/lib/analytics_ops/reports/catalog.rb +22 -5
  41. data/lib/analytics_ops/reports/definition.rb +74 -37
  42. data/lib/analytics_ops/reports/overview.rb +55 -0
  43. data/lib/analytics_ops/reports/overview_result.rb +54 -0
  44. data/lib/analytics_ops/reports/result.rb +38 -9
  45. data/lib/analytics_ops/reports.rb +2 -0
  46. data/lib/analytics_ops/resources.rb +2 -1
  47. data/lib/analytics_ops/service_account.rb +171 -0
  48. data/lib/analytics_ops/setup.rb +171 -0
  49. data/lib/analytics_ops/snapshot.rb +20 -5
  50. data/lib/analytics_ops/version.rb +1 -1
  51. data/lib/analytics_ops/workspace.rb +41 -12
  52. data/lib/analytics_ops.rb +4 -0
  53. data/lib/generators/analytics_ops/install_generator.rb +2 -0
  54. data/lib/generators/analytics_ops/templates/analytics_ops.yml +2 -15
  55. data/sig/analytics_ops.rbs +117 -10
  56. metadata +26 -1
@@ -1,86 +1,147 @@
1
1
  # Authentication
2
2
 
3
- Analytics Ops uses [Google Application Default Credentials (ADC)](https://cloud.google.com/docs/authentication/application-default-credentials)
4
- through Google's official Ruby clients. It does not implement OAuth, store
5
- tokens, or accept credential fields in configuration.
3
+ Analytics Ops supports one authentication method: a Google service-account
4
+ JSON key.
5
+
6
+ It does not use `gcloud`, browser login, Desktop OAuth, Application Default
7
+ Credentials, `GOOGLE_APPLICATION_CREDENTIALS`, or API keys.
8
+
9
+ ## One-time setup
10
+
11
+ You need a Google Cloud project only to own the service account, enable the two
12
+ Analytics APIs, and supply API quota. It does not need to be the project that
13
+ hosts your website.
14
+
15
+ 1. In the Cloud project, enable:
16
+ - Google Analytics Admin API
17
+ - Google Analytics Data API
18
+ 2. Open [Google Cloud service accounts](https://console.cloud.google.com/iam-admin/serviceaccounts).
19
+ 3. Create a service account named **Analytics Ops Local**. Do not grant broad
20
+ Cloud IAM roles.
21
+ 4. Open the service account, choose **Keys**, create a JSON key, and save it
22
+ outside every source repository.
23
+ 5. Copy the service-account email, such as
24
+ `analytics-ops@example-project.iam.gserviceaccount.com`.
25
+ 6. In GA4, open **Admin → Account access management → Add users**.
26
+ 7. Add the service-account email:
27
+ - **Viewer** is enough for reports, discovery, snapshots, and audits.
28
+ - **Editor** is required if this identity will apply reviewed changes.
29
+ 8. Connect Analytics Ops:
6
30
 
7
- ## Local read-only use
31
+ ```bash
32
+ analytics-ops setup \
33
+ --service-account /absolute/path/to/service-account.json
34
+ ```
8
35
 
9
- Use an identity that has read access to the target GA4 property:
36
+ Setup verifies both APIs, lists the GA4 properties available to the service
37
+ account, lets you select one, and creates `config/analytics_ops.yml`.
38
+
39
+ After that, use normal commands without another credential option:
10
40
 
11
41
  ```bash
12
- gcloud auth application-default login \
13
- --scopes="https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/analytics.readonly"
42
+ analytics-ops overview
43
+ analytics-ops report traffic
44
+ analytics-ops realtime
45
+ analytics-ops audit
14
46
  ```
15
47
 
16
- Enable both APIs in the Google Cloud project used for authentication:
48
+ ## What Analytics Ops remembers
49
+
50
+ Analytics Ops writes this user-level file:
17
51
 
18
- - Google Analytics Admin API
19
- - Google Analytics Data API
52
+ ```text
53
+ ~/.config/analytics_ops/connection.json
54
+ ```
55
+
56
+ It contains only the absolute path to the service-account key. The connection
57
+ file is created atomically with mode `0600`, and its directory uses mode
58
+ `0700`. Analytics Ops does not copy the key or place its contents in project
59
+ configuration.
20
60
 
21
- Then run:
61
+ Keep the key in a stable location. Moving or deleting it requires setup again:
22
62
 
23
63
  ```bash
24
- analytics-ops doctor
64
+ analytics-ops setup \
65
+ --service-account /new/absolute/path/to/service-account.json
25
66
  ```
26
67
 
27
- `doctor` makes small read-only calls to both APIs. It confirms that Google
28
- accepts the credentials and that the property can be read. The installed
29
- clients do not expose a reliable contract for inspecting the scopes inside an
30
- already-issued token, so `doctor` reports scope inspection as `unknown`
31
- instead of guessing.
68
+ One service account can access several GA4 accounts. Add the same
69
+ service-account email at account level in each GA4 account.
70
+
71
+ ## Read and edit access
72
+
73
+ Normal commands request only Google's `analytics.readonly` scope. The guarded
74
+ `apply` command requests `analytics.edit` in addition to read access.
32
75
 
33
- ## Applying changes
76
+ Scopes do not grant GA4 access by themselves. The service account must also
77
+ have the matching Viewer or Editor role in GA4 Access Management.
34
78
 
35
- Read-only credentials cannot apply a plan. Use a separately protected identity
36
- with the `analytics.edit` scope and sufficient access to the GA4 property:
79
+ Analytics Ops still requires a saved plan, explicit confirmation, and a fresh
80
+ matching snapshot before any apply. Merely configuring an Editor identity
81
+ does not cause writes.
82
+
83
+ ## Automation
84
+
85
+ Use the same service-account setup non-interactively:
37
86
 
38
87
  ```bash
39
- gcloud auth application-default login \
40
- --scopes="https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/analytics.edit"
88
+ analytics-ops setup \
89
+ --service-account /secure/path/service-account.json \
90
+ --property 123456789 \
91
+ --non-interactive \
92
+ --json
41
93
  ```
42
94
 
43
- Keep mutation credentials out of a deployed Rails web container. Apply plans
44
- from an administrator workstation or a protected release job.
95
+ Keep the JSON key in the CI platform's protected secret-file storage. Never
96
+ put its contents in an environment variable, command argument, repository, or
97
+ build log.
45
98
 
46
- ## Automation
99
+ ## Ruby API
47
100
 
48
- Prefer one of these:
101
+ The Ruby API can use an explicit service account without changing the saved
102
+ user connection:
49
103
 
50
- - A dedicated service account with only the GA properties and roles it needs.
51
- - Workload Identity Federation, which avoids a long-lived JSON key.
52
- - An explicitly injected Google credentials object when using the Ruby API.
104
+ ```ruby
105
+ service_account = AnalyticsOps::ServiceAccount.new(
106
+ "/secure/path/service-account.json"
107
+ )
53
108
 
54
- Cloud IAM permission alone does not grant access to a GA4 property. Add the
55
- service-account identity—such as
56
- `ga-reader@example-project.iam.gserviceaccount.com`—to the property with the
57
- appropriate Google Analytics role.
109
+ workspace = AnalyticsOps::Workspace.load(
110
+ "config/analytics_ops.yml",
111
+ profile: "production",
112
+ service_account: service_account
113
+ )
114
+ ```
115
+
116
+ Loading the identity reads and validates the local key. It does not contact
117
+ Google. Network access starts only when a connection or workspace operation is
118
+ called.
58
119
 
59
120
  ## Values that are easy to confuse
60
121
 
61
122
  | Value | Example | Meaning |
62
123
  | --- | --- | --- |
63
- | Property ID | `123456789` | Numeric GA4 property identifier used by Admin and Data APIs |
64
- | Stream ID | `987654321` | Numeric data-stream resource identifier |
65
- | Measurement ID | `G-EXAMPLE1` | Web tagging identifier; not accepted as `property_id` or `stream_id` |
66
- | Account ID | `100000001` | Numeric Analytics account identifier shown by discovery |
67
- | OAuth client | Client ID and secret in your Cloud project | Starts a user OAuth flow; never put it in Analytics Ops YAML |
68
- | Service account | An IAM email identity | Receives GA property access; a JSON private key is credential material |
124
+ | GA4 account ID | `100000001` | Analytics container shown by discovery |
125
+ | GA4 property ID | `123456789` | Numeric reporting and configuration boundary |
126
+ | Stream ID | `987654321` | Numeric data-stream resource |
127
+ | Measurement ID | `G-EXAMPLE1` | Browser tagging ID; not a property or stream ID |
128
+ | Cloud project | `example-analytics-project` | Owns APIs, quota, and the service account |
129
+ | Service-account email | `analytics-ops@example-project.iam.gserviceaccount.com` | Identity added to GA4 Access Management |
130
+ | Service-account JSON | A downloaded private key file | Secret used by Analytics Ops; never commit it |
69
131
 
70
- ## Credential rules
132
+ An API key identifies a Cloud project but cannot authorize access to private
133
+ GA4 data or settings.
71
134
 
72
- - Never put credentials, paths to credentials, access tokens, refresh tokens,
73
- private keys, API keys, or OAuth secrets in `analytics_ops.yml`.
74
- - Never put credentials in a saved plan.
75
- - Never commit service-account JSON.
76
- - Never paste credentials into an issue, fixture, log, or report.
77
- - Revoke or rotate credentials immediately after suspected exposure.
78
- - Do not create a shared public OAuth client for this gem.
135
+ ## Credential rules
79
136
 
80
- Analytics Ops redacts common credential patterns from translated errors, but
81
- redaction is a final safety net—not a safe way to handle secrets.
137
+ - Never place the JSON key or its path in `analytics_ops.yml`.
138
+ - Never commit a service-account key.
139
+ - Never paste key contents into chat, issues, logs, screenshots, or reports.
140
+ - Never place production mutation credentials in a Rails web container.
141
+ - Revoke and replace a key immediately if it may have been exposed.
142
+ - Remove stale keys from the service account after rotation.
82
143
 
83
- Official Google setup references:
144
+ Official Google references:
84
145
 
85
146
  - [Admin API quickstart](https://developers.google.com/analytics/devguides/config/admin/v1/quickstart)
86
- - [Data API quickstart](https://developers.google.com/analytics/devguides/reporting/data/v1/quickstart-client-libraries)
147
+ - [Data API quickstart](https://developers.google.com/analytics/devguides/reporting/data/v1/quickstart)
data/docs/commands.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Commands
2
2
 
3
- The executable is `analytics-ops`. All commands use
3
+ The executable is `analytics-ops`. Commands that operate on one property use
4
4
  `config/analytics_ops.yml` and the `production` profile unless overridden.
5
+ `setup`, `properties`, and `discover` work before that file exists.
5
6
 
6
7
  ```text
7
8
  analytics-ops COMMAND [options]
@@ -10,22 +11,70 @@ analytics-ops COMMAND [options]
10
11
  ## Fast examples
11
12
 
12
13
  ```bash
14
+ analytics-ops setup --service-account /absolute/path/to/service-account.json
15
+ analytics-ops overview
16
+ analytics-ops properties
13
17
  analytics-ops doctor
14
- analytics-ops audit --format json
18
+ analytics-ops audit --json
15
19
  analytics-ops plan --output tmp/ga4-plan.json
16
20
  analytics-ops apply tmp/ga4-plan.json
17
- analytics-ops report landing_pages --format csv
18
- analytics-ops realtime --format json
21
+ analytics-ops report landing-pages --csv
22
+ analytics-ops realtime --json
19
23
  ```
20
24
 
21
25
  Only `apply` can write to Google.
22
26
 
23
- ## Read-only commands
27
+ ## Setup
28
+
29
+ ```bash
30
+ analytics-ops setup --service-account /absolute/path/to/service-account.json
31
+ ```
32
+
33
+ Analytics Ops supports only service-account authentication. On the first run,
34
+ pass the downloaded JSON key explicitly. Setup then:
35
+
36
+ 1. Validates that the file is a Google service-account key.
37
+ 2. Lists accessible accounts and properties without loading YAML.
38
+ 3. Prompts with numbered choices showing account, property name, and ID.
39
+ 4. Proves Admin and Data API read access.
40
+ 5. Creates the smallest valid `config/analytics_ops.yml` using the
41
+ `production` profile.
42
+ 6. Stores only the key's absolute path in
43
+ `~/.config/analytics_ops/connection.json` with mode `0600`.
44
+ 7. Prints `analytics-ops overview` as the next command.
45
+
46
+ Setup never overwrites an existing profile that targets another property. A
47
+ matching file is a successful no-op. The key is never copied into a project or
48
+ printed.
49
+
50
+ Later setup runs use the remembered path:
51
+
52
+ ```bash
53
+ analytics-ops setup
54
+ ```
55
+
56
+ Automation uses the same single route with an explicit property:
57
+
58
+ ```bash
59
+ analytics-ops setup \
60
+ --service-account /secure/path/service-account.json \
61
+ --property 123456789 \
62
+ --non-interactive \
63
+ --json
64
+ ```
65
+
66
+ The CLI never consults `gcloud`, browser login, Application Default
67
+ Credentials, `GOOGLE_APPLICATION_CREDENTIALS`, or API keys.
68
+
69
+ ## Commands that do not change GA4
24
70
 
25
71
  | Command | Result |
26
72
  | --- | --- |
73
+ | `setup` | Loads the service account, selects a property, verifies both APIs, and writes local configuration; never changes Google Analytics |
74
+ | `properties` | Lists accessible account and property summaries without configuration or per-property stream calls |
27
75
  | `doctor` | Checks the local file, credentials, Admin API, Data API, property access, client versions, edit visibility, and clock |
28
- | `discover` | Lists accessible account IDs, property IDs, and stream IDs |
76
+ | `discover` | Lists accessible account IDs, property IDs, and stream IDs without configuration |
77
+ | `overview` | Returns five bounded reports for the previous 28 complete days in one batch request |
29
78
  | `snapshot` | Prints normalized managed remote state and its fingerprint |
30
79
  | `audit` | Compares desired state with a fresh snapshot; exits 2 for drift |
31
80
  | `plan` | Generates the same comparison; `--output FILE` saves its exact JSON |
@@ -34,9 +83,8 @@ Only `apply` can write to Google.
34
83
  | `realtime [NAME]` | Runs `realtime_events` by default |
35
84
  | `schema` | Prints the version-1 configuration schema |
36
85
 
37
- `doctor` cannot reliably inspect scopes inside an issued Google token. It
38
- reports that check as unknown and proves effective access with real read-only
39
- calls instead.
86
+ `doctor` uses the read-only Analytics scope and proves effective access with
87
+ small real calls to both APIs.
40
88
 
41
89
  ## Apply
42
90
 
@@ -69,23 +117,28 @@ plans and applies in one step.
69
117
  | `-c, --config PATH` | Configuration file |
70
118
  | `-p, --profile NAME` | Profile inside the file |
71
119
  | `-f, --format FORMAT` | `human`, `json`, or report-only `csv` |
120
+ | `--json` | Shortcut for `--format json` |
121
+ | `--csv` | Shortcut for `--format csv` |
72
122
  | `-o, --output PATH` | Save generated JSON; valid only with `plan` |
123
+ | `--property ID` | Select an accessible property without prompting; setup only |
124
+ | `--service-account PATH` | Connect a Google service-account JSON key; setup only |
73
125
  | `--transport grpc|rest` | Official Google-client transport |
74
- | `--timeout SECONDS` | Positive API call timeout |
126
+ | `--timeout SECONDS` | Finite positive API call timeout |
75
127
  | `--log-level LEVEL` | Structured request metadata: `debug`, `info`, `warn`, or `error`; default `warn` |
76
128
  | `--yes` | Approve every operation in a saved plan; apply only |
77
- | `--non-interactive` | Never prompt; apply only and requires `--yes` |
129
+ | `--non-interactive` | Never prompt; apply requires `--yes`, setup requires `--property` |
78
130
 
79
- Unknown arguments and options fail instead of being ignored. CSV is rejected
80
- for anything except report results. CSV cells beginning with spreadsheet
81
- formula characters are prefixed safely.
131
+ Unknown arguments, conflicting format flags, and non-finite timeouts fail
132
+ instead of being ignored. CSV is rejected for anything except report results.
133
+ CSV cells whose first meaningful character is a spreadsheet formula marker
134
+ are prefixed safely, including cells hidden behind whitespace or controls.
82
135
 
83
136
  ## Output
84
137
 
85
- - Human output is readable in a terminal.
138
+ - Human output removes terminal control characters from remote text.
86
139
  - JSON output uses stable gem-owned fields and structured errors.
87
- - CSV contains report headers and rows only; it never applies to plans,
88
- snapshots, or errors.
140
+ - CSV contains one report's headers and rows only; it is rejected for batched
141
+ overviews, plans, snapshots, and errors.
89
142
 
90
143
  No command prints credentials or generated Google protobuf objects. Report
91
144
  rows are emitted only because the user requested a report; they are never
@@ -107,6 +160,7 @@ logged automatically.
107
160
  | 78 | Unsupported installed-client capability |
108
161
  | 79 | Stale saved plan |
109
162
  | 80 | Partial apply; inspect reconciliation output |
163
+ | 130 | Interrupted by the user; no Ruby backtrace is printed |
110
164
 
111
165
  When `--format json` is selected, errors are JSON on standard error and are
112
166
  suitable for automation.
@@ -80,13 +80,16 @@
80
80
  "required": ["event_data", "user_data", "reset_on_new_activity"],
81
81
  "properties": {
82
82
  "event_data": { "$ref": "#/$defs/retentionValue" },
83
- "user_data": { "$ref": "#/$defs/retentionValue" },
83
+ "user_data": { "$ref": "#/$defs/userRetentionValue" },
84
84
  "reset_on_new_activity": { "type": "boolean" }
85
85
  }
86
86
  },
87
87
  "retentionValue": {
88
88
  "enum": ["2_months", "14_months", "26_months", "38_months", "50_months"]
89
89
  },
90
+ "userRetentionValue": {
91
+ "enum": ["2_months", "14_months"]
92
+ },
90
93
  "googleSignals": {
91
94
  "type": "object",
92
95
  "additionalProperties": false,
@@ -102,11 +105,32 @@
102
105
  "required": ["parameter_name", "display_name", "scope"],
103
106
  "properties": {
104
107
  "parameter_name": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,39}$" },
105
- "display_name": { "type": "string", "minLength": 1, "maxLength": 82 },
106
- "description": { "type": "string", "maxLength": 150 },
108
+ "display_name": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_ ]{0,81}$" },
109
+ "description": {
110
+ "type": "string",
111
+ "maxLength": 150,
112
+ "pattern": "^[^\\u0000-\\u001F\\u007F]*$"
113
+ },
107
114
  "scope": { "enum": ["event", "user", "item"] },
108
115
  "disallow_ads_personalization": { "type": "boolean" }
109
- }
116
+ },
117
+ "allOf": [
118
+ {
119
+ "if": { "properties": { "scope": { "const": "user" } } },
120
+ "then": {
121
+ "properties": {
122
+ "parameter_name": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,23}$" }
123
+ }
124
+ }
125
+ },
126
+ {
127
+ "if": {
128
+ "required": ["disallow_ads_personalization"],
129
+ "properties": { "disallow_ads_personalization": { "const": true } }
130
+ },
131
+ "then": { "properties": { "scope": { "const": "user" } } }
132
+ }
133
+ ]
110
134
  },
111
135
  "customMetric": {
112
136
  "type": "object",
@@ -114,16 +138,38 @@
114
138
  "required": ["parameter_name", "display_name", "scope"],
115
139
  "properties": {
116
140
  "parameter_name": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,39}$" },
117
- "display_name": { "type": "string", "minLength": 1, "maxLength": 82 },
118
- "description": { "type": "string", "maxLength": 150 },
141
+ "display_name": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_ ]{0,81}$" },
142
+ "description": {
143
+ "type": "string",
144
+ "maxLength": 150,
145
+ "pattern": "^[^\\u0000-\\u001F\\u007F]*$"
146
+ },
119
147
  "scope": { "const": "event" },
120
148
  "measurement_unit": {
121
149
  "enum": [
122
150
  "standard", "currency", "feet", "meters", "kilometers", "miles",
123
151
  "milliseconds", "seconds", "minutes", "hours"
124
152
  ]
153
+ },
154
+ "restricted_metric_types": {
155
+ "type": "array",
156
+ "uniqueItems": true,
157
+ "items": { "enum": ["cost_data", "revenue_data"] }
125
158
  }
126
- }
159
+ },
160
+ "allOf": [
161
+ {
162
+ "if": {
163
+ "required": ["measurement_unit"],
164
+ "properties": { "measurement_unit": { "const": "currency" } }
165
+ },
166
+ "then": {
167
+ "required": ["restricted_metric_types"],
168
+ "properties": { "restricted_metric_types": { "minItems": 1 } }
169
+ },
170
+ "else": { "properties": { "restricted_metric_types": { "maxItems": 0 } } }
171
+ }
172
+ ]
127
173
  }
128
174
  }
129
175
  }
@@ -3,6 +3,17 @@
3
3
  Analytics Ops reads strict, versioned YAML. The file describes desired GA4
4
4
  state; it never contains credentials.
5
5
 
6
+ For the normal first run, let setup create the file after you choose a
7
+ property:
8
+
9
+ ```bash
10
+ analytics-ops setup --service-account /absolute/path/to/service-account.json
11
+ ```
12
+
13
+ Setup writes only a quoted property ID under the `production` profile. It
14
+ will reuse a matching file but will not overwrite an existing profile that
15
+ targets another property.
16
+
6
17
  ## Smallest valid file
7
18
 
8
19
  ```yaml
@@ -44,7 +55,7 @@ profiles:
44
55
  default_uri: "https://www.example.test"
45
56
 
46
57
  # Accepted only as an explicit experimental declaration.
47
- # Version 0.1.0 reports a finding and does not apply this setting.
58
+ # Version 0.2.0 reports a finding and does not apply this setting.
48
59
  enhanced_measurement:
49
60
  enabled: true
50
61
  experimental: true
@@ -75,13 +86,16 @@ profiles:
75
86
  description: Non-sensitive calculated estimate
76
87
  scope: event
77
88
  measurement_unit: currency
89
+ # Google requires currency metrics to be classified explicitly.
90
+ restricted_metric_types:
91
+ - revenue_data
78
92
 
79
93
  # Checklist findings only; Analytics Ops does not claim to manage these.
80
94
  manual_requirements:
81
95
  - email_redaction_enabled
82
96
  - consent_mode_reviewed
83
97
 
84
- # Explicit declaration only in 0.1.0; never silently applied.
98
+ # Explicit declaration only in 0.2.0; never silently applied.
85
99
  google_signals:
86
100
  state: disabled
87
101
  experimental: true
@@ -102,12 +116,13 @@ All three fields are required when `retention` is present:
102
116
 
103
117
  - `event_data`: `2_months`, `14_months`, `26_months`, `38_months`, or
104
118
  `50_months`
105
- - `user_data`: the same values
119
+ - `user_data`: `2_months` or `14_months`
106
120
  - `reset_on_new_activity`: `true` or `false`
107
121
 
108
- Google may restrict longer periods by property type or account capabilities.
109
- An API rejection is returned as a typed error; Analytics Ops does not silently
110
- substitute a different period.
122
+ The 26-, 38-, and 50-month event periods are available only to Analytics 360
123
+ properties. Google does not allow those periods for user data, so Analytics
124
+ Ops rejects them locally. An API rejection for an ineligible event period is
125
+ returned as a typed error; the gem never substitutes a different period.
111
126
 
112
127
  ### Key events
113
128
 
@@ -119,6 +134,8 @@ substitute a different period.
119
134
  Required fields are `parameter_name`, `display_name`, and `scope`.
120
135
  Supported scopes are `event`, `user`, and `item`.
121
136
  `disallow_ads_personalization` is valid only for user-scoped dimensions.
137
+ Display names must start with a letter and contain only letters, numbers,
138
+ spaces, and underscores, matching Google's create/update contract.
122
139
 
123
140
  Identity is `scope + parameter_name`; display name and description are
124
141
  mutable. Immutable conflicts are findings, not automatic replacement.
@@ -131,8 +148,14 @@ Required fields are `parameter_name`, `display_name`, and
131
148
  `standard`, `currency`, `feet`, `meters`, `kilometers`, `miles`,
132
149
  `milliseconds`, `seconds`, `minutes`, and `hours`.
133
150
 
134
- Identity is `parameter_name`. Scope and unit are immutable; Analytics Ops
135
- will not archive and recreate a conflicting metric.
151
+ A `currency` metric also requires `restricted_metric_types` containing
152
+ `cost_data`, `revenue_data`, or both. This classification controls restricted
153
+ data access in Google Analytics, so Analytics Ops never guesses it. The field
154
+ must be omitted or empty for non-currency metrics.
155
+
156
+ Identity is `parameter_name`. Scope, unit, and restricted-data classification
157
+ are treated as immutable; Analytics Ops will not archive and recreate a
158
+ conflicting metric.
136
159
 
137
160
  ## Environment variables
138
161
 
@@ -151,8 +174,9 @@ execution, YAML alias support, or Ruby-object deserialization.
151
174
  ## Strict safety rules
152
175
 
153
176
  - The file is limited to 1 MiB.
177
+ - YAML nesting is bounded and duplicate mapping keys are rejected.
154
178
  - Unknown fields fail closed.
155
- - Secret-shaped fields fail closed at any depth.
179
+ - Secret-shaped fields and credential-shaped values fail closed.
156
180
  - Numeric identifiers must be YAML strings.
157
181
  - User-visible strings reject control characters.
158
182
  - Duplicate resource identities fail validation.
@@ -9,6 +9,7 @@ second HTTP stack and does not expose generated Google objects publicly.
9
9
  | --- | --- | --- |
10
10
  | `google-analytics-admin` | `~> 0.8.0` | 0.8.0 |
11
11
  | `google-analytics-data` | `~> 0.9.0` | 0.9.0 |
12
+ | `googleauth` | `~> 1.12` | 1.17.1 |
12
13
 
13
14
  The reviewed lockfile resolves these generated transports:
14
15
 
@@ -22,10 +23,10 @@ transport name does not make every feature Alpha: Google publishes feature
22
23
  maturity separately. Analytics Ops treats capabilities Google identifies as
23
24
  Alpha as experimental or unsupported.
24
25
 
25
- The pessimistic direct bounds allow reviewed patch updates inside each minor
26
- line and reject a new minor contract automatically. `doctor` reports the
27
- installed direct versions, expected bounds, and selected `grpc` or `rest`
28
- transport.
26
+ The API-wrapper bounds allow reviewed patch updates inside each minor line and
27
+ reject a new minor contract automatically. The `googleauth` bound stays within
28
+ its compatible 1.x public API. `doctor` reports the installed Admin and Data
29
+ wrapper versions, expected bounds, and selected `grpc` or `rest` transport.
29
30
 
30
31
  ## Executable contract coverage
31
32
 
@@ -40,11 +41,14 @@ protobuf classes for:
40
41
  - `list_key_events` and `create_key_event`
41
42
  - list/create/update custom dimensions
42
43
  - list/create/update custom metrics
44
+ - custom-metric restricted cost/revenue enum translation
43
45
  - update web data-stream default URI
44
- - `run_report` and `run_realtime_report`
46
+ - `run_report`, `batch_run_reports`, and `run_realtime_report`
45
47
 
46
48
  The tests also prove enum mappings and normalization into immutable gem-owned
47
- values without making network requests.
49
+ values without making network requests. Service-account tests exercise the
50
+ official `Google::Auth::ServiceAccountCredentials` loader with generated fake
51
+ keys and verify the exact read/edit scopes.
48
52
 
49
53
  ## Updating a Google client
50
54