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

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 (89) hide show
  1. package/README.md +215 -46
  2. package/agent/integration.md +186 -46
  3. package/agent/manifest.json +28 -10
  4. package/agent/manifest.schema.json +41 -8
  5. package/dist/client/federation.d.ts +156 -11
  6. package/dist/client/federation.d.ts.map +1 -1
  7. package/dist/client/federation.js +82 -4
  8. package/dist/client/federation.js.map +1 -1
  9. package/dist/client/index.d.ts +306 -97
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +116 -13
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/component/_generated/api.d.ts +6 -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 +26 -13
  17. package/dist/component/_generated/component.d.ts.map +1 -1
  18. package/dist/component/constants.d.ts +3 -0
  19. package/dist/component/constants.d.ts.map +1 -1
  20. package/dist/component/constants.js +6 -0
  21. package/dist/component/constants.js.map +1 -1
  22. package/dist/component/coverage.d.ts +8 -0
  23. package/dist/component/coverage.d.ts.map +1 -1
  24. package/dist/component/coverage.js +32 -6
  25. package/dist/component/coverage.js.map +1 -1
  26. package/dist/component/geo.d.ts +2 -0
  27. package/dist/component/geo.d.ts.map +1 -1
  28. package/dist/component/geo.js +56 -19
  29. package/dist/component/geo.js.map +1 -1
  30. package/dist/component/http.d.ts.map +1 -1
  31. package/dist/component/http.js +47 -27
  32. package/dist/component/http.js.map +1 -1
  33. package/dist/component/ingest.d.ts.map +1 -1
  34. package/dist/component/ingest.js +149 -295
  35. package/dist/component/ingest.js.map +1 -1
  36. package/dist/component/localTime.d.ts +25 -0
  37. package/dist/component/localTime.d.ts.map +1 -0
  38. package/dist/component/localTime.js +126 -0
  39. package/dist/component/localTime.js.map +1 -0
  40. package/dist/component/reports.d.ts +20 -13
  41. package/dist/component/reports.d.ts.map +1 -1
  42. package/dist/component/reports.js +158 -38
  43. package/dist/component/reports.js.map +1 -1
  44. package/dist/component/retention.js +26 -8
  45. package/dist/component/retention.js.map +1 -1
  46. package/dist/component/rollupStore.d.ts +320 -0
  47. package/dist/component/rollupStore.d.ts.map +1 -0
  48. package/dist/component/rollupStore.js +596 -0
  49. package/dist/component/rollupStore.js.map +1 -0
  50. package/dist/component/rollups.d.ts +20 -0
  51. package/dist/component/rollups.d.ts.map +1 -0
  52. package/dist/component/rollups.js +73 -0
  53. package/dist/component/rollups.js.map +1 -0
  54. package/dist/component/schema.d.ts +311 -5
  55. package/dist/component/schema.js +9 -0
  56. package/dist/component/schema.js.map +1 -1
  57. package/dist/component/sites.d.ts +12 -0
  58. package/dist/component/sites.d.ts.map +1 -1
  59. package/dist/component/sites.js +30 -0
  60. package/dist/component/sites.js.map +1 -1
  61. package/dist/component/validators.d.ts +24 -5
  62. package/dist/component/validators.d.ts.map +1 -1
  63. package/dist/component/validators.js +16 -1
  64. package/dist/component/validators.js.map +1 -1
  65. package/dist/tracker/generated.d.ts +3 -0
  66. package/dist/tracker/generated.d.ts.map +1 -1
  67. package/dist/tracker/generated.js +3 -0
  68. package/dist/tracker/generated.js.map +1 -1
  69. package/docs/federation-setup.md +93 -26
  70. package/docs/federation.md +120 -30
  71. package/docs/upgrading.md +103 -17
  72. package/llms.txt +6 -5
  73. package/package.json +2 -1
  74. package/src/component/_generated/api.ts +6 -0
  75. package/src/component/_generated/component.ts +27 -12
  76. package/src/component/constants.ts +6 -0
  77. package/src/component/coverage.ts +41 -6
  78. package/src/component/geo.ts +82 -28
  79. package/src/component/http.ts +60 -26
  80. package/src/component/ingest.ts +226 -425
  81. package/src/component/localTime.ts +167 -0
  82. package/src/component/reports.ts +222 -39
  83. package/src/component/retention.ts +25 -7
  84. package/src/component/rollupStore.ts +799 -0
  85. package/src/component/rollups.ts +82 -0
  86. package/src/component/schema.ts +17 -0
  87. package/src/component/sites.ts +32 -0
  88. package/src/component/validators.ts +20 -1
  89. package/src/tracker/generated.ts +12 -0
