@iann29/rastro 0.1.0-alpha.1 → 0.1.0-alpha.11

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 (179) hide show
  1. package/README.md +693 -69
  2. package/agent/integration.md +801 -0
  3. package/agent/manifest.json +205 -0
  4. package/agent/manifest.schema.json +444 -0
  5. package/dist/client/federation.d.ts +381 -0
  6. package/dist/client/federation.d.ts.map +1 -0
  7. package/dist/client/federation.js +274 -0
  8. package/dist/client/federation.js.map +1 -0
  9. package/dist/client/index.d.ts +2673 -15
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +565 -33
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/component/_generated/api.d.ts +22 -0
  14. package/dist/component/_generated/api.d.ts.map +1 -1
  15. package/dist/component/_generated/api.js.map +1 -1
  16. package/dist/component/_generated/component.d.ts +315 -4
  17. package/dist/component/_generated/component.d.ts.map +1 -1
  18. package/dist/component/_generated/server.d.ts +4 -0
  19. package/dist/component/_generated/server.d.ts.map +1 -1
  20. package/dist/component/_generated/server.js.map +1 -1
  21. package/dist/component/affiliates.d.ts.map +1 -1
  22. package/dist/component/affiliates.js +6 -2
  23. package/dist/component/affiliates.js.map +1 -1
  24. package/dist/component/cardinality.d.ts +12 -0
  25. package/dist/component/cardinality.d.ts.map +1 -0
  26. package/dist/component/cardinality.js +94 -0
  27. package/dist/component/cardinality.js.map +1 -0
  28. package/dist/component/constants.d.ts +28 -1
  29. package/dist/component/constants.d.ts.map +1 -1
  30. package/dist/component/constants.js +44 -1
  31. package/dist/component/constants.js.map +1 -1
  32. package/dist/component/convex.config.d.ts +6 -1
  33. package/dist/component/convex.config.js +9 -1
  34. package/dist/component/convex.config.js.map +1 -1
  35. package/dist/component/coverage.d.ts +27 -0
  36. package/dist/component/coverage.d.ts.map +1 -0
  37. package/dist/component/coverage.js +56 -0
  38. package/dist/component/coverage.js.map +1 -0
  39. package/dist/component/diagnostics.d.ts +9 -0
  40. package/dist/component/diagnostics.d.ts.map +1 -0
  41. package/dist/component/diagnostics.js +47 -0
  42. package/dist/component/diagnostics.js.map +1 -0
  43. package/dist/component/errors.d.ts +1 -1
  44. package/dist/component/errors.d.ts.map +1 -1
  45. package/dist/component/errors.js.map +1 -1
  46. package/dist/component/eventStore.d.ts +21 -9
  47. package/dist/component/eventStore.d.ts.map +1 -1
  48. package/dist/component/eventStore.js +142 -152
  49. package/dist/component/eventStore.js.map +1 -1
  50. package/dist/component/funnels.d.ts.map +1 -1
  51. package/dist/component/funnels.js +5 -3
  52. package/dist/component/funnels.js.map +1 -1
  53. package/dist/component/geo.d.ts +73 -0
  54. package/dist/component/geo.d.ts.map +1 -0
  55. package/dist/component/geo.js +648 -0
  56. package/dist/component/geo.js.map +1 -0
  57. package/dist/component/goals.d.ts.map +1 -1
  58. package/dist/component/goals.js +8 -5
  59. package/dist/component/goals.js.map +1 -1
  60. package/dist/component/guards.d.ts.map +1 -1
  61. package/dist/component/guards.js.map +1 -1
  62. package/dist/component/http.d.ts.map +1 -1
  63. package/dist/component/http.js +281 -62
  64. package/dist/component/http.js.map +1 -1
  65. package/dist/component/identity.d.ts +13 -0
  66. package/dist/component/identity.d.ts.map +1 -0
  67. package/dist/component/identity.js +58 -0
  68. package/dist/component/identity.js.map +1 -0
  69. package/dist/component/ingest.d.ts +3 -1
  70. package/dist/component/ingest.d.ts.map +1 -1
  71. package/dist/component/ingest.js +498 -95
  72. package/dist/component/ingest.js.map +1 -1
  73. package/dist/component/live.d.ts.map +1 -1
  74. package/dist/component/live.js +7 -6
  75. package/dist/component/live.js.map +1 -1
  76. package/dist/component/localTime.d.ts +25 -0
  77. package/dist/component/localTime.d.ts.map +1 -0
  78. package/dist/component/localTime.js +126 -0
  79. package/dist/component/localTime.js.map +1 -0
  80. package/dist/component/reports.d.ts +266 -10
  81. package/dist/component/reports.d.ts.map +1 -1
  82. package/dist/component/reports.js +1243 -137
  83. package/dist/component/reports.js.map +1 -1
  84. package/dist/component/retention.d.ts +75 -1
  85. package/dist/component/retention.d.ts.map +1 -1
  86. package/dist/component/retention.js +561 -38
  87. package/dist/component/retention.js.map +1 -1
  88. package/dist/component/rollupStore.d.ts +320 -0
  89. package/dist/component/rollupStore.d.ts.map +1 -0
  90. package/dist/component/rollupStore.js +596 -0
  91. package/dist/component/rollupStore.js.map +1 -0
  92. package/dist/component/rollups.d.ts +20 -0
  93. package/dist/component/rollups.d.ts.map +1 -0
  94. package/dist/component/rollups.js +73 -0
  95. package/dist/component/rollups.js.map +1 -0
  96. package/dist/component/sanitize.d.ts +24 -1
  97. package/dist/component/sanitize.d.ts.map +1 -1
  98. package/dist/component/sanitize.js +96 -16
  99. package/dist/component/sanitize.js.map +1 -1
  100. package/dist/component/schema.d.ts +687 -65
  101. package/dist/component/schema.js +187 -20
  102. package/dist/component/schema.js.map +1 -1
  103. package/dist/component/sites.d.ts +12 -0
  104. package/dist/component/sites.d.ts.map +1 -1
  105. package/dist/component/sites.js +41 -7
  106. package/dist/component/sites.js.map +1 -1
  107. package/dist/component/useragent.d.ts +9 -0
  108. package/dist/component/useragent.d.ts.map +1 -0
  109. package/dist/component/useragent.js +152 -0
  110. package/dist/component/useragent.js.map +1 -0
  111. package/dist/component/validators.d.ts +124 -69
  112. package/dist/component/validators.d.ts.map +1 -1
  113. package/dist/component/validators.js +35 -14
  114. package/dist/component/validators.js.map +1 -1
  115. package/dist/component/visitors.d.ts +19 -0
  116. package/dist/component/visitors.d.ts.map +1 -0
  117. package/dist/component/visitors.js +86 -0
  118. package/dist/component/visitors.js.map +1 -0
  119. package/dist/component/vitals.d.ts +41 -0
  120. package/dist/component/vitals.d.ts.map +1 -0
  121. package/dist/component/vitals.js +115 -0
  122. package/dist/component/vitals.js.map +1 -0
  123. package/dist/react/index.d.ts.map +1 -1
  124. package/dist/react/index.js.map +1 -1
  125. package/dist/tracker/generated.d.ts +11 -4
  126. package/dist/tracker/generated.d.ts.map +1 -1
  127. package/dist/tracker/generated.js +11 -4
  128. package/dist/tracker/generated.js.map +1 -1
  129. package/dist/tracker/tracker.d.ts +1 -1
  130. package/dist/tracker/tracker.d.ts.map +1 -1
  131. package/dist/tracker/tracker.js +57 -20
  132. package/dist/tracker/tracker.js.map +1 -1
  133. package/dist/tracker/vitals.d.ts +10 -0
  134. package/dist/tracker/vitals.d.ts.map +1 -0
  135. package/dist/tracker/vitals.js +140 -0
  136. package/dist/tracker/vitals.js.map +1 -0
  137. package/dist/tracker.min.js +1 -1
  138. package/dist/vitals.min.js +1 -0
  139. package/docs/benchmarks/2026-08-20-realistic.md +76 -76
  140. package/docs/benchmarks/2026-08-21-formal-certification.md +353 -0
  141. package/docs/benchmarks/2026-08-30-alpha6-recertification.md +206 -0
  142. package/docs/federation-setup.md +464 -0
  143. package/docs/federation.md +352 -0
  144. package/docs/upgrading.md +344 -0
  145. package/llms.txt +72 -0
  146. package/package.json +55 -11
  147. package/scripts/benchmark-ingest.mjs +175 -73
  148. package/scripts/generate-federation-keys.mjs +20 -0
  149. package/src/component/_generated/api.ts +22 -0
  150. package/src/component/_generated/component.ts +381 -4
  151. package/src/component/_generated/server.ts +4 -0
  152. package/src/component/affiliates.ts +20 -5
  153. package/src/component/cardinality.ts +117 -0
  154. package/src/component/constants.ts +44 -1
  155. package/src/component/convex.config.ts +11 -1
  156. package/src/component/coverage.ts +71 -0
  157. package/src/component/diagnostics.ts +65 -0
  158. package/src/component/errors.ts +2 -1
  159. package/src/component/eventStore.ts +217 -193
  160. package/src/component/funnels.ts +19 -16
  161. package/src/component/geo.ts +835 -0
  162. package/src/component/goals.ts +29 -21
  163. package/src/component/guards.ts +3 -1
  164. package/src/component/http.ts +404 -70
  165. package/src/component/identity.ts +74 -0
  166. package/src/component/ingest.ts +894 -188
  167. package/src/component/live.ts +13 -7
  168. package/src/component/localTime.ts +167 -0
  169. package/src/component/reports.ts +1872 -197
  170. package/src/component/retention.ts +788 -96
  171. package/src/component/rollupStore.ts +799 -0
  172. package/src/component/rollups.ts +82 -0
  173. package/src/component/sanitize.ts +144 -29
  174. package/src/component/schema.ts +217 -21
  175. package/src/component/sites.ts +59 -12
  176. package/src/component/useragent.ts +171 -0
  177. package/src/component/validators.ts +49 -14
  178. package/src/component/visitors.ts +116 -0
  179. package/src/component/vitals.ts +146 -0
