@iann29/rastro 0.1.0-alpha.0 → 0.1.0-alpha.10

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 (164) hide show
  1. package/README.md +579 -57
  2. package/agent/integration.md +661 -0
  3. package/agent/manifest.json +187 -0
  4. package/agent/manifest.schema.json +411 -0
  5. package/dist/client/federation.d.ts +236 -0
  6. package/dist/client/federation.d.ts.map +1 -0
  7. package/dist/client/federation.js +196 -0
  8. package/dist/client/federation.js.map +1 -0
  9. package/dist/client/index.d.ts +2467 -18
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +461 -32
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/component/_generated/api.d.ts +18 -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 +302 -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 +34 -7
  29. package/dist/component/constants.d.ts.map +1 -1
  30. package/dist/component/constants.js +47 -7
  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 +19 -0
  36. package/dist/component/coverage.d.ts.map +1 -0
  37. package/dist/component/coverage.js +30 -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 +106 -0
  47. package/dist/component/eventStore.d.ts.map +1 -0
  48. package/dist/component/eventStore.js +294 -0
  49. package/dist/component/eventStore.js.map +1 -0
  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 +71 -0
  54. package/dist/component/geo.d.ts.map +1 -0
  55. package/dist/component/geo.js +611 -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 +25 -9
  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 +284 -53
  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 +1105 -243
  72. package/dist/component/ingest.js.map +1 -1
  73. package/dist/component/live.d.ts +8 -0
  74. package/dist/component/live.d.ts.map +1 -1
  75. package/dist/component/live.js +74 -9
  76. package/dist/component/live.js.map +1 -1
  77. package/dist/component/reports.d.ts +273 -24
  78. package/dist/component/reports.d.ts.map +1 -1
  79. package/dist/component/reports.js +1320 -150
  80. package/dist/component/reports.js.map +1 -1
  81. package/dist/component/retention.d.ts +75 -1
  82. package/dist/component/retention.d.ts.map +1 -1
  83. package/dist/component/retention.js +541 -31
  84. package/dist/component/retention.js.map +1 -1
  85. package/dist/component/sanitize.d.ts +24 -1
  86. package/dist/component/sanitize.d.ts.map +1 -1
  87. package/dist/component/sanitize.js +107 -19
  88. package/dist/component/sanitize.js.map +1 -1
  89. package/dist/component/schema.d.ts +462 -55
  90. package/dist/component/schema.js +195 -15
  91. package/dist/component/schema.js.map +1 -1
  92. package/dist/component/sites.d.ts.map +1 -1
  93. package/dist/component/sites.js +38 -25
  94. package/dist/component/sites.js.map +1 -1
  95. package/dist/component/useragent.d.ts +9 -0
  96. package/dist/component/useragent.d.ts.map +1 -0
  97. package/dist/component/useragent.js +152 -0
  98. package/dist/component/useragent.js.map +1 -0
  99. package/dist/component/validators.d.ts +101 -16
  100. package/dist/component/validators.d.ts.map +1 -1
  101. package/dist/component/validators.js +19 -2
  102. package/dist/component/validators.js.map +1 -1
  103. package/dist/component/visitors.d.ts +19 -0
  104. package/dist/component/visitors.d.ts.map +1 -0
  105. package/dist/component/visitors.js +86 -0
  106. package/dist/component/visitors.js.map +1 -0
  107. package/dist/component/vitals.d.ts +41 -0
  108. package/dist/component/vitals.d.ts.map +1 -0
  109. package/dist/component/vitals.js +115 -0
  110. package/dist/component/vitals.js.map +1 -0
  111. package/dist/react/index.d.ts.map +1 -1
  112. package/dist/react/index.js.map +1 -1
  113. package/dist/tracker/generated.d.ts +8 -4
  114. package/dist/tracker/generated.d.ts.map +1 -1
  115. package/dist/tracker/generated.js +8 -4
  116. package/dist/tracker/generated.js.map +1 -1
  117. package/dist/tracker/tracker.d.ts +1 -1
  118. package/dist/tracker/tracker.d.ts.map +1 -1
  119. package/dist/tracker/tracker.js +81 -33
  120. package/dist/tracker/tracker.js.map +1 -1
  121. package/dist/tracker/vitals.d.ts +10 -0
  122. package/dist/tracker/vitals.d.ts.map +1 -0
  123. package/dist/tracker/vitals.js +140 -0
  124. package/dist/tracker/vitals.js.map +1 -0
  125. package/dist/tracker.min.js +1 -1
  126. package/dist/vitals.min.js +1 -0
  127. package/docs/benchmarks/2026-08-20-realistic.md +171 -0
  128. package/docs/benchmarks/2026-08-21-formal-certification.md +353 -0
  129. package/docs/benchmarks/2026-08-30-alpha6-recertification.md +206 -0
  130. package/docs/federation-setup.md +397 -0
  131. package/docs/federation.md +262 -0
  132. package/docs/upgrading.md +259 -0
  133. package/llms.txt +71 -0
  134. package/package.json +61 -11
  135. package/scripts/benchmark-ingest.mjs +601 -0
  136. package/scripts/generate-federation-keys.mjs +20 -0
  137. package/src/component/_generated/api.ts +18 -0
  138. package/src/component/_generated/component.ts +366 -4
  139. package/src/component/_generated/server.ts +4 -0
  140. package/src/component/affiliates.ts +20 -5
  141. package/src/component/cardinality.ts +117 -0
  142. package/src/component/constants.ts +47 -7
  143. package/src/component/convex.config.ts +11 -1
  144. package/src/component/coverage.ts +36 -0
  145. package/src/component/diagnostics.ts +65 -0
  146. package/src/component/errors.ts +2 -1
  147. package/src/component/eventStore.ts +489 -0
  148. package/src/component/funnels.ts +19 -16
  149. package/src/component/geo.ts +781 -0
  150. package/src/component/goals.ts +45 -16
  151. package/src/component/guards.ts +3 -1
  152. package/src/component/http.ts +403 -60
  153. package/src/component/identity.ts +74 -0
  154. package/src/component/ingest.ts +1652 -291
  155. package/src/component/live.ts +90 -9
  156. package/src/component/reports.ts +1929 -199
  157. package/src/component/retention.ts +774 -97
  158. package/src/component/sanitize.ts +154 -32
  159. package/src/component/schema.ts +219 -15
  160. package/src/component/sites.ts +56 -28
  161. package/src/component/useragent.ts +171 -0
  162. package/src/component/validators.ts +29 -1
  163. package/src/component/visitors.ts +116 -0
  164. package/src/component/vitals.ts +146 -0