@@ -21,7 +21,11 @@ npm view @iann29/rastro@alpha version
21
21
  ```
22
22
 
23
23
  After the operator approves that exact version, inspect it outside the host
24
- without executing package code or lifecycle scripts:
24
+ without executing package code or lifecycle scripts. Run the block from a
25
+ directory whose `node_modules` resolves `typescript` — the host checkout after
26
+ `npm install` is the natural place. From anywhere else the inspector exits with
27
+ `ERR_MODULE_NOT_FOUND` for `typescript` and `npx --no-install tsc` finds no
28
+ compiler; neither outcome is a verdict about the package.
25
29
 
26
30
  ```bash
27
31
  tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
@@ -244,7 +248,7 @@ repository commit.
244
248
  ```text
245
249
  product browser -> customer HTTP-actions URL -> customer Rastro tables
246
250
 
247
- amagerastro.com browser -> customer functions URL -> read-only reports
251
+ amagerastro.com browser -> customer functions URL -> reports (+ opt-in configure)
248
252
  ^
249
253
  |
250
254
  short-lived control-plane JWT
@@ -316,7 +320,11 @@ Confirm local analytics first:
316
320
  2. The site exists and its domains include the exact product hostname.
317
321
  3. The tracker loads from `<HTTP_ACTIONS_URL>/rastro/tracker.js`.
318
322
  4. A real `POST /rastro/events` returns HTTP 200 with accepted counters.
319
- 5. An authenticated host query can read a report for that exact site.
323
+ 5. An authenticated host query can read a report for that exact site. Check with
324
+ `liveVisitors` or `sessionJourney`, which reflect an accepted request
325
+ immediately; `listSessions` and the bucketed `overview` can trail it by
326
+ seconds, so an empty result from those two right after the request is not a
327
+ failed ingestion.
320
328
  6. Retained routes, IDs, campaigns, affiliates, location fields, conversion
321
329
  records, and custom properties contain no unexpected personal data, access
322
330
  tokens, secrets, or form values. Confirm that every intended organization
@@ -347,6 +355,7 @@ rastroFederationGrants: defineTable({
347
355
  connectionId: v.string(),
348
356
  organizationId: v.string(),
349
357
  siteIds: v.array(v.string()),
358
+ permissions: v.optional(v.array(v.string())),
350
359
  revokedAt: v.optional(v.number()),
351
360
  createdAt: v.number(),
352
361
  updatedAt: v.number(),
@@ -356,7 +365,10 @@ rastroFederationGrants: defineTable({
356
365
  Create `convex/rastroFederationAdmin.ts` with internal or equivalently protected
357
366
  operator functions that:
358
367
 
359
- - accept a connection ID, organization ID, opaque host owner ID, and site IDs;
368
+ - accept a connection ID, organization ID, opaque host owner ID, site IDs, and
369
+ an optional `permissions` list validated with
370
+ `federatedAnalyticsPermissionValidator` (absent means read only;
371
+ `["analytics:configure"]` opts the connection into the configure scope);
360
372
  - reject empty/duplicate IDs and more than ten unique sites;
361
373
  - load every site from Rastro and verify it belongs to that owner;
362
374
  - upsert by the `by_connectionId` index and clear `revokedAt` when
@@ -376,10 +388,16 @@ object. If the app has no `env` object, add one without changing any other
376
388
  `defineApp` option. Do not add another Rastro mount:
377
389
 
378
390
  ```ts
