@iann29/rastro 0.1.0-alpha.4 → 0.1.0-alpha.6

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 (135) hide show
  1. package/README.md +203 -56
  2. package/agent/integration.md +17 -15
  3. package/agent/manifest.json +6 -4
  4. package/agent/manifest.schema.json +8 -5
  5. package/dist/client/federation.d.ts +34 -8
  6. package/dist/client/federation.d.ts.map +1 -1
  7. package/dist/client/federation.js +17 -3
  8. package/dist/client/federation.js.map +1 -1
  9. package/dist/client/index.d.ts +486 -31
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +209 -41
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/component/_generated/api.d.ts +8 -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 +82 -8
  17. package/dist/component/_generated/component.d.ts.map +1 -1
  18. package/dist/component/affiliates.d.ts.map +1 -1
  19. package/dist/component/affiliates.js +6 -2
  20. package/dist/component/affiliates.js.map +1 -1
  21. package/dist/component/cardinality.d.ts.map +1 -1
  22. package/dist/component/cardinality.js.map +1 -1
  23. package/dist/component/constants.d.ts +6 -0
  24. package/dist/component/constants.d.ts.map +1 -1
  25. package/dist/component/constants.js +12 -0
  26. package/dist/component/constants.js.map +1 -1
  27. package/dist/component/diagnostics.d.ts.map +1 -1
  28. package/dist/component/diagnostics.js.map +1 -1
  29. package/dist/component/eventStore.d.ts +3 -1
  30. package/dist/component/eventStore.d.ts.map +1 -1
  31. package/dist/component/eventStore.js +13 -10
  32. package/dist/component/eventStore.js.map +1 -1
  33. package/dist/component/funnels.d.ts.map +1 -1
  34. package/dist/component/funnels.js +5 -3
  35. package/dist/component/funnels.js.map +1 -1
  36. package/dist/component/geo.d.ts.map +1 -1
  37. package/dist/component/geo.js +3 -2
  38. package/dist/component/geo.js.map +1 -1
  39. package/dist/component/goals.d.ts.map +1 -1
  40. package/dist/component/goals.js +8 -5
  41. package/dist/component/goals.js.map +1 -1
  42. package/dist/component/guards.d.ts.map +1 -1
  43. package/dist/component/guards.js.map +1 -1
  44. package/dist/component/http.d.ts.map +1 -1
  45. package/dist/component/http.js +71 -37
  46. package/dist/component/http.js.map +1 -1
  47. package/dist/component/identity.d.ts +13 -0
  48. package/dist/component/identity.d.ts.map +1 -0
  49. package/dist/component/identity.js +58 -0
  50. package/dist/component/identity.js.map +1 -0
  51. package/dist/component/ingest.d.ts +3 -1
  52. package/dist/component/ingest.d.ts.map +1 -1
  53. package/dist/component/ingest.js +168 -29
  54. package/dist/component/ingest.js.map +1 -1
  55. package/dist/component/live.d.ts.map +1 -1
  56. package/dist/component/live.js +1 -3
  57. package/dist/component/live.js.map +1 -1
  58. package/dist/component/reports.d.ts +70 -4
  59. package/dist/component/reports.d.ts.map +1 -1
  60. package/dist/component/reports.js +232 -59
  61. package/dist/component/reports.js.map +1 -1
  62. package/dist/component/retention.d.ts +4 -4
  63. package/dist/component/retention.d.ts.map +1 -1
  64. package/dist/component/retention.js +54 -22
  65. package/dist/component/retention.js.map +1 -1
  66. package/dist/component/sanitize.d.ts.map +1 -1
  67. package/dist/component/sanitize.js +42 -11
  68. package/dist/component/sanitize.js.map +1 -1
  69. package/dist/component/schema.d.ts +83 -10
  70. package/dist/component/schema.js +34 -1
  71. package/dist/component/schema.js.map +1 -1
  72. package/dist/component/sites.d.ts.map +1 -1
  73. package/dist/component/sites.js +6 -6
  74. package/dist/component/sites.js.map +1 -1
  75. package/dist/component/useragent.d.ts +9 -0
  76. package/dist/component/useragent.d.ts.map +1 -0
  77. package/dist/component/useragent.js +152 -0
  78. package/dist/component/useragent.js.map +1 -0
  79. package/dist/component/validators.d.ts +29 -18
  80. package/dist/component/validators.d.ts.map +1 -1
  81. package/dist/component/validators.js +8 -2
  82. package/dist/component/validators.js.map +1 -1
  83. package/dist/component/visitors.d.ts +19 -0
  84. package/dist/component/visitors.d.ts.map +1 -0
  85. package/dist/component/visitors.js +86 -0
  86. package/dist/component/visitors.js.map +1 -0
  87. package/dist/component/vitals.d.ts +41 -0
  88. package/dist/component/vitals.d.ts.map +1 -0
  89. package/dist/component/vitals.js +115 -0
  90. package/dist/component/vitals.js.map +1 -0
  91. package/dist/react/index.d.ts.map +1 -1
  92. package/dist/react/index.js.map +1 -1
  93. package/dist/tracker/generated.d.ts +8 -4
  94. package/dist/tracker/generated.d.ts.map +1 -1
  95. package/dist/tracker/generated.js +8 -4
  96. package/dist/tracker/generated.js.map +1 -1
  97. package/dist/tracker/tracker.d.ts.map +1 -1
  98. package/dist/tracker/tracker.js +23 -11
  99. package/dist/tracker/tracker.js.map +1 -1
  100. package/dist/tracker/vitals.d.ts +10 -0
  101. package/dist/tracker/vitals.d.ts.map +1 -0
  102. package/dist/tracker/vitals.js +137 -0
  103. package/dist/tracker/vitals.js.map +1 -0
  104. package/dist/tracker.min.js +1 -1
  105. package/dist/vitals.min.js +1 -0
  106. package/docs/benchmarks/2026-08-20-realistic.md +71 -71
  107. package/docs/benchmarks/2026-08-21-formal-certification.md +50 -30
  108. package/docs/benchmarks/2026-08-30-alpha6-recertification.md +206 -0
  109. package/docs/federation.md +1 -1
  110. package/package.json +27 -9
  111. package/scripts/benchmark-ingest.mjs +101 -48
  112. package/src/component/_generated/api.ts +8 -0
  113. package/src/component/_generated/component.ts +87 -5
  114. package/src/component/affiliates.ts +20 -5
  115. package/src/component/cardinality.ts +8 -7
  116. package/src/component/constants.ts +12 -0
  117. package/src/component/diagnostics.ts +3 -2
  118. package/src/component/eventStore.ts +53 -46
  119. package/src/component/funnels.ts +19 -16
  120. package/src/component/geo.ts +16 -14
  121. package/src/component/goals.ts +29 -21
  122. package/src/component/guards.ts +3 -1
  123. package/src/component/http.ts +126 -78
  124. package/src/component/identity.ts +74 -0
  125. package/src/component/ingest.ts +349 -130
  126. package/src/component/live.ts +5 -4
  127. package/src/component/reports.ts +572 -221
  128. package/src/component/retention.ts +221 -98
  129. package/src/component/sanitize.ts +77 -27
  130. package/src/component/schema.ts +46 -5
  131. package/src/component/sites.ts +22 -11
  132. package/src/component/useragent.ts +171 -0
  133. package/src/component/validators.ts +19 -7
  134. package/src/component/visitors.ts +116 -0
  135. package/src/component/vitals.ts +146 -0
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Privacy-first, real-time web analytics built as an isolated Convex component.
6
6
 