@@ -0,0 +1,352 @@
1
+ # Federated Amage Rastro dashboard
2
+
3
+ This document is the protocol reference. Follow
4
+ [`federation-setup.md`](federation-setup.md) for the executable customer-host
5
+ installation, pairing, verification, and revocation sequence.
6
+
7
+ The federated dashboard model keeps telemetry in the customer's Convex or
8
+ Synapse deployment while a browser loaded from `amagerastro.com` subscribes
9
+ directly to authorized host functions in that deployment.
10
+
11
+ ```text
12
+ tracker -> customer deployment -> Rastro component tables
13
+
14
+ amagerastro.com browser -> customer deployment -> reactive Rastro reports
15
+ ```
16
+
17
+ The Amage Rastro control plane stores account and connection metadata. It does
18
+ not need to proxy or persist the customer's analytics events.
19
+
20
+ ## Connector protocol v1
21
+
22
+ The typed `FEDERATED_ANALYTICS_CONNECTOR_MANIFEST` constant defines the stable
23
+ v1 contract:
24
+
25
+ - protocol: `amage-rastro-analytics`
26
+ - protocol version: `1`
27
+ - canonical host module: `rastroFederation`
28
+ - canonical function names and supported capabilities
29
+ - public request, connection, range, live visitor, and journey limits
30
+
31
+ The host module must be `convex/rastroFederation.ts`, so its public references
32
+ match the function names advertised by the manifest. The generated `manifest`
33
+ query is public because it contains only static protocol metadata. It does not
34
+ resolve a connection or expose customer data. Compare a served manifest with
35
+ `agent/manifest.json`'s `connector` object structurally, never as serialized
36
+ text: the two are the same value with different key order, and a byte-for-byte
37
+ comparison fails on a correct host.
38
+
39
+ The exact generated surface is:
40
+
41
+ - `manifest`
42
+ - `connectionStatus`
43
+ - `listSites`
44
+ - `overview`
45
+ - `liveVisitors`
46
+ - `listSessions`
47
+ - `sessionJourney`
48
+ - `listConversions`
49
+ - `visitorJourney`
50
+ - `goalsReport`
51
+ - `funnelsReport`
52
+ - `affiliatesReport`
53
+ - `dataCoverage`
54
+ - `vitalsReport` (optional capability `vitals`, hosts from `alpha.6`)
55
+ - `siteMap` (optional capability `siteMap`, hosts from `alpha.8`)
56
+ - the configure scope (optional capability `configure`, hosts from `alpha.11`):
57
+ `siteSettings`, `updateSite`, `listGoals`, `upsertGoal`, `removeGoal`,
58
+ `listFunnels`, `upsertFunnel`, `removeFunnel`, `listAffiliates`,
59
+ `upsertAffiliate`, `removeAffiliate`, `retentionStatus`, `setRetentionPolicy`,
60
+ and `disableRetentionPolicy`
61
+
62
+ Hosts from `alpha.11` also advertise the optional capability `localDays`: their
63
+ `overview` answers a daily range that covers whole calendar days in the site's
64
+ timezone from buckets kept in that zone, and `listSites` says per site which
65
+ zone that is and since when the days are complete. Dashboards must not require
66
+ the capability.
67
+
68
+ There are no ingestion, owner-wide enumeration, or arbitrary host functions in
69
+ this surface, and no write runs with a read-only token: every function of the
70
+ configure scope requires the `analytics:configure` permission on the token
71
+ **and** on the host's local grant, as described under
72
+ [Configure scope](#configure-scope). The read-only goal, funnel, and affiliate
73
+ reports include their bounded definitions, and `listConversions` returns only
74
+ the trusted server-side conversion ledger. Browser `conversion` telemetry
75
+ remains untrusted journey data and never enters that ledger. Journey events
76
+ include bounded custom properties. `exposeFederatedAnalyticsApi` accepts only
77
+ `FederatedAnalyticsAuthorizerOptions`; it does not accept or expose an arbitrary
78
+ host `AnalyticsAuthorizer`.
79
+
80
+ `connectionStatus` authenticates and resolves the authoritative connection on
81
+ every call. Its response is deliberately redacted to status, protocol version,
82
+ capabilities, the effective permissions, and site count. It does not return
83
+ connection IDs, organization IDs, identity claims, or site IDs.
84
+
85
+ `listSites` takes no arguments. It loads sites only from the `siteIds` in the
86
+ validated local connection and returns `FederatedSiteSummary` values containing
87
+ only `siteId`, `name`, `currency`, `timezone`, `cookieless`, and (hosts from
88
+ `alpha.11`) `localDays` — `{ timezone, since }` when the host keeps the site's
89
+ calendar days, `null` otherwise. In particular, it never returns `ownerId`,
90
+ `domains`, or `networkId`.
91
+
92
+ ## Configure scope
93
+
94
+ Hosts from `alpha.11` advertise the optional `configure` capability. Behind it,
95
+ the dashboard's organization owners and admins can manage goals, funnels,
96
+ affiliates, site settings, and the retention policy of the granted sites without
97
+ a host deploy. The scope is off unless the host opts in:
98
+
99
+ - The control plane mints `rastro_permissions: ["analytics:read"]` for every
100
+ member and adds `"analytics:configure"` only for members whose organization
101
+ role is `owner` or `admin`.
102
+ - The host's local grant lists the permissions it honors. A grant without
103
+ `permissions`, or without `analytics:configure` in it, is read only whatever
104
+ the token claims.
105
+ - A configure function runs only when both agree; otherwise it fails with
106
+ `FEDERATION_CONFIGURE_FORBIDDEN` before the site scope is examined, so a
107
+ read-only token learns nothing about the grant by probing. `connectionStatus`
108
+ reports the effective permissions, and the dashboard renders forms only when
109
+ they include the configure scope; otherwise every form ends in the code the
110
+ host runs itself.
111
+
112
+ The scope is bounded to configuration: `upsertGoal`, `removeGoal`,
113
+ `upsertFunnel`, `removeFunnel`, `upsertAffiliate`, and `removeAffiliate` mirror
114
+ the `Rastro` class, `listGoals`, `listFunnels`, and `listAffiliates` return the
115
+ definitions, `setRetentionPolicy` and `disableRetentionPolicy` manage the
116
+ recurring cleanup that `retentionStatus` reports, and `updateSite` changes only
117
+ `name`, `domains`, and `timezone` — the currency is fixed by the ledger and
118
+ `cookieless`, `networkId`, and `ownerId` stay outside the protocol.
119
+ `siteSettings` is the one place the surface shows a site's `domains`, because a
120
+ connection allowed to change them must see them; `listSites` stays redacted for
121
+ every reader. Nothing in the scope ingests telemetry or enumerates sites beyond
122
+ the grant. The constants `FEDERATED_ANALYTICS_CONFIGURE_PERMISSION`,
123
+ `FEDERATED_ANALYTICS_PERMISSIONS`, `FEDERATED_ANALYTICS_CONFIGURE_FUNCTIONS`,
124
+ the `federatedAnalyticsPermissionValidator`, and `effectiveFederatedPermissions`
125
+ are exported for hosts and connector clients.
126
+
127
+ ## Authentication contract
128
+
129
+ The customer host must configure Convex authentication to trust the Amage Rastro
130
+ OIDC issuer. Convex validates the JWT signature, issuer, audience, and expiry
131
+ before any host function runs. The helper then requires these custom claims:
132
+
133
+ | Claim | Value |
134
+ | ------------------------ | -------------------------------------------------------------------------- |
135
+ | `rastro_connection_id` | The paired deployment connection |
136
+ | `rastro_organization_id` | The Amage Rastro organization |
137
+ | `rastro_permissions` | Must include `analytics:read`; owners and admins add `analytics:configure` |
138
+
139
+ Claim names and the permission constants are exported as
140
+ `FEDERATED_ANALYTICS_CLAIMS`, `FEDERATED_ANALYTICS_READ_PERMISSION`, and
141
+ `FEDERATED_ANALYTICS_CONFIGURE_PERMISSION`.
142
+
143
+ The audience is the customer's normalized Convex deployment URL, for example
144
+ `https://product-123.convex.cloud`. It is stable across connections to that
145
+ deployment; `rastro_connection_id` identifies the individual grant. A customer
146
+ host configures the provider with the issuer and audience issued during pairing:
147
+
148
+ ```ts
149
+ // convex/auth.config.ts
150
+ import type { AuthConfig } from "convex/server";
151
+
152
+ export default {
153
+ providers: [
154
+ // Keep the application's existing providers here.
155
+ {
156
+ domain: process.env.RASTRO_FEDERATION_ISSUER!,
157
+ applicationID: process.env.RASTRO_FEDERATION_AUDIENCE!,
158
+ },
159
+ ],
160
+ } satisfies AuthConfig;
161
+ ```
162
+
163
+ `RASTRO_FEDERATION_AUDIENCE` must exactly match the deployment URL registered in
164
+ the control plane. Insert the provider object into the host's existing
165
+ `providers` array; the complete insertion fragment is in
166
+ [`federation-setup.md`](federation-setup.md). Never replace existing providers.
167
+ The example control plane publishes OIDC discovery and JWKS endpoints under
168
+ `/federation`, signs RS256 tokens with a ten-minute lifetime, and never sends
169
+ its private key to the browser.
170
+
171
+ ## Authoritative local connection
172
+
173
+ JWT claims identify a requested connection but do not define its site access.
174
+ The customer deployment resolves a local connection record on every
175
+ authenticated query. This record is authoritative and makes revocation immediate
176
+ instead of waiting for a token to expire.
177
+
178
+ A host schema can model it as:
179
+
180
+ ```ts
181
+ import { defineSchema, defineTable } from "convex/server";
182
+ import { v } from "convex/values";
183
+
184
+ export default defineSchema({
185
+ rastroFederationGrants: defineTable({
186
+ connectionId: v.string(),
187
+ organizationId: v.string(),
188
+ siteIds: v.array(v.string()),
189
+ // Absent means read only; ["analytics:configure"] opts into the scope.
190
+ permissions: v.optional(v.array(v.string())),
191
+ createdAt: v.number(),
192
+ updatedAt: v.number(),
193
+ revokedAt: v.optional(v.number()),
194
+ }).index("by_connectionId", ["connectionId"]),
195
+ });
196
+ ```
197
+
198
+ Pairing and revocation mutations belong to the host application and must use its
199
+ existing administrator authorization. The Rastro component cannot inspect host
200
+ authentication state. The grant's `permissions`, like its `siteIds`, are the
201
+ host's decision: a token cannot grant itself the configure scope.
202
+
203
+ ## Host API
204
+
205
+ ```ts
206
+ // convex/rastroFederation.ts
207
+ import {
208
+ exposeFederatedAnalyticsApi,
209
+ type FederatedAnalyticsConnection,
210
+ type FederatedAnalyticsPermission,
211
+ } from "@iann29/rastro";
212
+ import { components } from "./_generated/api";
213
+ import { env, type QueryCtx } from "./_generated/server";
214
+
215
+ // RASTRO_FEDERATION_ISSUER is declared optional in convex.config.ts so a push
216
+ // never depends on it; the production issuer is the default.
217
+ const issuer = (
218
+ env.RASTRO_FEDERATION_ISSUER ?? "https://site.api.amagerastro.com/federation"
219
+ ).replace(/\/$/, "");
220
+
221
+ const federated = exposeFederatedAnalyticsApi(components.rastroAnalytics, {
222
+ issuer,
223
+ resolveConnection: async (ctx, identity) => {
224
+ const db = ctx.db as unknown as QueryCtx["db"];
225
+ const grant = await db
226
+ .query("rastroFederationGrants")
227
+ .withIndex("by_connectionId", (query) =>
228
+ query.eq("connectionId", identity.connectionId),
229
+ )
230
+ .unique();
231
+
232
+ if (!grant || grant.organizationId !== identity.organizationId) return null;
233
+ return {
234
+ connectionId: grant.connectionId,
235
+ organizationId: grant.organizationId,
236
+ siteIds: grant.siteIds,
237
+ permissions: grant.permissions as FederatedAnalyticsPermission[],
238
+ revokedAt: grant.revokedAt,
239
+ } satisfies FederatedAnalyticsConnection;
240
+ },
241
+ });
242
+
243
+ export const {
244
+ manifest,
245
+ connectionStatus,
246
+ listSites,
247
+ overview,
248
+ liveVisitors,
249
+ listSessions,
250
+ sessionJourney,
251
+ listConversions,
252
+ visitorJourney,
253
+ goalsReport,
254
+ funnelsReport,
255
+ affiliatesReport,
256
+ vitalsReport,
257
+ siteMap,
258
+ dataCoverage,
259
+ siteSettings,
260
+ updateSite,
261
+ listGoals,
262
+ upsertGoal,
263
+ removeGoal,
264
+ listFunnels,
265
+ upsertFunnel,
266
+ removeFunnel,
267
+ listAffiliates,
268
+ upsertAffiliate,
269
+ removeAffiliate,
270
+ retentionStatus,
271
+ setRetentionPolicy,
272
+ disableRetentionPolicy,
273
+ } = federated;
274
+ ```
275
+
276
+ The resolver receives the authenticated `UserIdentity` as well as the parsed
277
+ connection and organization IDs, so a host may enforce additional local rules.
278
+ It must use an indexed, authoritative host lookup rather than token-provided
279
+ site IDs. A host that never lists `analytics:configure` in a grant may leave the
280
+ configure functions out of the export; the manifest still advertises them, and
281
+ the dashboard never calls them for a read-only connection.
282
+
283
+ ## Stable errors
284
+
285
+ `FEDERATED_ANALYTICS_ERROR_CODES` exports all protocol v1 authorization codes:
286
+
287
+ | Code | Meaning |
288
+ | -------------------------------- | -------------------------------------------- |
289
+ | `FEDERATION_UNAUTHENTICATED` | No authenticated identity |
290
+ | `FEDERATION_ISSUER_MISMATCH` | Identity came from another issuer |
291
+ | `FEDERATION_INVALID_CLAIMS` | Required bounded claims or permission absent |
292
+ | `FEDERATION_CONNECTION_DENIED` | Connection missing, revoked, or malformed |
293
+ | `FEDERATION_INVALID_SCOPE` | Requested site list violates public limits |
294
+ | `FEDERATION_SITE_DENIED` | Requested site is outside the connection |
295
+ | `FEDERATION_CONFIGURE_FORBIDDEN` | Token or grant lacks `analytics:configure` |
296
+
297
+ Consumers should branch on `ConvexError.data.code`, not human-readable messages.
298
+
299
+ ## Enforced invariants
300
+
301
+ - The identity issuer must match exactly.
302
+ - The token must include the read permission.
303
+ - A configure function runs only when the token and the grant both carry
304
+ `analytics:configure`; a grant listing an unknown permission is denied whole.
305
+ - Report queries must request one through ten unique, bounded site IDs.
306
+ - The resolved connection ID and organization must match the token.
307
+ - Revoked, missing, malformed, empty, or duplicate-site connections are denied.
308
+ - The resolved connection may authorize at most 10 unique site IDs.
309
+ - Every report site must exist in the local connection scope.
310
+ - Site discovery trusts only the resolved connection, never caller arguments.
311
+ - Owner-wide enumeration, ingestion, and arbitrary host functions are absent;
312
+ the only writes are the configure scope's, and `listSites` never returns
313
+ `ownerId`, `domains`, or `networkId`.
314
+ - `dataCoverage` is read-only and uses the same connection-scoped site
315
+ authorization.
316
+
317
+ `dataCoverage` accepts one authorized `siteId` and an inclusive integer
318
+ millisecond `from`/`to` range. It returns availability, rollup generation, and
319
+ retention boundaries for each dashboard dataset, but no events, visitor IDs,
320
+ session IDs, or site configuration. Dashboard clients should use it to explain
321
+ `complete`, `partial`, `retained`, and `unavailable` states rather than treating
322
+ an empty report as proof that no telemetry exists.
323
+
324
+ Availability is derived from bounded indexed source reads rather than a shared
325
+ per-ingest watermark. The dataset list distinguishes `overviewHour` from
326
+ `overviewDay`; clients must not apply one granularity's retention boundary to
327
+ the other. Heartbeats advance `sessions` availability only, not `events` or
328
+ either overview dataset.
329
+
330
+ ## Control-plane status
331
+
332
+ The production control plane at `https://www.amagerastro.com` and its reference
333
+ implementation under `control-plane/` provide Better Auth sessions,
334
+ organization-scoped connections, OIDC discovery, JWKS, ten-minute RS256 tokens
335
+ whose permissions follow the member's organization role, manifest verification,
336
+ redacted site discovery, audit records, revocation of new token issuance, and
337
+ dynamic browser subscriptions to active deployments. Verification records the
338
+ permissions the host honors for an admin token, so **Conexões** shows whether a
339
+ connection is read only or reads and configures.
340
+
341
+ Do not assume that a dist-tag or source checkout contains this protocol. Inspect
342
+ an operator-approved exact npm registry artifact outside the host, without
343
+ lifecycle scripts, and statically verify every runtime and declaration export
344
+ listed by the machine manifest before changing a customer deployment. Automation
345
+ must stop at this gate rather than fetching source or reimplementing the
346
+ connector.
347
+
348
+ Remote local-grant provisioning is intentionally not an unauthenticated control-
349
+ plane write. The customer operator provisions the authoritative grant through an
350
+ internal or host-admin mutation. For immediate revocation, revoke that local
351
+ grant before revoking the central connection; otherwise an already-issued token
352
+ remains valid until its ten-minute expiry.