379
- RASTRO_FEDERATION_ISSUER: v.string(),
380
- RASTRO_FEDERATION_AUDIENCE: v.string(),
391
+ RASTRO_FEDERATION_ISSUER: v.optional(v.string()),
392
+ RASTRO_FEDERATION_AUDIENCE: v.optional(v.string()),
381
393
  ```
382
394
 
395
+ Declare them optional. A required `v.string()` makes the push fail on every
396
+ deployment where the values are not set yet, which is every deployment that
397
+ pushes code before pairing. Set both values before pushing: `auth.config.ts`
398
+ reads them at deploy time, so values set after a push take effect only on the
399
+ next push.
400
+
383
401
  Insert this conditional spread into the existing `providers` array; never
384
402
  replace the array or its current entries:
385
403
 
@@ -432,12 +450,21 @@ name `rastroFederation`.
432
450
  import {
433
451
  exposeFederatedAnalyticsApi,
434
452
  type FederatedAnalyticsConnection,
453
+ type FederatedAnalyticsPermission,
435
454
  } from "@iann29/rastro";
436
455
  import { components } from "./_generated/api";
437
456
  import { env, type QueryCtx } from "./_generated/server";
438
457
 
458
+ /** The production Amage Rastro OIDC issuer; override with RASTRO_FEDERATION_ISSUER. */
459
+ export const DEFAULT_FEDERATION_ISSUER =
460
+ "https://site.api.amagerastro.com/federation";
461
+
462
+ export const federationIssuer = (
463
+ env.RASTRO_FEDERATION_ISSUER ?? DEFAULT_FEDERATION_ISSUER
464
+ ).replace(/\/$/, "");
465
+
439
466
  const federated = exposeFederatedAnalyticsApi(components.rastroAnalytics, {
440
- issuer: env.RASTRO_FEDERATION_ISSUER.replace(/\/$/, ""),
467
+ issuer: federationIssuer,
441
468
  resolveConnection: async (ctx, identity) => {
442
469
  const db = ctx.db as unknown as QueryCtx["db"];
443
470
  const grant = await db
@@ -452,6 +479,7 @@ const federated = exposeFederatedAnalyticsApi(components.rastroAnalytics, {
452
479
  connectionId: grant.connectionId,
453
480
  organizationId: grant.organizationId,
454
481
  siteIds: grant.siteIds,
482
+ permissions: grant.permissions as FederatedAnalyticsPermission[],
455
483
  revokedAt: grant.revokedAt,
456
484
  } satisfies FederatedAnalyticsConnection;
457
485
  },
@@ -473,11 +501,35 @@ export const {
473
501
  vitalsReport,
474
502
  siteMap,
475
503
  dataCoverage,
504
+ siteSettings,
505
+ updateSite,
506
+ listGoals,
507
+ upsertGoal,
508
+ removeGoal,
509
+ listFunnels,
510
+ upsertFunnel,
511
+ removeFunnel,
512
+ listAffiliates,
513
+ upsertAffiliate,
514
+ removeAffiliate,
515
+ retentionStatus,
516
+ setRetentionPolicy,
517
+ disableRetentionPolicy,
476
518
  } = federated;
477
519
  ```
478
520
 
479
- `manifest` is public static metadata. Every other function is read-only and
480
- requires both a valid JWT and a matching, non-revoked local grant.
521
+ `manifest` is public static metadata. Every report function is read-only and
522
+ requires both a valid JWT and a matching, non-revoked local grant. The functions
523
+ from `siteSettings` down form the configure scope (optional capability
524
+ `configure`, hosts from `alpha.11`): they additionally require
525
+ `analytics:configure` on the token — the control plane claims it only for
526
+ organization owners and admins — and in the grant's `permissions`, and fail with
527
+ `FEDERATION_CONFIGURE_FORBIDDEN` otherwise. Export them even when every grant
528
+ stays read only, so a later opt-in needs no host deploy; the dashboard never
529
+ calls them for a read-only connection. The issuer falls back to the production
530
+ control plane so the module compiles and pushes before the environment variable
531
+ exists; tokens are still rejected until `auth.config.ts` trusts the same issuer,
532
+ which needs both variables set and a push afterwards.
481
533
 
482
534
  ## Function contract
483
535
 
@@ -499,13 +551,34 @@ requires both a valid JWT and a matching, non-revoked local grant.
499
551
  | `siteMap` | site IDs and complete UTC-day range | Routes with derived exits/bounces plus transitions; max 90 days |
500
552
  | `dataCoverage` | one site and inclusive integer range | Coverage metadata; echoes site ID, no event/visitor/session IDs |