7
- Amage Rastro combines a 1,000-byte gzip browser tracker with reactive traffic
7
+ Amage Rastro combines a 1.1 KB gzip browser tracker with reactive traffic
8
8
  metrics, a live visitor atlas, journey timelines, trusted payment attribution,
9
9
  goals, funnels, affiliates, and multi-site reports. Data and functions stay
10
10
  inside your Convex deployment.
@@ -27,26 +27,32 @@ Public integration documentation is available at
27
27
  should start with [`llms.txt`](https://www.amagerastro.com/llms.txt) and verify
28
28
  the [machine manifest](https://www.amagerastro.com/agent/manifest.json).
29
29
 
30
- Current evidence supports more than 120 million stored events/day for realistic
31
- traffic. Near-limit heavy payloads are certified at 76.62 million/day, and the
32
- feature-saturated profile at 33.22 million/day. Workload-specific results and
33
- caveats are reported separately rather than collapsed into one capacity claim.
30
+ Current evidence supports more than 140 million stored events/day for realistic
31
+ traffic and more than 178 million for lean traffic. Near-limit heavy payloads
32
+ are certified at 55.26 million/day on a data-heavy store (76.62 million/day on a
33
+ fresh store, 2026-08-22). The feature-saturated profile currently fails
34
+ certification on a known rollup-contention defect
35
+ ([#52](https://github.com/amageweb/amage-rastro/issues/52)). Workload-specific
36
+ results and caveats are reported separately rather than collapsed into one
37
+ capacity claim.
34
38
 
35
39
  ## What ships
36
40
 
37
- | Capability | Implementation |
38
- | --------------------------- | ---------------------------------------------------------------------------------------------------- |
39
- | Live visitor map | Reactive live sessions with coarse city-level coordinates and TTL expiry |
40
- | Traffic analytics | Sharded hour/day rollups, time series, top pages, sources, countries, devices, browsers, and events |
41
- | Journey replay | Bounded pageview/click/custom-event timelines; no DOM, keystroke, or form-value recording |
42
- | Payment conversion tracking | Trusted server-side ledger with idempotent event IDs and canonical site currency |
43
- | Goals and funnels | Exact pageview/custom-event goals and ordered conversion funnels |
44
- | Affiliates | Referral attribution, trusted revenue, and commission accounting |
45
- | Cross-site analytics | Reports over up to ten authorized site IDs with a preloaded pseudonymous visitor ID |
46
- | Automatic GeoIP | Opt-in native request metadata plus IPinfo/IPWhois fallback; bounded quota and no raw-IP persistence |
47
- | Auto-captured clicks | Anchors, buttons, outbound links, and explicit `data-rastro-event` controls |
48
- | Cookieless mode | Session-scoped pseudonymous IDs in `sessionStorage`; no cookies |
49
- | Tiny tracker | 1,846 bytes raw, 1,000 bytes gzip; the test suite enforces `< 1,024` bytes |
41
+ | Capability | Implementation |
42
+ | --------------------------- | -------------------------------------------------------------------------------------------------------- |
43
+ | Live visitor map | Reactive live sessions with coarse city-level coordinates and TTL expiry |
44
+ | Traffic analytics | Sharded hour/day rollups, time series, top pages, sources, countries, devices, browsers, and events |
45
+ | Field Web Vitals | Opt-in tracker module reporting LCP, CLS, INP, FCP, and TTFB into bounded per-page daily histograms |
46
+ | Journey replay | Bounded pageview/click/custom-event timelines; no DOM, keystroke, or form-value recording |
47
+ | Payment conversion tracking | Trusted server-side ledger with idempotent event IDs and canonical site currency |
48
+ | Goals and funnels | Exact pageview/custom-event goals and ordered conversion funnels |
49
+ | Affiliates | Referral attribution, trusted revenue, and commission accounting |
50
+ | Cross-site analytics | Reports over up to ten authorized site IDs with a preloaded pseudonymous visitor ID |
51
+ | Automatic GeoIP | Opt-in native request metadata plus IPinfo/IPWhois fallback; bounded quota and no raw-IP persistence |
52
+ | Auto-captured clicks | Anchors, buttons, and `data-rastro-event` controls with a bounded visible label; outbound origin+path |
53
+ | Bot filtering | Self-declared crawlers, unfurlers, monitors, HTTP libraries, and headless browsers rejected at ingestion |
54
+ | Cookieless mode | Session-scoped pseudonymous IDs in `sessionStorage`; no cookies |
55
+ | Tiny tracker | 2,132 bytes raw, 1,127 bytes gzip; the test suite enforces `< 1,280` bytes gzip |
50
56
 
51
57
  ## Quickstart
52
58
 
@@ -89,11 +95,12 @@ app.use(rastro, { httpPrefix: "/rastro/" });
89
95
  export default app;
90
96
  ```
91
97
 
92
- This creates isolated component tables and four HTTP routes:
98
+ This creates isolated component tables and five HTTP routes:
93
99
 
94
100
  - `POST /rastro/events` — browser event ingestion
95
101
  - `OPTIONS /rastro/events` — CORS preflight
96
102
  - `GET /rastro/tracker.js` — the pre-gzipped tracker
103
+ - `GET /rastro/vitals.js` — the pre-gzipped, opt-in Web Vitals module
97
104
  - `GET /rastro/health` — integration health
98
105
 
99
106
  Push the component and regenerate host bindings before importing
@@ -175,6 +182,40 @@ the internal `geo:probe` action. If health reports no usable client IP, use
175
182
  trusted Vercel/Cloudflare geographic headers with `RASTRO_TRUST_PROXY` or fix
176
183
  the deployment's proxy forwarding.
177
184
 
185
+ ### Browser and device classification
186
+
187
+ The tracker sends no client identification. Ingestion derives `browser`, `os`,
188
+ and `device` from headers the browser attaches itself, and persists only those
189
+ three normalized values; the raw `User-Agent` never reaches a mutation or a
190
+ component table.
191
+
192
+ Chromium attaches the low-entropy `Sec-CH-UA`, `Sec-CH-UA-Mobile`, and
193
+ `Sec-CH-UA-Platform` client hints to cross-origin requests from secure origins,
194
+ with no `Accept-CH` opt-in required of the tracked site. They are preferred when
195
+ present, because they are the only signal separating Brave, Opera, Vivaldi,
196
+ Samsung Internet, Yandex, and bare Chromium from Chrome: Brave ships Chrome's
197
+ `User-Agent` verbatim by design. Hint brands are allowlisted, so an unrecognized
198
+ or forged brand falls back to the `User-Agent` instead of entering the browser
199
+ dimension verbatim.
200
+
201
+ The `User-Agent` remains the fallback for Firefox and Safari, which implement no
202
+ client hints, and for insecure origins, which receive none. Known limits of that
203
+ path:
204
+
205
+ - iPadOS 13+ requests sites with a Macintosh `User-Agent` by default and Safari
206
+ sends no hints, so those sessions are reported as desktop macOS.
207
+ - Without hints every Android client is reported as mobile;
208
+ `Sec-CH-UA-Mobile: ?0` on the Android platform is the only available tablet
209
+ signal, because the explicit `Sec-CH-UA-Form-Factors` hint is high entropy and
210
+ would require an `Accept-CH` opt-in and a permissions-policy delegation from
211
+ every tracked site.
212
+ - Known bots are rejected at ingestion by `User-Agent`: self-declared crawlers,
213
+ link unfurlers, uptime monitors, HTTP libraries, headless browsers, and any
214
+ agent carrying a URL. The response keeps the usual counters with `rejected`
215
+ set and adds `X-Rastro-Rejected: bot`; overview metadata reports
216
+ `botDetection` as `userAgent`. A scripted browser that spoofs a stock
217
+ `User-Agent` is still counted as an ordinary session.
218
+
178
219
  ### 3. Create a site
179
220
 
180
221
  Components cannot read the host app's authentication state. The host must
@@ -264,6 +305,29 @@ serves the script and ingestion from different origins:
264
305
  ></script>
265
306
  ```
266
307
 
308
+ ### Field Web Vitals (optional)
309
+
310
+ A second, separate module measures real-visitor LCP, CLS, INP, FCP, and TTFB
311
+ with `PerformanceObserver` and reports them once per page load, when the tab is
312
+ first hidden. It stays out of `tracker.js` so the base tracker keeps its byte
313
+ budget; add it only where you want performance data:
314
+
315
+ ```html
316
+ <script
317
+ defer
318
+ data-site="YOUR_SITE_ID"
319
+ src="https://YOUR_DEPLOYMENT.convex.site/rastro/vitals.js"
320
+ ></script>
321
+ ```
322
+
323
+ The module shares the tracker's session identity through `sessionStorage`,
324
+ accepts the same `data-endpoint` and `data-visitor` overrides, and works with or
325
+ without the base tracker on the page. Measurements are attributed to the landing
326
+ path of the page load, never to later SPA routes. Vitals feed only the
327
+ `vitalsReport` histograms — analytics event counts, sessions, and live presence
328
+ do not change. Browsers without an entry type simply skip that metric (Safari
329
+ reports no LCP, CLS, or INP).
330
+
267
331
  ### React
268
332
 
269
333
  React is an optional peer dependency. It is required only when importing the
@@ -332,6 +396,12 @@ For declarative click capture:
332
396
  </button>
333
397
  ```
334
398
 
399
+ Auto-captured clicks record the control's visible text, bounded to 64 characters
400
+ and whitespace-normalized, so journeys read "Clicked «View pricing»" without
401
+ annotation. `data-rastro-label` replaces that text; use it on controls whose
402
+ copy may contain personal data. Input values are never read. Outbound links keep
403
+ their origin and pathname; internal links keep the pathname.
404
+
335
405
  Properties accept bounded string, number, boolean, or null values. Never send
336
406
  emails, names, form values, secrets, or other personal data as analytics
337
407
  properties.
@@ -342,6 +412,46 @@ session or with a timestamp in another minute is allowed. The tracker preserves
342
412
  timestamps when retrying. Trusted payment event IDs remain separately
343
413
  payload-bound by the site's financial ledger.
344
414
 
415
+ ## Attribute campaigns
416
+
417
+ The tracker reads `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, and
418
+ `utm_content` from the landing URL, keeps them in `sessionStorage` for the rest
419
+ of the session, and sends them as properties of every pageview; no other query
420
+ parameter ever leaves the page. A session's `source` is the lowercase
421
+ `utm_source` when present and the referrer host otherwise, so campaign traffic
422
+ no longer reads as `direct`. `utmSource`, `utmMedium`, and `utmCampaign` stay on
423
+ the session for journeys, and `overview.topCampaigns` ranks `utm_campaign` by
424
+ event volume next to `topSources`.
425
+
426
+ ## Identify visitors after signup
427
+
428
+ Anonymous sessions carry a random `visitorId` until the host supplies a
429
+ pseudonymous one through `data-visitor` or the `RastroScript` `visitorId` prop.
430
+ To keep the anonymous history when a visitor creates an account or signs in,
431
+ link the two ids from the trusted server path, never from the browser:
432
+
433
+ ```js
434
+ const { visitorId: anonymousVisitorId } = window.rastro("context");
435
+ // Send anonymousVisitorId with the signup or sign-in request.
436
+ ```
437
+
438
+ ```ts
439
+ await analytics.linkVisitor(ctx, {
440
+ siteId,
441
+ visitorId: user.analyticsId, // the value later passed to the tracker
442
+ previousVisitorId: anonymousVisitorId,
443
+ });
444
+ ```
445
+
446
+ `visitorJourney` for either id then returns one chronological journey; every
447
+ event keeps the id it was recorded with. Links are idempotent, an anonymous id
448
+ belongs to at most one identity, a linked id cannot be linked again, chains and
449
+ identity merges are rejected, and an identity holds at most ten aliases. The
450
+ tracker still sets no cookie: the host's own session identifies the account and
451
+ the anonymous id lives only in `sessionStorage`. Linking anonymous history to an
452
+ account turns it into personal data under the host's privacy policy; disclose it
453
+ and offer an opt-out.
454
+
345
455
  ## Attribute trusted payments
346
456
 
347
457
  Browser-created conversion events are untrusted telemetry and never affect
@@ -428,8 +538,8 @@ functions.
428
538
 
429
539
  ### Federated dashboard backend
430
540
 
431
- The reference control plane in `example/` can connect a central dashboard while
432
- telemetry remains in the customer's deployment. It uses
541
+ The reference control plane in `control-plane/` can connect a central dashboard
542
+ while telemetry remains in the customer's deployment. It uses
433
543
  `exposeFederatedAnalyticsApi` with issuer and connection-resolver options. The
434
544
  helper constructs its authorization boundary internally; it cannot accept a
435
545
  permissive host authorizer. Its surface contains a public protocol v1 manifest,
@@ -455,7 +565,7 @@ runbook.
455
565
  The `Rastro` class wraps the component boundary for host functions:
456
566
 
457
567
  - Sites: `createSite`, `updateSite`, `getSite`, `listSites`
458
- - Ingestion: `ingest`, `trackConversion`
568
+ - Ingestion: `ingest`, `trackConversion`, `linkVisitor`
459
569
  - Reports: `overview`, `liveVisitors`, `listSessions`, `sessionJourney`,
460
570
  `listConversions`, `visitorJourney`, `goalsReport`, `funnelsReport`,
461
571
  `affiliatesReport`, `dataCoverage`
@@ -472,10 +582,15 @@ cost stays constant instead of increasing with an offset.
472
582
 
473
583
  Overview responses label visitor values as either HLL-estimated unique visitors
474
584
  or legacy `bucketOccurrences`; legacy aggregate rows are never presented as
475
- exact uniques. Breakdown metadata likewise identifies page, source, country,
476
- device, browser, and event rankings as event-volume counts. Feature status is
477
- explicit: outbound-link event volume is supported, while session replay, error
478
- insights, and bot detection are currently unsupported.
585
+ exact uniques. Anonymous sessions from the same device and network within one
586
+ UTC day count as one visitor through a salted daily key, so cookieless visitor
587
+ counts no longer collapse into session counts; runtimes without a client IP fall
588
+ back to one visitor per session, which `/rastro/health` reports as
589
+ `uniqueVisitors: "session"` instead of `"dailyHash"`. Breakdown metadata
590
+ likewise identifies page, source, country, device, browser, and event rankings
591
+ as event-volume counts. Feature status is explicit: outbound-link event volume
592
+ is supported, bot detection rejects self-declared agents by `User-Agent`, and
593
+ session replay and error insights are currently unsupported.
479
594
 
480
595
  All report ranges use integer Unix milliseconds with inclusive `from` and `to`
481
596
  boundaries. Range metadata reports `boundary: "inclusive"` and
@@ -504,6 +619,10 @@ boundaries use different bucket alignment.
504
619
  - Events per batch: 50
505
620
  - Session-minute groups per batch: 4
506
621
  - Active goals per exact matcher: 1
622
+ - Web Vitals pages per site and UTC day: 48 tracked page rows, then `(other)`;
623
+ device classes are fixed (desktop, mobile, tablet, unknown)
624
+ - Web Vitals report: complete reads bounded to 5,000 rollup rows across at most
625
+ 90 UTC days; wider saturated ranges fail with `REPORT_INCOMPLETE`
507
626
  - Primary aggregate shards: 256 hourly and 128 daily, derived from stable
508
627
  session identity
509
628
  - Funnel and affiliate aggregate shards: 16
@@ -533,31 +652,42 @@ separately. The browser `feature` profile exercises configured goals, funnels,
533
652
  and affiliate attribution; trusted conversions require a separate server-side
534
653
  driver and certification result.
535
654
 
536
- ### Historical ingest evidence requiring recertification
655
+ ### Certified ingest evidence (2026-08-30)
537
656
 
538
657
  | Profile | Workload | Certified stored/day | p95 latency | 100M/day target |
539
658
  | --------- | ------------------------------------------------- | -------------------: | ----------: | --------------- |
540
- | Lean | Minimal events, maximum batches | 136.65M | 4,543 ms | Pass |
541
- | Realistic | Reused sessions, browser mix, context, heartbeats | 120.81M | 4,247 ms | Pass |
542
- | Heavy | 62 KiB requests with bounded custom properties | 76.62M | 3,670 ms | Below target |
543
- | Feature | Goals, funnels, and affiliate attribution | 33.22M | 6,226 ms | Below target |
544
-
545
- Every listed run lasted at least ten minutes, completed with zero request
546
- failures, duplicates, or rejected events, and exactly reconciled the expected
547
- stored events. Results came from one client process against a non-production
548
- Synapse DEV deployment; they are engineering evidence, not a production SLA. The
549
- heavy row is the formal recertification after removing redundant payload
550
- serialization. GeoIP with a provider enabled and trusted server-side conversions
551
- remain separate pending certifications.
552
-
553
- These runs predate the current source-derived coverage and recurring-retention
554
- changes. They remain historical evidence for the named revisions and profiles,
555
- not certification of this uncommitted implementation. Recertify the final code
556
- before presenting any row as current capacity.
557
-
558
- The latest formal results are recorded in
559
- [`docs/benchmarks/2026-08-21-formal-certification.md`](docs/benchmarks/2026-08-21-formal-certification.md).
560
- Earlier development evidence remains available in
659
+ | Lean | Minimal events, maximum batches | 178.95M | 4,167 ms | Pass |
660
+ | Realistic | Reused sessions, browser mix, context, heartbeats | 142.04M | 4,953 ms | Pass |
661
+ | Heavy | 62 KiB requests with bounded custom properties | 55.26M | 7,702 ms | Below target |
662
+ | Feature | Goals, funnels, and affiliate attribution | **failed** | | Defect [#52] |
663
+
664
+ [#52]: https://github.com/amageweb/amage-rastro/issues/52
665
+
666
+ Lean, realistic, and heavy each ran at least ten minutes, completed with zero
667
+ request failures, duplicates, or rejected events, and exactly reconciled the
668
+ expected stored events (the realistic run's reconciliation was re-executed
669
+ exactly after a client-side DNS failure see the report). The realistic row's
670
+ driver session pool is 4,096: at current speed the historical 2,048-session pool
671
+ pushes single sessions past the documented 120 events/minute budget. Results
672
+ came from one client process against the same non-production Synapse DEV
673
+ deployment and site as the 2026-08-21 certification; they are engineering
674
+ evidence, not a production SLA.
675
+
676
+ Two caveats stand deliberately. The heavy row measured a store already holding
677
+ the battery's own multi-million-event history; the 2026-08-22 fresh-store result
678
+ (76.62M/day) remains valid for that state, and neither number overwrites the
679
+ other. The feature profile cannot currently be certified at all: affiliate
680
+ daily-rollup contention loses ~4–5% of batches under concurrent load
681
+ ([#52](https://github.com/amageweb/amage-rastro/issues/52)), so no
682
+ feature-profile capacity is claimed until the fix lands. GeoIP with a provider
683
+ enabled, trusted server-side conversions, and vitals-bearing traffic remain
684
+ separate pending certifications.
685
+
686
+ The full evidence is recorded in
687
+ [`docs/benchmarks/2026-08-30-alpha6-recertification.md`](docs/benchmarks/2026-08-30-alpha6-recertification.md).
688
+ Prior results remain in
689
+ [`docs/benchmarks/2026-08-21-formal-certification.md`](docs/benchmarks/2026-08-21-formal-certification.md)
690
+ and
561
691
  [`docs/benchmarks/2026-08-20-realistic.md`](docs/benchmarks/2026-08-20-realistic.md).
562
692
 
563
693
  From a repository checkout, run a duration-based profile against a confirmed
@@ -637,15 +767,26 @@ Amage Rastro's default tracker:
637
767
  - sets no cookies;
638
768
  - stores a pseudonymous session ID and referral slug in `sessionStorage`;
639
769
  - never persists raw visitor IP addresses in component tables;
770
+ - counts anonymous unique visitors through a salted hash of the request IP and
771
+ `User-Agent` that rotates every UTC day, derived in the HTTP action from a
772
+ per-deployment secret; a host-supplied `visitorId` always takes precedence,
773
+ and the raw IP never reaches a mutation or a table;
774
+ - never persists the raw `User-Agent`, only a normalized browser, operating
775
+ system, and device;
640
776
  - processes an IP only when an edge supplies geography or an operator enables a
641
777
  GeoIP provider, which receives that IP under its own privacy terms;
642
778
  - stores only country, city, and coordinates rounded to one decimal place;
643
- - does not record the DOM, screenshots, keystrokes, form values, or arbitrary
644
- page text;
645
- - captures only bounded paths, explicit properties, button/anchor targets, and
646
- outbound pathnames;
779
+ - does not record the DOM, screenshots, keystrokes, form values, input contents,
780
+ or arbitrary page text;
781
+ - captures only bounded paths, explicit properties, the visible label of a
782
+ clicked link or button (at most 64 characters, replaced by `data-rastro-label`
783
+ when present), and outbound link origins and pathnames;
647
784
  - strips query strings and fragments from stored paths and links.
648
785
 
786
+ The optional Web Vitals module inherits this contract: it reports at most five
787
+ timing numbers per page load (milliseconds, or CLS scaled by 1000) against the
788
+ landing path, and nothing else.
789
+
649
790
  Cookieless does not automatically mean consent-exempt in every jurisdiction.
650
791
  Review your collection purpose, custom properties, identity strategy, retention
651
792
  policy, and local law.
@@ -666,6 +807,10 @@ synapse dev --once
666
807
  npm run dev:frontend
667
808
  ```
668
809
 
810
+ The root `convex.json` targets the control plane in `control-plane/convex`. The
811
+ reference consumer host in `example/` is a separate Convex app; see
812
+ [`example/README.md`](example/README.md) to run, seed, and pair it.
813
+
669
814
  Quality gates:
670
815
 
671
816
  ```bash
@@ -683,11 +828,13 @@ affiliate commission, cross-site journeys, keyset pagination beyond 1,000 rows,
683
828
  retention, request metadata and GeoIP, exact byte boundaries, HTTP routes, React
684
829
  integration, and tracker size/behavior.
685
830
 
686
- ## Synapse demo
831
+ ## Synapse deployments
687
832
 
688
- The active private development project is `ianProjects / Rastro Analytics`. The
689
- public-facing site URL is resolved from `.env.local`; credentials and
690
- `.synapse/` are gitignored.
833
+ The repository root is linked to the private Synapse project whose DEV and PROD
834
+ deployments run the control plane in `control-plane/`. The public-facing site
835
+ URL is resolved from `.env.local`; credentials and `.synapse/` are gitignored.
836
+ The consumer host in `example/` is a separate Convex app with its own deployment
837
+ link; run its CLI commands from that directory.
691
838
 
692
839
  ## License
693
840
 
@@ -470,6 +470,7 @@ export const {
470
470
  goalsReport,
471
471
  funnelsReport,
472
472
  affiliatesReport,
473
+ vitalsReport,
473
474
  dataCoverage,
474
475
  } = federated;
475
476
  ```
@@ -479,21 +480,22 @@ requires both a valid JWT and a matching, non-revoked local grant.
479
480
 
480
481
  ## Function contract
481
482
 
482
- | Function | Input summary | Important output/behavior |
483
- | ------------------ | ------------------------------------------------------- | --------------------------------------------------------------- |
484
- | `manifest` | `{}` | Static protocol, capabilities, names, and limits; no auth |
485
- | `connectionStatus` | `{}` | Redacted status, protocol version, capabilities, site count |
486
- | `listSites` | `{}` | Only `siteId`, `name`, `currency`, `timezone`, `cookieless` |
487
- | `overview` | `siteIds`, `from`, `to`, optional hour/day interval | Aggregate metrics and time series |
488
- | `liveVisitors` | `siteIds`, `now`, optional limit | At most 500 current visitors |
489
- | `listSessions` | one `siteId`, range, `{numItems,cursor}` keyset options | Paginated sessions |
490
- | `sessionJourney` | one site/session, range, `{numItems,cursor}` options | Paginated bounded events |
491
- | `listConversions` | one site, range, `{numItems,cursor}` options | Paginated trusted server-side conversion ledger |
492
- | `visitorJourney` | site IDs, visitor ID, range, optional limit | Cross-site journey inside the grant only |
493
- | `goalsReport` | one site and range | Read-only goal definitions plus aggregates |
494
- | `funnelsReport` | one site and complete UTC-day range | Read-only definitions/steps plus aggregates; max 90 days |
495
- | `affiliatesReport` | one site and complete UTC-day range | Read-only affiliate definitions plus aggregates |
496
- | `dataCoverage` | one site and inclusive integer range | Coverage metadata; echoes site ID, no event/visitor/session IDs |
483
+ | Function | Input summary | Important output/behavior |
484
+ | ------------------ | ------------------------------------------------------- | ---------------------------------------------------------------- |
485
+ | `manifest` | `{}` | Static protocol, capabilities, names, and limits; no auth |
486
+ | `connectionStatus` | `{}` | Redacted status, protocol version, capabilities, site count |
487
+ | `listSites` | `{}` | Only `siteId`, `name`, `currency`, `timezone`, `cookieless` |
488
+ | `overview` | `siteIds`, `from`, `to`, optional hour/day interval | Aggregate metrics and time series |
489
+ | `liveVisitors` | `siteIds`, `now`, optional limit | At most 500 current visitors |
490
+ | `listSessions` | one `siteId`, range, `{numItems,cursor}` keyset options | Paginated sessions |
491
+ | `sessionJourney` | one site/session, range, `{numItems,cursor}` options | Paginated bounded events |
492
+ | `listConversions` | one site, range, `{numItems,cursor}` options | Paginated trusted server-side conversion ledger |
493
+ | `visitorJourney` | site IDs, visitor ID, range, optional limit | Cross-site journey inside the grant only |
494
+ | `goalsReport` | one site and range | Read-only goal definitions plus aggregates |
495
+ | `funnelsReport` | one site and complete UTC-day range | Read-only definitions/steps plus aggregates; max 90 days |
496
+ | `affiliatesReport` | one site and complete UTC-day range | Read-only affiliate definitions plus aggregates |
497
+ | `vitalsReport` | one site and complete UTC-day range | Web Vitals p75/ratings per metric, page, and device; max 90 days |
498
+ | `dataCoverage` | one site and inclusive integer range | Coverage metadata; echoes site ID, no event/visitor/session IDs |
497
499
 
498
500
  The federated surface deliberately excludes ingestion, site mutation, retention
499
501
  writes, configuration writes, domains, owner IDs, network IDs, and arbitrary
@@ -15,9 +15,9 @@
15
15
  "upgrading": "https://www.amagerastro.com/docs/reference/upgrading.md",
16
16
  "integrity": {
17
17
  "algorithm": "sha256",
18
- "agent": "711faf2ca11ee22432cb8b51c7f64fb7e25045a6509391fc8532f08b2fdf3e11",
18
+ "agent": "e2faf12674afb40409c268048595856a248d4a4373af4c7a013089364fc9f28b",
19
19
  "llms": "8c1da1fea23f95a618c4d636840b793195fceedf05f81394df74e1ed5e3fc4da",
20
- "protocol": "6d15aa19469d1e5e008949f1f42417875137ba040bb2bf4427841a5aa0caea77",
20
+ "protocol": "3a5cba45befc3ff0b56828cb207d8ce67ddc0df6bf0280a977d4db8d7f50aaee",
21
21
  "setup": "04c61ecef029a99ba95e1002439dbd1e42ca1ce351e6deb0988fb556ffce649e",
22
22
  "upgrading": "9ff81b8a5e4a615e284916aeae8c7602f3249093599f60b97066a614db406c5a"
23
23
  }
@@ -67,7 +67,8 @@
67
67
  "goalsReport": "goalsReport",
68
68
  "funnelsReport": "funnelsReport",
69
69
  "affiliatesReport": "affiliatesReport",
70
- "dataCoverage": "dataCoverage"
70
+ "dataCoverage": "dataCoverage",
71
+ "vitalsReport": "vitalsReport"
71
72
  },
72
73
  "capabilities": [
73
74
  "siteSummaries",
@@ -80,7 +81,8 @@
80
81
  "goals",
81
82
  "funnels",
82
83
  "affiliates",
83
- "dataCoverage"
84
+ "dataCoverage",
85
+ "vitals"
84
86
  ],
85
87
  "limits": {
86
88
  "maxSitesPerRequest": 10,
@@ -167,7 +167,8 @@
167
167
  "goalsReport",
168
168
  "funnelsReport",
169
169
  "affiliatesReport",
170
- "dataCoverage"
170
+ "dataCoverage",
171
+ "vitalsReport"
171
172
  ],
172
173
  "properties": {
173
174
  "manifest": { "const": "manifest" },
@@ -182,14 +183,15 @@
182
183
  "goalsReport": { "const": "goalsReport" },
183
184
  "funnelsReport": { "const": "funnelsReport" },
184
185
  "affiliatesReport": { "const": "affiliatesReport" },
185
- "dataCoverage": { "const": "dataCoverage" }
186
+ "dataCoverage": { "const": "dataCoverage" },
187
+ "vitalsReport": { "const": "vitalsReport" }
186
188
  },
187
189
  "additionalProperties": false
188
190
  },
189
191
  "capabilities": {
190
192
  "type": "array",
191
- "minItems": 11,
192
- "maxItems": 11,
193
+ "minItems": 12,
194
+ "maxItems": 12,
193
195
  "uniqueItems": true,
194
196
  "items": {
195
197
  "enum": [
@@ -203,7 +205,8 @@
203
205
  "goals",
204
206
  "funnels",
205
207
  "affiliates",
206
- "dataCoverage"
208
+ "dataCoverage",
209
+ "vitals"
207
210
  ]
208
211
  }
209
212
  },
@@ -14,6 +14,27 @@ export declare const FEDERATED_ANALYTICS_ERROR_CODES: Readonly<{
14
14
  readonly INVALID_SCOPE: "FEDERATION_INVALID_SCOPE";
15
15
  readonly SITE_DENIED: "FEDERATION_SITE_DENIED";
16
16
  }>;
17
+ /**
18
+ * The protocol-v1 baseline every compatible host must serve. Newer hosts may
19
+ * advertise more functions and capabilities; a dashboard must require only
20
+ * this frozen set, or older hosts would fail verification on every addition.
21
+ */
22
+ export declare const FEDERATED_ANALYTICS_REQUIRED_FUNCTIONS: Readonly<{
23
+ readonly manifest: "manifest";
24
+ readonly connectionStatus: "connectionStatus";
25
+ readonly listSites: "listSites";
26
+ readonly overview: "overview";
27
+ readonly liveVisitors: "liveVisitors";
28
+ readonly listSessions: "listSessions";
29
+ readonly sessionJourney: "sessionJourney";
30
+ readonly listConversions: "listConversions";
31
+ readonly visitorJourney: "visitorJourney";
32
+ readonly goalsReport: "goalsReport";
33
+ readonly funnelsReport: "funnelsReport";
34
+ readonly affiliatesReport: "affiliatesReport";
35
+ readonly dataCoverage: "dataCoverage";
36
+ }>;
37
+ export declare const FEDERATED_ANALYTICS_REQUIRED_CAPABILITIES: readonly ["siteSummaries", "overview", "liveVisitors", "sessions", "sessionJourney", "conversions", "visitorJourney", "goals", "funnels", "affiliates", "dataCoverage"];
17
38
  export declare const FEDERATED_ANALYTICS_CONNECTOR_MANIFEST: Readonly<{
18
39
  readonly protocol: "amage-rastro-analytics";
19
40
  readonly protocolVersion: 1;
@@ -32,8 +53,9 @@ export declare const FEDERATED_ANALYTICS_CONNECTOR_MANIFEST: Readonly<{
32
53
  readonly funnelsReport: "funnelsReport";
33
54
  readonly affiliatesReport: "affiliatesReport";
34
55
  readonly dataCoverage: "dataCoverage";
56
+ readonly vitalsReport: "vitalsReport";
35
57
  }>;
36
- readonly capabilities: readonly ["siteSummaries", "overview", "liveVisitors", "sessions", "sessionJourney", "conversions", "visitorJourney", "goals", "funnels", "affiliates", "dataCoverage"];
58
+ readonly capabilities: readonly ["siteSummaries", "overview", "liveVisitors", "sessions", "sessionJourney", "conversions", "visitorJourney", "goals", "funnels", "affiliates", "dataCoverage", "vitals"];
37
59
  readonly limits: Readonly<{
38
60
  readonly maxSitesPerRequest: 10;
39
61
  readonly maxSitesPerConnection: 10;
@@ -75,7 +97,7 @@ export interface FederatedAnalyticsAuthorizerOptions {
75
97
  resolveConnection: ResolveFederatedAnalyticsConnection;
76
98
  }
77
99
  export declare const federatedAnalyticsManifestValidator: import("convex/values").VObject<{
78
- capabilities: ("affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney")[];
100
+ capabilities: ("affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney" | "vitals")[];
79
101
  functions: {
80
102
  affiliatesReport: "affiliatesReport";
81
103
  connectionStatus: "connectionStatus";
@@ -90,6 +112,7 @@ export declare const federatedAnalyticsManifestValidator: import("convex/values"
90
112
  overview: "overview";
91
113
  sessionJourney: "sessionJourney";
92
114
  visitorJourney: "visitorJourney";
115
+ vitalsReport: "vitalsReport";
93
116
  };
94
117
  limits: {
95
118
  maxJourneyEvents: number;
@@ -119,6 +142,7 @@ export declare const federatedAnalyticsManifestValidator: import("convex/values"
119
142
  overview: "overview";
120
143
  sessionJourney: "sessionJourney";
121
144
  visitorJourney: "visitorJourney";
145
+ vitalsReport: "vitalsReport";
122
146
  }, {
123
147
  manifest: import("convex/values").VLiteral<"manifest", "required">;
124
148
  connectionStatus: import("convex/values").VLiteral<"connectionStatus", "required">;
@@ -133,8 +157,9 @@ export declare const federatedAnalyticsManifestValidator: import("convex/values"
133
157
  funnelsReport: import("convex/values").VLiteral<"funnelsReport", "required">;
134
158
  affiliatesReport: import("convex/values").VLiteral<"affiliatesReport", "required">;
135
159
  dataCoverage: import("convex/values").VLiteral<"dataCoverage", "required">;
136
- }, "required", "affiliatesReport" | "connectionStatus" | "dataCoverage" | "funnelsReport" | "goalsReport" | "listConversions" | "listSessions" | "listSites" | "liveVisitors" | "manifest" | "overview" | "sessionJourney" | "visitorJourney">;
137
- capabilities: import("convex/values").VArray<("affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney")[], import("convex/values").VUnion<"affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney", [import("convex/values").VLiteral<"siteSummaries", "required">, import("convex/values").VLiteral<"overview", "required">, import("convex/values").VLiteral<"liveVisitors", "required">, import("convex/values").VLiteral<"sessions", "required">, import("convex/values").VLiteral<"sessionJourney", "required">, import("convex/values").VLiteral<"conversions", "required">, import("convex/values").VLiteral<"visitorJourney", "required">, import("convex/values").VLiteral<"goals", "required">, import("convex/values").VLiteral<"funnels", "required">, import("convex/values").VLiteral<"affiliates", "required">, import("convex/values").VLiteral<"dataCoverage", "required">], "required", never>, "required">;
160
+ vitalsReport: import("convex/values").VLiteral<"vitalsReport", "required">;
161
+ }, "required", "affiliatesReport" | "connectionStatus" | "dataCoverage" | "funnelsReport" | "goalsReport" | "listConversions" | "listSessions" | "listSites" | "liveVisitors" | "manifest" | "overview" | "sessionJourney" | "visitorJourney" | "vitalsReport">;
162
+ capabilities: import("convex/values").VArray<("affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney" | "vitals")[], import("convex/values").VUnion<"affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney" | "vitals", [import("convex/values").VLiteral<"siteSummaries", "required">, import("convex/values").VLiteral<"overview", "required">, import("convex/values").VLiteral<"liveVisitors", "required">, import("convex/values").VLiteral<"sessions", "required">, import("convex/values").VLiteral<"sessionJourney", "required">, import("convex/values").VLiteral<"conversions", "required">, import("convex/values").VLiteral<"visitorJourney", "required">, import("convex/values").VLiteral<"goals", "required">, import("convex/values").VLiteral<"funnels", "required">, import("convex/values").VLiteral<"affiliates", "required">, import("convex/values").VLiteral<"dataCoverage", "required">, import("convex/values").VLiteral<"vitals", "required">], "required", never>, "required">;
138
163
  limits: import("convex/values").VObject<{
139
164
  maxJourneyEvents: number;
140
165
  maxLiveVisitors: number;
@@ -148,16 +173,16 @@ export declare const federatedAnalyticsManifestValidator: import("convex/values"
148
173
  maxLiveVisitors: import("convex/values").VFloat64<number, "required">;
149
174
  maxJourneyEvents: import("convex/values").VFloat64<number, "required">;
150
175
  }, "required", "maxJourneyEvents" | "maxLiveVisitors" | "maxReportRangeDays" | "maxSitesPerConnection" | "maxSitesPerRequest">;
151
- }, "required", "capabilities" | "functions" | "functions.affiliatesReport" | "functions.connectionStatus" | "functions.dataCoverage" | "functions.funnelsReport" | "functions.goalsReport" | "functions.listConversions" | "functions.listSessions" | "functions.listSites" | "functions.liveVisitors" | "functions.manifest" | "functions.overview" | "functions.sessionJourney" | "functions.visitorJourney" | "limits" | "limits.maxJourneyEvents" | "limits.maxLiveVisitors" | "limits.maxReportRangeDays" | "limits.maxSitesPerConnection" | "limits.maxSitesPerRequest" | "moduleName" | "protocol" | "protocolVersion">;
176
+ }, "required", "capabilities" | "functions" | "functions.affiliatesReport" | "functions.connectionStatus" | "functions.dataCoverage" | "functions.funnelsReport" | "functions.goalsReport" | "functions.listConversions" | "functions.listSessions" | "functions.listSites" | "functions.liveVisitors" | "functions.manifest" | "functions.overview" | "functions.sessionJourney" | "functions.visitorJourney" | "functions.vitalsReport" | "limits" | "limits.maxJourneyEvents" | "limits.maxLiveVisitors" | "limits.maxReportRangeDays" | "limits.maxSitesPerConnection" | "limits.maxSitesPerRequest" | "moduleName" | "protocol" | "protocolVersion">;
152
177
  export declare const federatedAnalyticsConnectionStatusValidator: import("convex/values").VObject<{
153
- capabilities: ("affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney")[];
178
+ capabilities: ("affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney" | "vitals")[];
154
179
  protocolVersion: 1;
155
180
  siteCount: number;
156
181
  status: "connected";
157
182
  }, {
158
183
  status: import("convex/values").VLiteral<"connected", "required">;
159
184
  protocolVersion: import("convex/values").VLiteral<1, "required">;
160
- capabilities: import("convex/values").VArray<("affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney")[], import("convex/values").VUnion<"affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney", [import("convex/values").VLiteral<"siteSummaries", "required">, import("convex/values").VLiteral<"overview", "required">, import("convex/values").VLiteral<"liveVisitors", "required">, import("convex/values").VLiteral<"sessions", "required">, import("convex/values").VLiteral<"sessionJourney", "required">, import("convex/values").VLiteral<"conversions", "required">, import("convex/values").VLiteral<"visitorJourney", "required">, import("convex/values").VLiteral<"goals", "required">, import("convex/values").VLiteral<"funnels", "required">, import("convex/values").VLiteral<"affiliates", "required">, import("convex/values").VLiteral<"dataCoverage", "required">], "required", never>, "required">;
185
+ capabilities: import("convex/values").VArray<("affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney" | "vitals")[], import("convex/values").VUnion<"affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney" | "vitals", [import("convex/values").VLiteral<"siteSummaries", "required">, import("convex/values").VLiteral<"overview", "required">, import("convex/values").VLiteral<"liveVisitors", "required">, import("convex/values").VLiteral<"sessions", "required">, import("convex/values").VLiteral<"sessionJourney", "required">, import("convex/values").VLiteral<"conversions", "required">, import("convex/values").VLiteral<"visitorJourney", "required">, import("convex/values").VLiteral<"goals", "required">, import("convex/values").VLiteral<"funnels", "required">, import("convex/values").VLiteral<"affiliates", "required">, import("convex/values").VLiteral<"dataCoverage", "required">, import("convex/values").VLiteral<"vitals", "required">], "required", never>, "required">;
161
186
  siteCount: import("convex/values").VFloat64<number, "required">;
162
187
  }, "required", "capabilities" | "protocolVersion" | "siteCount" | "status">;
163
188
  export declare const federatedSiteSummaryValidator: import("convex/values").VObject<{
@@ -191,8 +216,9 @@ export declare function createFederatedAnalyticsManifest(): {
191
216
  funnelsReport: "funnelsReport";
192
217
  affiliatesReport: "affiliatesReport";
193
218
  dataCoverage: "dataCoverage";
219
+ vitalsReport: "vitalsReport";
194
220
  };
195
- capabilities: ("affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney")[];
221
+ capabilities: ("affiliates" | "conversions" | "dataCoverage" | "funnels" | "goals" | "liveVisitors" | "overview" | "sessionJourney" | "sessions" | "siteSummaries" | "visitorJourney" | "vitals")[];
196
222
  limits: {
197
223
  maxSitesPerRequest: 10;
198
224
  maxSitesPerConnection: 10;