@@ -0,0 +1,262 @@
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.
35
+
36
+ The exact generated surface is:
37
+
38
+ - `manifest`
39
+ - `connectionStatus`
40
+ - `listSites`
41
+ - `overview`
42
+ - `liveVisitors`
43
+ - `listSessions`
44
+ - `sessionJourney`
45
+ - `listConversions`
46
+ - `visitorJourney`
47
+ - `goalsReport`
48
+ - `funnelsReport`
49
+ - `affiliatesReport`
50
+ - `dataCoverage`
51
+ - `vitalsReport` (optional capability `vitals`, hosts from `alpha.6`)
52
+ - `siteMap` (optional capability `siteMap`, hosts from `alpha.8`)
53
+
54
+ There are no ingestion, configuration-write, retention-write, raw-site, or site
55
+ mutation functions in this surface. The read-only goal, funnel, and affiliate
56
+ reports include their bounded definitions, and `listConversions` returns only
57
+ the trusted server-side conversion ledger. Browser `conversion` telemetry
58
+ remains untrusted journey data and never enters that ledger. Journey events
59
+ include bounded custom properties. `exposeFederatedAnalyticsApi` accepts only
60
+ `FederatedAnalyticsAuthorizerOptions`; it does not accept or expose an arbitrary
61
+ host `AnalyticsAuthorizer`.
62
+
63
+ `connectionStatus` authenticates and resolves the authoritative connection on
64
+ every call. Its response is deliberately redacted to status, protocol version,
65
+ capabilities, and site count. It does not return connection IDs, organization
66
+ IDs, identity claims, or site IDs.
67
+
68
+ `listSites` takes no arguments. It loads sites only from the `siteIds` in the
69
+ validated local connection and returns `FederatedSiteSummary` values containing
70
+ only `siteId`, `name`, `currency`, `timezone`, and `cookieless`. In particular,
71
+ it never returns `ownerId`, `domains`, or `networkId`.
72
+
73
+ ## Authentication contract
74
+
75
+ The customer host must configure Convex authentication to trust the Amage Rastro
76
+ OIDC issuer. Convex validates the JWT signature, issuer, audience, and expiry
77
+ before any host function runs. The helper then requires these custom claims:
78
+
79
+ | Claim | Value |
80
+ | ------------------------ | -------------------------------- |
81
+ | `rastro_connection_id` | The paired deployment connection |
82
+ | `rastro_organization_id` | The Amage Rastro organization |
83
+ | `rastro_permissions` | Must include `analytics:read` |
84
+
85
+ Claim names and the permission constant are exported as
86
+ `FEDERATED_ANALYTICS_CLAIMS` and `FEDERATED_ANALYTICS_READ_PERMISSION`.
87
+
88
+ The audience is the customer's normalized Convex deployment URL, for example
89
+ `https://product-123.convex.cloud`. It is stable across connections to that
90
+ deployment; `rastro_connection_id` identifies the individual grant. A customer
91
+ host configures the provider with the issuer and audience issued during pairing:
92
+
93
+ ```ts
94
+ // convex/auth.config.ts
95
+ import type { AuthConfig } from "convex/server";
96
+
97
+ export default {
98
+ providers: [
99
+ // Keep the application's existing providers here.
100
+ {
101
+ domain: process.env.RASTRO_FEDERATION_ISSUER!,
102
+ applicationID: process.env.RASTRO_FEDERATION_AUDIENCE!,
103
+ },
104
+ ],
105
+ } satisfies AuthConfig;
106
+ ```
107
+
108
+ `RASTRO_FEDERATION_AUDIENCE` must exactly match the deployment URL registered in
109
+ the control plane. Insert the provider object into the host's existing
110
+ `providers` array; the complete insertion fragment is in
111
+ [`federation-setup.md`](federation-setup.md). Never replace existing providers.
112
+ The example control plane publishes OIDC discovery and JWKS endpoints under
113
+ `/federation`, signs RS256 tokens with a ten-minute lifetime, and never sends
114
+ its private key to the browser.
115
+
116
+ ## Authoritative local connection
117
+
118
+ JWT claims identify a requested connection but do not define its site access.
119
+ The customer deployment resolves a local connection record on every
120
+ authenticated query. This record is authoritative and makes revocation immediate
121
+ instead of waiting for a token to expire.
122
+
123
+ A host schema can model it as:
124
+
125
+ ```ts
126
+ import { defineSchema, defineTable } from "convex/server";
127
+ import { v } from "convex/values";
128
+
129
+ export default defineSchema({
130
+ rastroFederationGrants: defineTable({
131
+ connectionId: v.string(),
132
+ organizationId: v.string(),
133
+ siteIds: v.array(v.string()),
134
+ createdAt: v.number(),
135
+ updatedAt: v.number(),
136
+ revokedAt: v.optional(v.number()),
137
+ }).index("by_connectionId", ["connectionId"]),
138
+ });
139
+ ```
140
+
141
+ Pairing and revocation mutations belong to the host application and must use its
142
+ existing administrator authorization. The Rastro component cannot inspect host
143
+ authentication state.
144
+
145
+ ## Host API
146
+
147
+ ```ts
148
+ // convex/rastroFederation.ts
149
+ import {
150
+ exposeFederatedAnalyticsApi,
151
+ type FederatedAnalyticsConnection,
152
+ } from "@iann29/rastro";
153
+ import { components } from "./_generated/api";
154
+ import { env, type QueryCtx } from "./_generated/server";
155
+
156
+ const federated = exposeFederatedAnalyticsApi(components.rastroAnalytics, {
157
+ issuer: env.RASTRO_FEDERATION_ISSUER.replace(/\/$/, ""),
158
+ resolveConnection: async (ctx, identity) => {
159
+ const db = ctx.db as unknown as QueryCtx["db"];
160
+ const grant = await db
161
+ .query("rastroFederationGrants")
162
+ .withIndex("by_connectionId", (query) =>
163
+ query.eq("connectionId", identity.connectionId),
164
+ )
165
+ .unique();
166
+
167
+ if (!grant || grant.organizationId !== identity.organizationId) return null;
168
+ return {
169
+ connectionId: grant.connectionId,
170
+ organizationId: grant.organizationId,
171
+ siteIds: grant.siteIds,
172
+ revokedAt: grant.revokedAt,
173
+ } satisfies FederatedAnalyticsConnection;
174
+ },
175
+ });
176
+
177
+ export const {
178
+ manifest,
179
+ connectionStatus,
180
+ listSites,
181
+ overview,
182
+ liveVisitors,
183
+ listSessions,
184
+ sessionJourney,
185
+ listConversions,
186
+ visitorJourney,
187
+ goalsReport,
188
+ funnelsReport,
189
+ affiliatesReport,
190
+ vitalsReport,
191
+ siteMap,
192
+ dataCoverage,
193
+ } = federated;
194
+ ```
195
+
196
+ The resolver receives the authenticated `UserIdentity` as well as the parsed
197
+ connection and organization IDs, so a host may enforce additional local rules.
198
+ It must use an indexed, authoritative host lookup rather than token-provided
199
+ site IDs.
200
+
201
+ ## Stable errors
202
+
203
+ `FEDERATED_ANALYTICS_ERROR_CODES` exports all protocol v1 authorization codes:
204
+
205
+ | Code | Meaning |
206
+ | ------------------------------ | -------------------------------------------- |
207
+ | `FEDERATION_UNAUTHENTICATED` | No authenticated identity |
208
+ | `FEDERATION_ISSUER_MISMATCH` | Identity came from another issuer |
209
+ | `FEDERATION_INVALID_CLAIMS` | Required bounded claims or permission absent |
210
+ | `FEDERATION_CONNECTION_DENIED` | Connection missing, revoked, or malformed |
211
+ | `FEDERATION_INVALID_SCOPE` | Requested site list violates public limits |
212
+ | `FEDERATION_SITE_DENIED` | Requested site is outside the connection |
213
+
214
+ Consumers should branch on `ConvexError.data.code`, not human-readable messages.
215
+
216
+ ## Enforced invariants
217
+
218
+ - The identity issuer must match exactly.
219
+ - The token must include the read permission.
220
+ - Report queries must request one through ten unique, bounded site IDs.
221
+ - The resolved connection ID and organization must match the token.
222
+ - Revoked, missing, malformed, empty, or duplicate-site connections are denied.
223
+ - The resolved connection may authorize at most 10 unique site IDs.
224
+ - Every report site must exist in the local connection scope.
225
+ - Site discovery trusts only the resolved connection, never caller arguments.
226
+ - Owner-wide enumeration, raw site configuration, and every write are absent.
227
+ - `dataCoverage` is read-only and uses the same connection-scoped site
228
+ authorization.
229
+
230
+ `dataCoverage` accepts one authorized `siteId` and an inclusive integer
231
+ millisecond `from`/`to` range. It returns availability, rollup generation, and
232
+ retention boundaries for each dashboard dataset, but no events, visitor IDs,
233
+ session IDs, or site configuration. Dashboard clients should use it to explain
234
+ `complete`, `partial`, `retained`, and `unavailable` states rather than treating
235
+ an empty report as proof that no telemetry exists.
236
+
237
+ Availability is derived from bounded indexed source reads rather than a shared
238
+ per-ingest watermark. The dataset list distinguishes `overviewHour` from
239
+ `overviewDay`; clients must not apply one granularity's retention boundary to
240
+ the other. Heartbeats advance `sessions` availability only, not `events` or
241
+ either overview dataset.
242
+
243
+ ## Control-plane status
244
+
245
+ The production control plane at `https://www.amagerastro.com` and its reference
246
+ implementation under `control-plane/` provide Better Auth sessions,
247
+ organization-scoped connections, OIDC discovery, JWKS, ten-minute RS256 tokens,
248
+ manifest verification, redacted site discovery, audit records, revocation of new
249
+ token issuance, and dynamic browser subscriptions to active deployments.
250
+
251
+ Do not assume that a dist-tag or source checkout contains this protocol. Inspect
252
+ an operator-approved exact npm registry artifact outside the host, without
253
+ lifecycle scripts, and statically verify every runtime and declaration export
254
+ listed by the machine manifest before changing a customer deployment. Automation
255
+ must stop at this gate rather than fetching source or reimplementing the
256
+ connector.
257
+
258
+ Remote local-grant provisioning is intentionally not an unauthenticated control-
259
+ plane write. The customer operator provisions the authoritative grant through an
260
+ internal or host-admin mutation. For immediate revocation, revoke that local
261
+ grant before revoking the central connection; otherwise an already-issued token
262
+ remains valid until its ten-minute expiry.
@@ -0,0 +1,259 @@
1
+ # Upgrading Amage Rastro
2
+
3
+ Amage Rastro is pre-stable. Pin exact versions, read the changelog, and rehearse
4
+ upgrades against a non-production snapshot before changing a maintained
5
+ deployment.
6
+
7
+ ## Release matrix
8
+
9
+ | Current state | Required path | Success condition |
10
+ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
11
+ | Fresh deployment | Install the intended release directly | Component deploys and `/rastro/health` succeeds |
12
+ | `0.1.0-alpha.0` with maintained data | Install `0.1.0-alpha.1` first and wait for staged visitor-index backfills | Every staged index reports complete before installing `alpha.2` |
13
+ | `0.1.0-alpha.1` | Wait for staged visitor-index backfills, then install `alpha.2` | Backfills complete before active readers deploy |
14
+ | `0.1.0-alpha.2` with legacy `events` rows | Stay on `alpha.2` and run/resume migration | Durable success plus empty source |
15
+ | `0.1.0-alpha.3` fresh/canonical deployment | Rehearse the next alpha normally | Schema, reports, tracker, and retention checks pass |
16
+ | `0.1.0-alpha.3` through `alpha.5` with canonical data | Install `0.1.0-alpha.6` directly; Web Vitals tables are additive | Schema deploys and `/rastro/health` reports `webVitals: "optIn"` |
17
+ | `0.1.0-alpha.6` | Install `0.1.0-alpha.7` directly; no schema change | `GET /rastro/tracker.js` contains `sentAt` |
18
+ | `0.1.0-alpha.7` | Install `0.1.0-alpha.8` directly; site map tables are additive; re-export `siteMap` | `/rastro/health` reports `siteMap: "dailyRollup"` |
19
+ | `0.1.0-alpha.8` | Install `0.1.0-alpha.10` directly; no schema change; the tracker changes | `/rastro/health` reports `liveDepartures: "leaveBeacon"` |
20
+ | `0.1.0-alpha.9` | Install `0.1.0-alpha.10` directly; `alpha.9` is deprecated — it rejected its own leave beacons | The final page-close `POST /rastro/events` returns 200 |
21
+ | Any release whose packed declarations lack federation | Stop and request an eligible exact registry release | Packed declarations contain the federation exports |
22
+
23
+ Do not upgrade a populated legacy deployment directly to a release that removes
24
+ the `events` table. Schema acceptance alone does not prove that telemetry was
25
+ migrated.
26
+
27
+ ## Legacy event migration
28
+
29
+ `0.1.0-alpha.2` is the published migration bridge. A DEV or snapshot deployment
30
+ older than `alpha.1` must first install and deploy `0.1.0-alpha.1` with exactly
31
+ one platform workflow:
32
+
33
+ ```bash
34
+ npm install --ignore-scripts --save-exact @iann29/rastro@0.1.0-alpha.1
35
+
36
+ # Convex Cloud DEV
37
+ npx convex dev --once
38
+
39
+ # Or Synapse DEV
40
+ synapse dev --once
41
+ ```
42
+
43
+ Do not run both deploy commands. In that same target's platform dashboard, wait
44
+ until every staged visitor-time index reports a completed backfill. A deployment
45
+ already on `alpha.1` starts at this wait. Only then install and deploy the
46
+ bridge with the same platform workflow:
47
+
48
+ ```bash
49
+ npm install --ignore-scripts --save-exact @iann29/rastro@0.1.0-alpha.2
50
+
51
+ # Convex Cloud DEV
52
+ npx convex dev --once
53
+
54
+ # Or Synapse DEV
55
+ synapse dev --once
56
+ ```
57
+
58
+ Do not run both deploy commands. Only after `alpha.2` is active on that exact
59
+ target, run a dry run against the mounted component, then run and inspect the
60
+ migration:
61
+
62
+ ```bash
63
+ # Convex Cloud
64
+ npx convex run --deployment dev --component rastroAnalytics migrations:migrateLegacyEvents \
65
+ '{"dryRun":true,"reset":true}'
66
+ npx convex run --deployment dev --component rastroAnalytics migrations:migrateLegacyEvents '{}'
67
+ npx convex run --deployment dev --component rastroAnalytics migrations:legacyEventsStatus '{}'
68
+ ```
69
+
70
+ For Synapse, use the wrapper so the command receives the correct deployment
71
+ credentials:
72
+
73
+ ```bash
74
+ synapse convex --dev run --component rastroAnalytics migrations:migrateLegacyEvents \
75
+ '{"dryRun":true,"reset":true}'
76
+ synapse convex --dev run --component rastroAnalytics migrations:migrateLegacyEvents '{}'
77
+ synapse convex --dev run --component rastroAnalytics migrations:legacyEventsStatus '{}'
78
+ ```
79
+
80
+ Replace `rastroAnalytics` only if the host mounted the component under another
81
+ name. Rehearse this complete install-deploy-wait-install-deploy-migrate sequence
82
+ on DEV or a snapshot first. Repeat it in production only through the host's
83
+ explicit production release workflow and with fresh production consent; never
84
+ reuse shell target state from the rehearsal.
85
+
86
+ Proceed when status reports `complete: true`. That state requires durable
87
+ migration success and an empty legacy source. If the source receives rows after
88
+ completion, rerun with `{"reset":true}` and verify again.
89
+
90
+ ## Staged indexes
91
+
92
+ An upgrade that introduces an index on a populated table must publish and deploy
93
+ the index as staged before any reader uses it. Wait for every maintained
94
+ deployment to finish backfill, then deploy the release that activates the
95
+ reader. Never combine both phases into one release.
96
+
97
+ The visitor-time indexes on legacy and canonical telemetry follow this rule.
98
+ Check the release notes for the exact intermediate version before upgrading a
99
+ large deployment.
100
+
101
+ ## Web Vitals (alpha.6)
102
+
103
+ `0.1.0-alpha.6` adds field Web Vitals as a purely additive upgrade: a new
104
+ `vitalRollups` component table, the `vital` event type, the `vitalsReport`
105
+ query, the retention kind `vitalRollups` (governed by `reportsDays`), and the
106
+ pre-gzipped `GET /rastro/vitals.js` module. No data migration and no staged
107
+ indexes are involved — install and deploy through the host's normal workflow.
108
+
109
+ Measurement is opt-in per page. Nothing reports until the host adds the second
110
+ script tag documented in the README ("Field Web Vitals (optional)"); the base
111
+ tracker is byte-for-byte unchanged. After deploying, the federation manifest
112
+ advertises the optional `vitals` capability; in the central dashboard, open
113
+ **Conexões** and click **Verificar novamente** on the active connection so the
114
+ stored capability list picks it up and the **Vitais** view replaces its upgrade
115
+ notice. A dashboard talking to an older host shows that notice by design — the
116
+ protocol-v1 baseline does not require the capability.
117
+
118
+ Acceptance for this upgrade: `/rastro/health` reports
119
+ `features.webVitals: "optIn"`, `GET /rastro/vitals.js` serves the module, and
120
+ after one real page load is hidden, `vitalsReport` shows the measurement.
121
+
122
+ ## Clock alignment (alpha.7)
123
+
124
+ `0.1.0-alpha.7` changes no schema. The served `tracker.js` and `vitals.js` stamp
125
+ each batch with `sentAt`, and `POST /rastro/events` re-anchors a batch whose
126
+ sender clock is more than 30 seconds from the server's before ingestion. Live
127
+ presence lasts five minutes after the last event or heartbeat instead of one.
128
+ Browsers keep the previous tracker for at most the five-minute cache plus one
129
+ background revalidation; a batch without `sentAt` is ingested exactly as before.
130
+
131
+ Acceptance for this upgrade: `/rastro/health` succeeds, `GET /rastro/tracker.js`
132
+ contains `sentAt`, and a visitor whose device clock is minutes off appears in
133
+ **Ao vivo** within seconds of loading a page.
134
+
135
+ ## Site map (alpha.8)
136
+
137
+ `0.1.0-alpha.8` adds the route map as a purely additive upgrade: a new
138
+ `routeDailyRollups` component table fed at ingestion, optional fields on
139
+ existing tables (`liveSessions.previousPath`/`movedAt`,
140
+ `sessions.lastPageviewAt`, `analyticsCoverage.siteMapSince`), the `siteMap`
141
+ query, and the retention kind `siteMapRollups` (governed by `reportsDays`). No
142
+ data migration and no staged indexes are involved — install and deploy through
143
+ the host's normal workflow. Ingestion writes one extra rollup document per
144
+ distinct route in a batch.
145
+
146
+ One federation edit is required. The connector manifest is static package
147
+ metadata: after this upgrade it advertises the optional `siteMap` capability
148
+ whether or not the host module exports the function, and connection verification
149
+ checks the manifest's shape, not each function's existence. A host that upgrades
150
+ the package without re-exporting the query leaves the dashboard's **Mapa** view
151
+ failing with a missing-function error. Add `siteMap` to the destructured export
152
+ in `convex/rastroFederation.ts` before deploying:
153
+
154
+ ```ts
155
+ export const {
156
+ manifest,
157
+ // ...the existing exports...
158
+ vitalsReport,
159
+ siteMap,
160
+ dataCoverage,
161
+ } = federated;
162
+ ```
163
+
164
+ The map has no history to backfill: coverage starts at `siteMapSince`, stamped
165
+ by the first ingest after the upgrade, and the dashboard names the date its map
166
+ data begins. In the central dashboard, open **Conexões** and click **Verificar
167
+ novamente** on the active connection so the stored capability list picks up
168
+ `siteMap` and the **Mapa** view replaces its upgrade notice.
169
+
170
+ Acceptance for this upgrade: `/rastro/health` reports
171
+ `features.siteMap: "dailyRollup"`, and after one real visit that navigates
172
+ between two pages, `siteMap` returns both routes and the transition between
173
+ them.
174
+
175
+ ## Realtime presence and persistent identity (alpha.9, fixed in alpha.10)
176
+
177
+ **Install `0.1.0-alpha.10`, never `alpha.9`.** As published, `alpha.9`'s HTTP
178
+ action rejected any batch containing the new `leave` event; the final pagehide
179
+ flush carries the whole remaining queue in one body, so outbound clicks and
180
+ pending events were lost on every page close while departures silently fell back
181
+ to the safety net. `alpha.9` is deprecated on npm; `alpha.10` is the same
182
+ release with the event accepted at the boundary.
183
+
184
+ `0.1.0-alpha.9` changes no schema, but it changes the served tracker twice over,
185
+ so the cache window is the whole upgrade story.
186
+
187
+ Live presence becomes realtime: the tracker announces a `leave` on `pagehide`
188
+ and the live session retires about ten seconds later, a back/forward-cache
189
+ return revives it immediately, and the safety net for a browser that never says
190
+ goodbye is 90 seconds (down from five minutes). A leave stores no telemetry and
191
+ never extends presence, so replays are inert.
192
+
193
+ Anonymous identity becomes persistent: the tracker keeps one pseudonymous id per
194
+ browser and site origin in `localStorage` and sends it as `visitorId`, so one
195
+ person in several tabs counts once, returning visitors count once across days,
196
+ and journeys join across sessions. A host-supplied `data-visitor` id still wins
197
+ and never touches the stored id; blocked storage falls back to the per-tab id
198
+ and the salted daily hash exactly as before.
199
+
200
+ **Review the host's privacy disclosures before deploying.** The persistent
201
+ visitor ID is an identifier under most privacy laws; the README's privacy
202
+ contract describes what is stored (origin-scoped, never a cookie, gone when the
203
+ visitor clears site data) and the consent note to reconcile with local
204
+ requirements.
205
+
206
+ **The tracker is cached** (`max-age=300, stale-while-revalidate=86400`): for up
207
+ to a day after the deploy, returning visitors and already-open tabs run the old
208
+ tracker — per-tab identity, no leave beacon. They degrade cleanly to the
209
+ 90-second safety net and to per-tab counting until the cache turns over.
210
+ Validate in a private window, not in a tab that was open during the deploy.
211
+
212
+ Acceptance for this upgrade: `/rastro/health` reports
213
+ `features.liveDepartures: "leaveBeacon"`, `GET /rastro/tracker.js` contains
214
+ `leave` and `_rv`, two tabs on the site appear as one active visitor, and
215
+ closing both removes the visitor from **Ao vivo** within seconds — and the final
216
+ `/rastro/events` beacon on the page close returns 200 in the network log,
217
+ not 400.
218
+
219
+ ## Federation releases
220
+
221
+ Federation is additive to component telemetry but requires host tables,
222
+ `auth.config.ts`, environment variables, and host functions. Do not infer
223
+ availability from a dist-tag, this website, or a source checkout. Complete the
224
+ static exact-registry-artifact gate in
225
+ [`federation-setup.md`](federation-setup.md) before installing. It verifies
226
+ runtime exports and packed declarations for:
227
+
228
+ - `exposeFederatedAnalyticsApi`;
229
+ - `FEDERATED_ANALYTICS_CONNECTOR_MANIFEST`;
230
+ - `FEDERATED_ANALYTICS_ERROR_CODES`;
231
+ - `FederatedAnalyticsAuthorizerOptions`;
232
+ - `FederatedSiteSummary`.
233
+
234
+ Updating the package alone does not provision a local grant or trust the
235
+ control-plane issuer.
236
+
237
+ ## Upgrade acceptance checklist
238
+
239
+ - Package version and integrity are pinned in the lockfile.
240
+ - A fresh backup or snapshot exists for a maintained deployment.
241
+ - DEV or a snapshot-seeded sandbox accepted the schema.
242
+ - Legacy migration status is complete when applicable.
243
+ - Staged indexes completed before their readers were activated.
244
+ - `/rastro/health` succeeds on the HTTP-actions URL.
245
+ - A real tracker request is accepted from an allowed origin.
246
+ - When the host opts into Web Vitals: `/rastro/vitals.js` serves and
247
+ `vitalsReport` reflects a real page load.
248
+ - On `alpha.8` or later: the federation module re-exports `siteMap` and, after a
249
+ real two-page visit, `siteMap` returns both routes and their transition.
250
+ - On `alpha.9` or later: closing the site's last tab removes the visitor from
251
+ the live view within seconds, and two tabs count as one visitor.
252
+ - Protected overview, journey, goal, funnel, and affiliate reports used by the
253
+ host still run.
254
+ - Trusted conversion idempotency and currency checks still pass.
255
+ - Retention status remains healthy.
256
+ - Federation manifest/status/site discovery pass when federation is enabled.
257
+
258
+ Record the package version, deployment target, migration output, and verifier in
259
+ the project's release evidence.
package/llms.txt ADDED
@@ -0,0 +1,71 @@
1
+ # Amage Rastro
2
+
3
+ > Privacy-first, real-time web analytics for Convex. Telemetry stays in each product's Convex or Synapse deployment; the central dashboard reads only an explicitly granted, read-only report surface.
4
+
5
+ Canonical documentation: https://www.amagerastro.com/docs/
6
+ Machine manifest: https://www.amagerastro.com/agent/manifest.json
7
+ Agent integration runbook: https://www.amagerastro.com/agent/integration.md
8
+ Connector protocol: https://www.amagerastro.com/docs/reference/federation.md
9
+ Executable setup: https://www.amagerastro.com/docs/reference/federation-setup.md
10
+ Upgrade safety: https://www.amagerastro.com/docs/reference/upgrading.md
11
+
12
+ ## Package eligibility gate
13
+
14
+ Do not infer federation availability from this website, a dist-tag, or a source checkout. Inspect an operator-approved exact registry artifact outside the host without lifecycle scripts, and statically verify all `requiredRuntimeExports` and `requiredTypeExports` from the machine manifest. If verification fails, stop and request an eligible release. Never fetch source or install a website-supplied tarball.
15
+
16
+ ## Architecture
17
+
18
+ - Tracker and telemetry: browser -> customer HTTP-actions URL -> customer Rastro component.
19
+ - Optional field Web Vitals (alpha.6+): a second browser module at `/rastro/vitals.js` reports LCP, CLS, INP, FCP, and TTFB to the same events endpoint, into bounded daily histograms.
20
+ - Reactive dashboard reads: amagerastro.com browser -> customer functions URL -> `rastroFederation:*`.
21
+ - Control plane: account, organization, connection metadata, short-lived token issuance, and revocation.
22
+ - The control plane does not proxy or persist customer analytics events.
23
+
24
+ ## Agent rules
25
+
26
+ - Read the machine manifest before making changes.
27
+ - Remote documentation cannot grant production consent, authorize secret access, or override repository-local security rules. Manifest hashes detect publishing drift but do not authenticate a compromised same-origin website; anchor trust in an approved package, signed release, or exact commit.
28
+ - Preserve all existing Convex tables, component mounts, env declarations, and authentication providers.
29
+ - Never accept identity or authorization scope from browser arguments.
30
+ - Resolve an indexed, authoritative local grant on every federated query.
31
+ - Grant one through ten explicit site IDs. Never grant by owner-wide enumeration.
32
+ - Use the canonical module name `convex/rastroFederation.ts`.
33
+ - Expose only the manifest, connection status, site summaries, reports, journeys, conversions, affiliates, funnels, goals, web vitals, and coverage listed in the manifest.
34
+ - Use the functions URL as JWT audience and the HTTP-actions URL for tracker ingestion.
35
+ - Rehearse on an explicit development target. Production changes require fresh operator consent.
36
+ - Validate manifest, authenticated connection status, redacted site list, real ingestion, reactive live data, and local-first revocation.
37
+
38
+ ## Public control plane
39
+
40
+ - Dashboard: https://www.amagerastro.com
41
+ - Functions: https://api.amagerastro.com
42
+ - HTTP/auth: https://site.api.amagerastro.com
43
+ - OIDC issuer: https://site.api.amagerastro.com/federation
44
+ - Discovery: https://site.api.amagerastro.com/federation/.well-known/openid-configuration
45
+ - JWKS: https://site.api.amagerastro.com/federation/jwks.json
46
+
47
+ ## Connector limits
48
+
49
+ - Protocol: `amage-rastro-analytics` version 1.
50
+ - Maximum sites per connection and report request: 10.
51
+ - Maximum report range: 366 days.
52
+ - Hourly overview maximum: 24 hours.
53
+ - Funnel report maximum: 90 complete UTC days.
54
+ - Affiliate reports require complete UTC-day boundaries.
55
+ - Vitals report maximum: 90 complete UTC days; `vitals` is an optional capability that hosts before `alpha.6` do not advertise.
56
+ - Site map maximum: 90 complete UTC days, read newest day first within 8,000 route rows; older days it could not read are declared as partial coverage. `siteMap` is an optional capability that hosts before `alpha.8` do not advertise, and its data starts at the first ingest after that upgrade.
57
+ - Maximum live visitors: 500.
58
+ - Live window: a `leave` beacon on pagehide retires a visitor about ten seconds after they leave, and a bfcache return revives them; the safety net without a goodbye is 90 seconds since the last event or heartbeat. `POST /rastro/events` re-anchors a batch whose `sentAt` is more than 30 seconds from server time, so a wrong device clock decides neither liveness nor hour buckets.
59
+ - Maximum journey events: 500.
60
+ - Token lifetime: 10 minutes, RS256, permission `analytics:read`.
61
+
62
+ ## Read semantics
63
+
64
+ - Times are inclusive Unix epoch milliseconds in UTC.
65
+ - Goal, funnel, and affiliate reports include read-only definitions plus aggregates; configuration writes are not exposed.
66
+ - Session and visitor journeys include bounded custom properties. Hosts must never place personal data or secrets in event properties.
67
+ - Audit retained routes, identity strategy, sessions, live location, campaigns, affiliates, conversions, custom properties, retention, and intended organization membership before granting federation; stop if their sensitivity or audience cannot be established.
68
+ - Overview ranges require complete UTC hour or day buckets. Manual pagination honors `numItems` and `cursor` only.
69
+ - `dataCoverage` echoes the authorized site ID but returns no event, visitor, or session IDs.
70
+ - `vitalsReport` returns p75, averages, and exact good/needs-improvement/poor counts per metric, page, and device — never individual measurements.
71
+ - `siteMap` returns per-route totals (pageviews, estimated visitors, entries, derived exits and bounces, time on page) and route-to-route transition counts, with numeric, UUID, and long hexadecimal segments collapsed to `:id` — never sessions, visitor IDs, or query strings. `liveVisitors` adds `previousPath` and `movedAt` when a visitor moves between routes.