501
553
 
502
- The federated surface deliberately excludes ingestion, site mutation, retention
503
- writes, configuration writes, domains, owner IDs, network IDs, and arbitrary
504
- host functions. Goal, funnel, and affiliate reports include their read-only
505
- definitions. Journey events include bounded custom `properties`; hosts must
506
- follow the tracker contract and never place personal data or secrets in them. A
507
- session belongs to one site; only `visitorJourney` can join the same
508
- pseudonymous visitor across explicitly granted sites.
554
+ The configure scope, every function of which requires `analytics:configure` on
555
+ the token and in the grant:
556
+
557
+ | Function | Input summary | Important output/behavior |
558
+ | ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------------- |
559
+ | `siteSettings` | one `siteId` | `name`, `domains`, `timezone`, `currency`, `cookieless`; no owner |
560
+ | `updateSite` | one `siteId`, optional `name`, `domains`, `timezone` | Changes only those three; currency and `cookieless` stay outside |
561
+ | `listGoals` | one `siteId` | Goal definitions, at most 50 |
562
+ | `upsertGoal` | `siteId`, optional `goalId`, key, name, matcher, value, flag | Returns the goal ID; keys and active matchers are unique per site |
563
+ | `removeGoal` | `siteId`, `goalId` | Deletes the definition; rollups already written remain |
564
+ | `listFunnels` | one `siteId` | Funnel definitions, at most 20 |
565
+ | `upsertFunnel` | `siteId`, optional `funnelId`, key, name, 2–10 steps, window | Returns the funnel ID; window between one minute and 90 days |
566
+ | `removeFunnel` | `siteId`, `funnelId` | Deletes the definition |
567
+ | `listAffiliates` | one `siteId` | Affiliate definitions, at most 100 |
568
+ | `upsertAffiliate` | `siteId`, optional `affiliateId`, slug, name, bps, window | Returns the affiliate ID; commission 0–10000 bps, window 1–365 days |
569
+ | `removeAffiliate` | `siteId`, `affiliateId` | Deletes the definition; accounted commissions remain |
570
+ | `retentionStatus` | one `siteId` | The policy, its cleanup jobs, and the backlog |
571
+ | `setRetentionPolicy` | `siteId` and five day counts between 1 and 3650 | Starts the recurring cleanup chains |
572
+ | `disableRetentionPolicy` | one `siteId` | Stops automatic cleanup; nothing is restored |
573
+
574
+ The federated surface deliberately excludes ingestion, owner IDs, network IDs,
575
+ and arbitrary host functions, and shows a site's domains only through
576
+ `siteSettings` to a connection allowed to change them. Goal, funnel, and
577
+ affiliate reports include their read-only definitions. Journey events include
578
+ bounded custom `properties`; hosts must follow the tracker contract and never
579
+ place personal data or secrets in them. A session belongs to one site; only
580
+ `visitorJourney` can join the same pseudonymous visitor across explicitly
581
+ granted sites.
509
582
 
510
583
  All times are Unix epoch milliseconds. The global report ceiling is 366 days,
511
584
  but hourly overview is limited to 24 hours and funnel reports to 90 complete UTC
@@ -552,14 +625,21 @@ synapse convex --dev run rastroFederationAdmin:provisionConnection \
552
625
  ```
553
626
 
554
627
  Never copy site IDs from JWT claims or grant every site belonging to an owner.
555
- The operator must name one through ten exact site IDs.
628
+ The operator must name one through ten exact site IDs. Add
629
+ `"permissions":["analytics:configure"]` to the same JSON only when the operator
630
+ wants the dashboard's owners and admins to configure those sites; omit it for a
631
+ read-only connection, and rerun without it to revoke the scope.
556
632
 
557
633
  ## Phase 6: verify in order
558
634
 
559
635
  Do not mark integration complete until all checks pass:
560
636
 
561
637
  1. Call `rastroFederation:manifest` without auth and deep-compare its connector
562
- fields with <https://www.amagerastro.com/agent/manifest.json>.
638
+ fields with the `connector` object of
639
+ <https://www.amagerastro.com/agent/manifest.json>. Compare structurally
640
+ (`assert.deepStrictEqual`, `toEqual`, or an equivalent order-insensitive
641
+ check), never as serialized text: the served object and the JSON file list
642
+ the same keys in a different order.
563
643
  2. In **Conexões**, click **Verificar conexão** and confirm active status and
564
644
  the exact site count.
565
645
  3. Confirm `listSites` omits domains, owner IDs, network IDs, connection IDs,
@@ -583,41 +663,100 @@ Do not mark integration complete until all checks pass:
583
663
  report, and require `FEDERATION_CONNECTION_DENIED` before central
584
664
  revocation.
585
665
  11. Reprovision only if the rehearsal requires restoring the development grant.
666
+ 12. In the same suite, call `upsertGoal` with a read-only identity and require
667
+ `FEDERATION_CONFIGURE_FORBIDDEN`; when the grant lists
668
+ `analytics:configure`, call it with an identity whose `rastro_permissions`
669
+ includes `analytics:configure` and require the goal in `listGoals`, then
670
+ require `FEDERATION_SITE_DENIED` for a site outside the grant.
671
+
672
+ Use this harness when the host has no `convex-test` suite yet. Convex functions
673
+ run under `edge-runtime` in tests, which is what a deployed function gets, so
674
+ install `vitest`, `convex-test`, and `@edge-runtime/vm` as dev dependencies and
675
+ configure the environment and the issuer the identity will claim:
676
+
677
+ ```ts
678
+ // vitest.config.ts
679
+ import { defineConfig } from "vitest/config";
680
+
681
+ export default defineConfig({
682
+ test: {
683
+ environment: "edge-runtime",
684
+ env: {
685
+ RASTRO_FEDERATION_ISSUER: "https://site.api.amagerastro.com/federation",
686
+ },
687
+ },
688
+ });
689
+ ```
586
690
 
587
- Use this assertion shape in the host's existing `convex-test` harness after
588
- registering its schema, modules, component, and `api.rastroFederation`:
691
+ Register the host schema and modules together with the Rastro component; the
692
+ package's `test` entry registers it under the mounted name `rastroAnalytics`:
589
693
 
590
694
  ```ts
591
- const identity = {
592
- issuer: RASTRO_FEDERATION_ISSUER,
593
- subject: "rastro-negative-test",
594
- tokenIdentifier: `${RASTRO_FEDERATION_ISSUER}|rastro-negative-test`,
595
- rastro_connection_id: CONNECTION_ID,
596
- rastro_organization_id: ORGANIZATION_ID,
597
- rastro_permissions: ["analytics:read"],
598
- };
599
-
600
- await expect(
601
- t.query(api.rastroFederation.overview, allowedArgs),
602
- ).rejects.toMatchObject({ data: { code: "FEDERATION_UNAUTHENTICATED" } });
603
- await expect(
604
- t.withIdentity(identity).query(api.rastroFederation.overview, {
605
- ...allowedArgs,
606
- siteIds: [KNOWN_OUTSIDE_GRANT_SITE_ID],
607
- }),
608
- ).rejects.toMatchObject({ data: { code: "FEDERATION_SITE_DENIED" } });
695
+ // convex/setup.test.ts
696
+ /// <reference types="vite/client" />
697
+ import { convexTest } from "convex-test";
698
+ import component from "@iann29/rastro/test";
699
+ import schema from "./schema";
700
+
701
+ const modules = import.meta.glob("./**/*.*s");
702
+
703
+ export function initConvexTest() {
704
+ const t = convexTest(schema, modules);
705
+ component.register(t);
706
+ return t;
707
+ }
609
708
  ```
610
709
 
710
+ Then use this assertion shape, with `federationIssuer` imported from
711
+ `convex/rastroFederation.ts` so the identity claims the issuer the module
712
+ verifies:
713
+
714
+ ```ts
715
+ // convex/rastroFederation.test.ts
716
+ import { expect, test } from "vitest";
717
+ import { api } from "./_generated/api";
718
+ import { federationIssuer } from "./rastroFederation";
719
+ import { initConvexTest } from "./setup.test";
720
+
721
+ test("denies a site outside the grant", async () => {
722
+ const t = initConvexTest();
723
+ const identity = {
724
+ issuer: federationIssuer,
725
+ subject: "rastro-negative-test",
726
+ tokenIdentifier: `${federationIssuer}|rastro-negative-test`,
727
+ rastro_connection_id: CONNECTION_ID,
728
+ rastro_organization_id: ORGANIZATION_ID,
729
+ rastro_permissions: ["analytics:read"],
730
+ };
731
+
732
+ await expect(
733
+ t.query(api.rastroFederation.overview, allowedArgs),
734
+ ).rejects.toMatchObject({ data: { code: "FEDERATION_UNAUTHENTICATED" } });
735
+ await expect(
736
+ t.withIdentity(identity).query(api.rastroFederation.overview, {
737
+ ...allowedArgs,
738
+ siteIds: [KNOWN_OUTSIDE_GRANT_SITE_ID],
739
+ }),
740
+ ).rejects.toMatchObject({ data: { code: "FEDERATION_SITE_DENIED" } });
741
+ });
742
+ ```
743
+
744
+ Provision the grant for `CONNECTION_ID` inside the test with
745
+ `t.mutation(internal.rastroFederationAdmin.provisionConnection, ...)` after
746
+ creating a site through the host's own admin function; `allowedArgs` names that
747
+ site and a complete UTC-day range.
748
+
611
749
  Consumers must branch on `ConvexError.data.code`, not human-readable text:
612
750
 
613
- | Code | Meaning |
614
- | ------------------------------ | ----------------------------------------------------------------- |
615
- | `FEDERATION_UNAUTHENTICATED` | No authenticated Rastro identity |
616
- | `FEDERATION_ISSUER_MISMATCH` | Identity came from another issuer |
617
- | `FEDERATION_INVALID_CLAIMS` | Required bounded claims or permission are absent |
618
- | `FEDERATION_CONNECTION_DENIED` | Local grant is missing, malformed, mismatched, or revoked |
619
- | `FEDERATION_INVALID_SCOPE` | Requested site list is empty, duplicate, malformed, or over limit |
620
- | `FEDERATION_SITE_DENIED` | A requested site is outside the local grant |
751
+ | Code | Meaning |
752
+ | -------------------------------- | ----------------------------------------------------------------- |
753
+ | `FEDERATION_UNAUTHENTICATED` | No authenticated Rastro identity |
754
+ | `FEDERATION_ISSUER_MISMATCH` | Identity came from another issuer |
755
+ | `FEDERATION_INVALID_CLAIMS` | Required bounded claims or permission are absent |
756
+ | `FEDERATION_CONNECTION_DENIED` | Local grant is missing, malformed, mismatched, or revoked |
757
+ | `FEDERATION_INVALID_SCOPE` | Requested site list is empty, duplicate, malformed, or over limit |
758
+ | `FEDERATION_SITE_DENIED` | A requested site is outside the local grant |
759
+ | `FEDERATION_CONFIGURE_FORBIDDEN` | The token or the grant lacks `analytics:configure` |
621
760
 
622
761
  ## Revocation
623
762
 
@@ -649,6 +788,7 @@ Package/version: <exact eligible registry version>
649
788
  Existing auth providers preserved: yes/no
650
789
  Manifest protocol: <value>
651
790
  Granted site count: <1..10>
791
+ Configure scope granted: yes/no
652
792
  Ingestion verified: yes/no
653
793
  Authorized reports verified: yes/no
654
794
  Wrong-site denial verified: yes/no
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://www.amagerastro.com/agent/manifest.schema.json",
3
3
  "name": "amage-rastro",
4
4
  "title": "Amage Rastro",
5
- "description": "Privacy-first, real-time web analytics for Convex with a read-only federated dashboard.",
5
+ "description": "Privacy-first, real-time web analytics for Convex with a federated dashboard that reads every granted site and configures only those whose host opts in.",
6
6
  "manifestVersion": "1.0.0",
7
7
  "language": "en",
8
8
  "canonicalBaseUrl": "https://www.amagerastro.com",
@@ -15,11 +15,11 @@
15
15
  "upgrading": "https://www.amagerastro.com/docs/reference/upgrading.md",
16
16
  "integrity": {
17
17
  "algorithm": "sha256",
18
- "agent": "b641b6cdecc0c23291e169bceef6308d1679392bec0b5c9e11715b6740be83b3",
19
- "llms": "0dab84ee468ef3f22c7560bd0080957218f66d3014a660be0e93bbef1de0ccd5",
20
- "protocol": "57fe1d40b81a872a027294945dc577b4a6346562620f2772074dc534c11c9190",
21
- "setup": "5e7cd04a7b285a887404fe3cdae918a63374822e656395df2b50f57736e0fd54",
22
- "upgrading": "c98d9773b8fe84b1b34bc3b8f35225ad65f7e23a7ce056ba0c2ce8bad979c127"
18
+ "agent": "438c4bebe59637fcc7a0093acf229a635f93849b7a5b8f25603e2cd6c5d06ea2",
19
+ "llms": "c8d1d7650636aa53007da448c174eb6af67fa685bffd7b9c3e682558e131a124",
20
+ "protocol": "bbc7a1b1bac693ca1f684b8df6dd73ea6b73d46875ebe89f145cbafabd9200f2",
21
+ "setup": "84f1fab088c151971d8b02326af298188699c875d7ae77c40f423ae44d5116dd",
22
+ "upgrading": "55fd64f84238edac7018e2e1e9f97724bd3602c77791d59f41099e7b45532221"
23
23
  }
24
24
  },
25
25
  "controlPlane": {
@@ -69,7 +69,21 @@
69
69
  "affiliatesReport": "affiliatesReport",
70
70
  "dataCoverage": "dataCoverage",
71
71
  "vitalsReport": "vitalsReport",
72
- "siteMap": "siteMap"
72
+ "siteMap": "siteMap",
73
+ "siteSettings": "siteSettings",
74
+ "updateSite": "updateSite",
75
+ "listGoals": "listGoals",
76
+ "upsertGoal": "upsertGoal",
77
+ "removeGoal": "removeGoal",
78
+ "listFunnels": "listFunnels",
79
+ "upsertFunnel": "upsertFunnel",
80
+ "removeFunnel": "removeFunnel",
81
+ "listAffiliates": "listAffiliates",
82
+ "upsertAffiliate": "upsertAffiliate",
83
+ "removeAffiliate": "removeAffiliate",
84
+ "retentionStatus": "retentionStatus",
85
+ "setRetentionPolicy": "setRetentionPolicy",
86
+ "disableRetentionPolicy": "disableRetentionPolicy"
73
87
  },
74
88
  "capabilities": [
75
89
  "siteSummaries",
@@ -84,7 +98,9 @@
84
98
  "affiliates",
85
99
  "dataCoverage",
86
100
  "vitals",
87
- "siteMap"
101
+ "siteMap",
102
+ "localDays",
103
+ "configure"
88
104
  ],
89
105
  "limits": {
90
106
  "maxSitesPerRequest": 10,
@@ -98,6 +114,7 @@
98
114
  "providerType": "customJwt",
99
115
  "audience": "exact-normalized-customer-functions-url",
100
116
  "permission": "analytics:read",
117
+ "configurePermission": "analytics:configure",
101
118
  "claims": {
102
119
  "connectionId": "rastro_connection_id",
103
120
  "organizationId": "rastro_organization_id",
@@ -160,7 +177,7 @@
160
177
  "Never accept ownerId, userId, organizationId, or site scope from an untrusted client as authorization.",
161
178
  "Never replace existing authentication providers; append the Rastro provider.",
162
179
  "Never use JWT site IDs as authoritative scope; resolve the indexed local grant on every query.",
163
- "Never expose writes, raw site configuration, owner-wide enumeration, or ingestion through the federated module.",
180
+ "Never expose ingestion, owner-wide enumeration, or arbitrary host functions through the federated module; configuration writes run only when both the token and the local grant carry analytics:configure.",
164
181
  "Never run a deployment-affecting command without an explicit development or production target.",
165
182
  "Revoke the local grant before central revocation when immediate denial is required.",
166
183
  "Keep telemetry in the customer deployment."
@@ -182,6 +199,7 @@
182
199
  "FEDERATION_INVALID_CLAIMS",
183
200
  "FEDERATION_CONNECTION_DENIED",
184
201
  "FEDERATION_INVALID_SCOPE",
185
- "FEDERATION_SITE_DENIED"
202
+ "FEDERATION_SITE_DENIED",
203
+ "FEDERATION_CONFIGURE_FORBIDDEN"
186
204
  ]
187
205
  }
@@ -169,7 +169,21 @@
169
169
  "affiliatesReport",
170
170
  "dataCoverage",
171
171
  "vitalsReport",
172
- "siteMap"
172
+ "siteMap",
173
+ "siteSettings",
174
+ "updateSite",
175
+ "listGoals",
176
+ "upsertGoal",
177
+ "removeGoal",
178
+ "listFunnels",
179
+ "upsertFunnel",
180
+ "removeFunnel",
181
+ "listAffiliates",
182
+ "upsertAffiliate",
183
+ "removeAffiliate",
184
+ "retentionStatus",
185
+ "setRetentionPolicy",
186
+ "disableRetentionPolicy"
173
187
  ],
174
188
  "properties": {
175
189
  "manifest": { "const": "manifest" },
@@ -186,14 +200,28 @@
186
200
  "affiliatesReport": { "const": "affiliatesReport" },
187
201
  "dataCoverage": { "const": "dataCoverage" },
188
202
  "vitalsReport": { "const": "vitalsReport" },
189
- "siteMap": { "const": "siteMap" }
203
+ "siteMap": { "const": "siteMap" },
204
+ "siteSettings": { "const": "siteSettings" },
205
+ "updateSite": { "const": "updateSite" },
206
+ "listGoals": { "const": "listGoals" },
207
+ "upsertGoal": { "const": "upsertGoal" },
208
+ "removeGoal": { "const": "removeGoal" },
209
+ "listFunnels": { "const": "listFunnels" },
210
+ "upsertFunnel": { "const": "upsertFunnel" },
211
+ "removeFunnel": { "const": "removeFunnel" },
212
+ "listAffiliates": { "const": "listAffiliates" },
213
+ "upsertAffiliate": { "const": "upsertAffiliate" },
214
+ "removeAffiliate": { "const": "removeAffiliate" },
215
+ "retentionStatus": { "const": "retentionStatus" },
216
+ "setRetentionPolicy": { "const": "setRetentionPolicy" },
217
+ "disableRetentionPolicy": { "const": "disableRetentionPolicy" }
190
218
  },
191
219
  "additionalProperties": false
192
220
  },
193
221
  "capabilities": {
194
222
  "type": "array",
195
- "minItems": 13,
196
- "maxItems": 13,
223
+ "minItems": 15,
224
+ "maxItems": 15,
197
225
  "uniqueItems": true,
198
226
  "items": {
199
227
  "enum": [
@@ -209,7 +237,9 @@
209
237
  "affiliates",
210
238
  "dataCoverage",
211
239
  "vitals",
212
- "siteMap"
240
+ "siteMap",
241
+ "localDays",
242
+ "configure"
213
243
  ]
214
244
  }
215
245
  },
@@ -240,6 +270,7 @@
240
270
  "providerType",
241
271
  "audience",
242
272
  "permission",
273
+ "configurePermission",
243
274
  "claims",
244
275
  "authoritativeScope"
245
276
  ],
@@ -247,6 +278,7 @@
247
278
  "providerType": { "const": "customJwt" },
248
279
  "audience": { "const": "exact-normalized-customer-functions-url" },
249
280
  "permission": { "const": "analytics:read" },
281
+ "configurePermission": { "const": "analytics:configure" },
250
282
  "claims": {
251
283
  "type": "object",
252
284
  "required": ["connectionId", "organizationId", "permissions"],
@@ -379,8 +411,8 @@
379
411
  },
380
412
  "stableErrors": {
381
413
  "type": "array",
382
- "minItems": 6,
383
- "maxItems": 6,
414
+ "minItems": 7,
415
+ "maxItems": 7,
384
416
  "uniqueItems": true,
385
417
  "items": {
386
418
  "enum": [
@@ -389,7 +421,8 @@
389
421
  "FEDERATION_INVALID_CLAIMS",
390
422
  "FEDERATION_CONNECTION_DENIED",
391
423
  "FEDERATION_INVALID_SCOPE",
392
- "FEDERATION_SITE_DENIED"
424
+ "FEDERATION_SITE_DENIED",
425
+ "FEDERATION_CONFIGURE_FORBIDDEN"
393
426
  ]
394
427
  }
395
428
  }