@ingenx-io/valets-schema-mcp-server 0.1.4 → 0.1.5

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 (49) hide show
  1. package/data/docs/collections/firestore-paths.md +42 -8
  2. package/data/docs/enums/attention-status.md +1 -1
  3. package/data/docs/enums/booking-status.md +1 -1
  4. package/data/docs/enums/customer-payment-status.md +1 -1
  5. package/data/docs/enums/customer-payment-target-type.md +1 -1
  6. package/data/docs/enums/delivery-type.md +1 -1
  7. package/data/docs/enums/deployment-link-type.md +26 -0
  8. package/data/docs/enums/event-status.md +2 -2
  9. package/data/docs/enums/fulfillment-status.md +2 -2
  10. package/data/docs/enums/loyalty-transaction-type.md +2 -2
  11. package/data/docs/enums/order-status.md +2 -2
  12. package/data/docs/enums/payment-method.md +2 -2
  13. package/data/docs/enums/payment-proof-status.md +2 -2
  14. package/data/docs/enums/payment-status.md +2 -2
  15. package/data/docs/enums/pending-issue.md +2 -2
  16. package/data/docs/enums/return-status.md +2 -2
  17. package/data/docs/enums/session-status.md +2 -2
  18. package/data/docs/enums/site-status.md +24 -0
  19. package/data/docs/enums/ticket-status.md +2 -2
  20. package/data/docs/index.md +9 -3
  21. package/data/docs/models/allowed-user.md +1 -1
  22. package/data/docs/models/analytics-backfill.md +398 -0
  23. package/data/docs/models/analytics-daily.md +351 -0
  24. package/data/docs/models/analytics-event.md +2 -2
  25. package/data/docs/models/analytics-hourly.md +372 -0
  26. package/data/docs/models/booking-version.md +2 -2
  27. package/data/docs/models/booking.md +2 -2
  28. package/data/docs/models/customer-payment-allocation.md +2 -2
  29. package/data/docs/models/customer-payment.md +2 -2
  30. package/data/docs/models/customer.md +2 -2
  31. package/data/docs/models/event.md +2 -2
  32. package/data/docs/models/loyalty-config.md +2 -2
  33. package/data/docs/models/loyalty-reward.md +2 -2
  34. package/data/docs/models/loyalty-status.md +2 -2
  35. package/data/docs/models/loyalty-transaction.md +2 -2
  36. package/data/docs/models/magic-link-request.md +2 -2
  37. package/data/docs/models/metrics-current.md +2 -2
  38. package/data/docs/models/metrics-daily.md +2 -2
  39. package/data/docs/models/metrics-monthly.md +2 -2
  40. package/data/docs/models/order-item.md +2 -2
  41. package/data/docs/models/order.md +248 -220
  42. package/data/docs/models/sale.md +2 -2
  43. package/data/docs/models/site-payment.md +2 -2
  44. package/data/docs/models/site.md +561 -0
  45. package/data/docs/models/ticket.md +2 -2
  46. package/data/static/llms.txt +198 -1
  47. package/data/static/openapi.yaml +617 -0
  48. package/data/static/schemas.json +719 -1
  49. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "description": "@valets/schema \u2014 consolidated schema bundle",
4
- "generated": "2026-04-18T21:21:09.696794+00:00",
4
+ "generated": "2026-04-18T22:24:55.497156+00:00",
5
5
  "schemas": {
6
6
  "allowed-user": {
7
7
  "type": "object",
@@ -74,6 +74,292 @@
74
74
  "paidAt": "pai_ref123"
75
75
  }
76
76
  },
77
+ "analytics-backfill": {
78
+ "type": "object",
79
+ "properties": {
80
+ "id": {
81
+ "readOnly": true,
82
+ "description": "(Read-only) Firestore document ID, auto-generated.",
83
+ "type": [
84
+ "string",
85
+ "null"
86
+ ]
87
+ },
88
+ "companyId": {
89
+ "type": "string",
90
+ "x-immutable": true,
91
+ "description": "(Immutable) FK \u2192 Company document ID."
92
+ },
93
+ "siteId": {
94
+ "type": "string",
95
+ "x-immutable": true,
96
+ "description": "(Immutable) FK \u2192 Site document ID (D40)."
97
+ },
98
+ "status": {
99
+ "type": "string",
100
+ "enum": [
101
+ "pending",
102
+ "running",
103
+ "completed",
104
+ "failed",
105
+ "cancelled"
106
+ ],
107
+ "description": "Run status."
108
+ },
109
+ "from": {
110
+ "type": "string",
111
+ "x-immutable": true,
112
+ "description": "(Immutable) Inclusive start date (`YYYY-MM-DD`, UTC)."
113
+ },
114
+ "to": {
115
+ "type": "string",
116
+ "x-immutable": true,
117
+ "description": "(Immutable) Inclusive end date (`YYYY-MM-DD`, UTC)."
118
+ },
119
+ "dryRun": {
120
+ "type": "boolean",
121
+ "x-immutable": true,
122
+ "description": "(Immutable) When true, rollup writes are skipped \u2014 only source counts are returned for diffing."
123
+ },
124
+ "processedDates": {
125
+ "type": "array",
126
+ "items": {
127
+ "type": "string"
128
+ },
129
+ "description": "Ordered list of `YYYY-MM-DD` dates already processed by this run."
130
+ },
131
+ "errors": {
132
+ "type": "array",
133
+ "items": {
134
+ "type": "object",
135
+ "properties": {
136
+ "date": {
137
+ "type": "string",
138
+ "description": "`YYYY-MM-DD` date that failed."
139
+ },
140
+ "message": {
141
+ "type": "string",
142
+ "description": "Error message captured at failure."
143
+ },
144
+ "at": {
145
+ "$ref": "#/definitions/firestore-timestamp",
146
+ "description": "When the error occurred."
147
+ }
148
+ },
149
+ "required": [
150
+ "date",
151
+ "message",
152
+ "at"
153
+ ],
154
+ "additionalProperties": false
155
+ },
156
+ "description": "Per-date errors collected during the run."
157
+ },
158
+ "triggeredBy": {
159
+ "type": "string",
160
+ "x-immutable": true,
161
+ "description": "(Immutable) UID of the admin who triggered the backfill."
162
+ },
163
+ "startedAt": {
164
+ "$ref": "#/definitions/firestore-timestamp",
165
+ "description": "(Read-only) When the run actually started.",
166
+ "readOnly": true
167
+ },
168
+ "completedAt": {
169
+ "anyOf": [
170
+ {
171
+ "$ref": "#/definitions/firestore-timestamp"
172
+ },
173
+ {
174
+ "type": "null"
175
+ }
176
+ ],
177
+ "readOnly": true,
178
+ "description": "(Read-only) When the run reached a terminal status."
179
+ }
180
+ },
181
+ "required": [
182
+ "companyId",
183
+ "siteId",
184
+ "status",
185
+ "from",
186
+ "to",
187
+ "dryRun",
188
+ "processedDates",
189
+ "errors",
190
+ "triggeredBy",
191
+ "startedAt"
192
+ ],
193
+ "additionalProperties": false,
194
+ "description": "AnalyticsBackfill run (D42 / ING-304). Collection: companies/{companyId}/sites/{siteId}/analytics_backfills/{runId}. Tracks admin-triggered rollup backfill jobs; supports dry-run.",
195
+ "example": {
196
+ "id": null,
197
+ "companyId": "comp_xyz789",
198
+ "siteId": "sit_ref123",
199
+ "status": "pending",
200
+ "from": "from",
201
+ "to": "to",
202
+ "dryRun": true,
203
+ "processedDates": [
204
+ "example"
205
+ ],
206
+ "errors": [
207
+ {
208
+ "date": "2026-02-15",
209
+ "message": "message",
210
+ "at": "at"
211
+ }
212
+ ],
213
+ "triggeredBy": "triggeredBy",
214
+ "startedAt": "startedAt",
215
+ "completedAt": "completedAt"
216
+ }
217
+ },
218
+ "analytics-daily": {
219
+ "type": "object",
220
+ "properties": {
221
+ "totalEvents": {
222
+ "type": "integer",
223
+ "minimum": -9007199254740991,
224
+ "maximum": 9007199254740991,
225
+ "description": "Total event count within the bucket."
226
+ },
227
+ "pageViews": {
228
+ "type": "integer",
229
+ "minimum": -9007199254740991,
230
+ "maximum": 9007199254740991,
231
+ "description": "Count of `page_view` + `screen_view` events."
232
+ },
233
+ "sessions": {
234
+ "type": "integer",
235
+ "minimum": -9007199254740991,
236
+ "maximum": 9007199254740991,
237
+ "description": "Distinct session count within the bucket (by `sessionId`)."
238
+ },
239
+ "uniqueUsers": {
240
+ "type": "integer",
241
+ "minimum": -9007199254740991,
242
+ "maximum": 9007199254740991,
243
+ "description": "Distinct identified `userId` count. Anonymous sessions (userId=null) are excluded."
244
+ },
245
+ "anonymousSessions": {
246
+ "type": "integer",
247
+ "minimum": -9007199254740991,
248
+ "maximum": 9007199254740991,
249
+ "description": "Distinct session count where `userId` was null for the entire session."
250
+ },
251
+ "orders": {
252
+ "type": "integer",
253
+ "minimum": -9007199254740991,
254
+ "maximum": 9007199254740991,
255
+ "description": "Count of `order_submitted` events (or Order documents scoped to this site, D43) within the bucket."
256
+ },
257
+ "paymentsCompleted": {
258
+ "type": "integer",
259
+ "minimum": -9007199254740991,
260
+ "maximum": 9007199254740991,
261
+ "description": "Count of `payment_completed` events within the bucket."
262
+ },
263
+ "paymentsFailed": {
264
+ "type": "integer",
265
+ "minimum": -9007199254740991,
266
+ "maximum": 9007199254740991,
267
+ "description": "Count of `payment_failed` events within the bucket."
268
+ },
269
+ "errors": {
270
+ "type": "integer",
271
+ "minimum": -9007199254740991,
272
+ "maximum": 9007199254740991,
273
+ "description": "Count of `error_occurred` + `exception_caught` events within the bucket."
274
+ },
275
+ "eventCounts": {
276
+ "type": "object",
277
+ "propertyNames": {
278
+ "type": "string"
279
+ },
280
+ "additionalProperties": {
281
+ "type": "integer",
282
+ "minimum": -9007199254740991,
283
+ "maximum": 9007199254740991
284
+ },
285
+ "description": "Per-event-name counts \u2014 key is the event name (canonical or custom), value is the count within the bucket."
286
+ },
287
+ "id": {
288
+ "readOnly": true,
289
+ "description": "(Read-only) Firestore document ID = `YYYY-MM-DD`.",
290
+ "type": [
291
+ "string",
292
+ "null"
293
+ ]
294
+ },
295
+ "companyId": {
296
+ "type": "string",
297
+ "x-immutable": true,
298
+ "description": "(Immutable) FK \u2192 Company document ID."
299
+ },
300
+ "siteId": {
301
+ "type": "string",
302
+ "x-immutable": true,
303
+ "description": "(Immutable) FK \u2192 Site document ID (D40)."
304
+ },
305
+ "date": {
306
+ "type": "string",
307
+ "x-immutable": true,
308
+ "description": "(Immutable) `YYYY-MM-DD` UTC \u2014 matches document ID."
309
+ },
310
+ "computedAt": {
311
+ "$ref": "#/definitions/firestore-timestamp",
312
+ "description": "(Read-only) When this rollup was last (re)computed. Updated on every set/merge.",
313
+ "readOnly": true
314
+ },
315
+ "sourceEventCount": {
316
+ "readOnly": true,
317
+ "description": "(Read-only, Optional) Total source events scanned when producing this rollup. Useful for dry-run diffing.",
318
+ "type": [
319
+ "integer",
320
+ "null"
321
+ ],
322
+ "minimum": -9007199254740991,
323
+ "maximum": 9007199254740991
324
+ }
325
+ },
326
+ "required": [
327
+ "totalEvents",
328
+ "pageViews",
329
+ "sessions",
330
+ "uniqueUsers",
331
+ "anonymousSessions",
332
+ "orders",
333
+ "paymentsCompleted",
334
+ "paymentsFailed",
335
+ "errors",
336
+ "eventCounts",
337
+ "companyId",
338
+ "siteId",
339
+ "date",
340
+ "computedAt"
341
+ ],
342
+ "additionalProperties": false,
343
+ "description": "AnalyticsDaily rollup (D42 / ING-304). Collection: companies/{companyId}/sites/{siteId}/analytics_daily/{YYYY-MM-DD}. Idempotent set/merge \u2014 reruns overwrite. Fall back to raw analytics_events for uncovered slices.",
344
+ "example": {
345
+ "totalEvents": 0,
346
+ "pageViews": 0,
347
+ "sessions": 0,
348
+ "uniqueUsers": 0,
349
+ "anonymousSessions": 0,
350
+ "orders": 0,
351
+ "paymentsCompleted": 0,
352
+ "paymentsFailed": 0,
353
+ "errors": 0,
354
+ "eventCounts": {},
355
+ "id": null,
356
+ "companyId": "comp_xyz789",
357
+ "siteId": "sit_ref123",
358
+ "date": "2026-02-15",
359
+ "computedAt": "computedAt",
360
+ "sourceEventCount": null
361
+ }
362
+ },
77
363
  "analytics-event": {
78
364
  "type": "object",
79
365
  "properties": {
@@ -277,6 +563,160 @@
277
563
  "userTraits": null
278
564
  }
279
565
  },
566
+ "analytics-hourly": {
567
+ "type": "object",
568
+ "properties": {
569
+ "totalEvents": {
570
+ "type": "integer",
571
+ "minimum": -9007199254740991,
572
+ "maximum": 9007199254740991,
573
+ "description": "Total event count within the bucket."
574
+ },
575
+ "pageViews": {
576
+ "type": "integer",
577
+ "minimum": -9007199254740991,
578
+ "maximum": 9007199254740991,
579
+ "description": "Count of `page_view` + `screen_view` events."
580
+ },
581
+ "sessions": {
582
+ "type": "integer",
583
+ "minimum": -9007199254740991,
584
+ "maximum": 9007199254740991,
585
+ "description": "Distinct session count within the bucket (by `sessionId`)."
586
+ },
587
+ "uniqueUsers": {
588
+ "type": "integer",
589
+ "minimum": -9007199254740991,
590
+ "maximum": 9007199254740991,
591
+ "description": "Distinct identified `userId` count. Anonymous sessions (userId=null) are excluded."
592
+ },
593
+ "anonymousSessions": {
594
+ "type": "integer",
595
+ "minimum": -9007199254740991,
596
+ "maximum": 9007199254740991,
597
+ "description": "Distinct session count where `userId` was null for the entire session."
598
+ },
599
+ "orders": {
600
+ "type": "integer",
601
+ "minimum": -9007199254740991,
602
+ "maximum": 9007199254740991,
603
+ "description": "Count of `order_submitted` events (or Order documents scoped to this site, D43) within the bucket."
604
+ },
605
+ "paymentsCompleted": {
606
+ "type": "integer",
607
+ "minimum": -9007199254740991,
608
+ "maximum": 9007199254740991,
609
+ "description": "Count of `payment_completed` events within the bucket."
610
+ },
611
+ "paymentsFailed": {
612
+ "type": "integer",
613
+ "minimum": -9007199254740991,
614
+ "maximum": 9007199254740991,
615
+ "description": "Count of `payment_failed` events within the bucket."
616
+ },
617
+ "errors": {
618
+ "type": "integer",
619
+ "minimum": -9007199254740991,
620
+ "maximum": 9007199254740991,
621
+ "description": "Count of `error_occurred` + `exception_caught` events within the bucket."
622
+ },
623
+ "eventCounts": {
624
+ "type": "object",
625
+ "propertyNames": {
626
+ "type": "string"
627
+ },
628
+ "additionalProperties": {
629
+ "type": "integer",
630
+ "minimum": -9007199254740991,
631
+ "maximum": 9007199254740991
632
+ },
633
+ "description": "Per-event-name counts \u2014 key is the event name (canonical or custom), value is the count within the bucket."
634
+ },
635
+ "id": {
636
+ "readOnly": true,
637
+ "description": "(Read-only) Firestore document ID = `YYYY-MM-DD-HH`.",
638
+ "type": [
639
+ "string",
640
+ "null"
641
+ ]
642
+ },
643
+ "companyId": {
644
+ "type": "string",
645
+ "x-immutable": true,
646
+ "description": "(Immutable) FK \u2192 Company document ID."
647
+ },
648
+ "siteId": {
649
+ "type": "string",
650
+ "x-immutable": true,
651
+ "description": "(Immutable) FK \u2192 Site document ID (D40)."
652
+ },
653
+ "date": {
654
+ "type": "string",
655
+ "x-immutable": true,
656
+ "description": "(Immutable) `YYYY-MM-DD` UTC for the bucket day."
657
+ },
658
+ "hour": {
659
+ "type": "integer",
660
+ "minimum": -9007199254740991,
661
+ "maximum": 9007199254740991,
662
+ "x-immutable": true,
663
+ "description": "(Immutable) UTC hour of day, 0\u201323."
664
+ },
665
+ "computedAt": {
666
+ "$ref": "#/definitions/firestore-timestamp",
667
+ "description": "(Read-only) When this rollup was last (re)computed.",
668
+ "readOnly": true
669
+ },
670
+ "sourceEventCount": {
671
+ "readOnly": true,
672
+ "description": "(Read-only, Optional) Total source events scanned when producing this rollup.",
673
+ "type": [
674
+ "integer",
675
+ "null"
676
+ ],
677
+ "minimum": -9007199254740991,
678
+ "maximum": 9007199254740991
679
+ }
680
+ },
681
+ "required": [
682
+ "totalEvents",
683
+ "pageViews",
684
+ "sessions",
685
+ "uniqueUsers",
686
+ "anonymousSessions",
687
+ "orders",
688
+ "paymentsCompleted",
689
+ "paymentsFailed",
690
+ "errors",
691
+ "eventCounts",
692
+ "companyId",
693
+ "siteId",
694
+ "date",
695
+ "hour",
696
+ "computedAt"
697
+ ],
698
+ "additionalProperties": false,
699
+ "description": "AnalyticsHourly rollup (D42 / ING-304). Collection: companies/{companyId}/sites/{siteId}/analytics_hourly/{YYYY-MM-DD-HH}. Scheduled-CF writer; idempotent set/merge.",
700
+ "example": {
701
+ "totalEvents": 0,
702
+ "pageViews": 0,
703
+ "sessions": 0,
704
+ "uniqueUsers": 0,
705
+ "anonymousSessions": 0,
706
+ "orders": 0,
707
+ "paymentsCompleted": 0,
708
+ "paymentsFailed": 0,
709
+ "errors": 0,
710
+ "eventCounts": {},
711
+ "id": null,
712
+ "companyId": "comp_xyz789",
713
+ "siteId": "sit_ref123",
714
+ "date": "2026-02-15",
715
+ "hour": 0,
716
+ "computedAt": "computedAt",
717
+ "sourceEventCount": null
718
+ }
719
+ },
280
720
  "attention-status": {
281
721
  "type": "string",
282
722
  "enum": [
@@ -1624,6 +2064,24 @@
1624
2064
  ],
1625
2065
  "description": "Fulfillment channel for an order. Determines whether the customer comes to the business (ON_SITE), collects their order themselves (PICK_UP), or receives a physical delivery (DELIVERY). Drives whether fulfillmentStatus is relevant."
1626
2066
  },
2067
+ "deployment-link-type": {
2068
+ "type": "string",
2069
+ "enum": [
2070
+ "web",
2071
+ "mobile",
2072
+ "pwa",
2073
+ "app-store",
2074
+ "play-store",
2075
+ "other"
2076
+ ],
2077
+ "description": "Category of a Site deployment link (D41). Used for UI icon/labelling and deep-link handling.",
2078
+ "x-note": "Lowercase by convention \u2014 deployment link types are display-oriented labels, not lifecycle states.",
2079
+ "x-see": {
2080
+ "decisions": [
2081
+ "D41"
2082
+ ]
2083
+ }
2084
+ },
1627
2085
  "event": {
1628
2086
  "type": "object",
1629
2087
  "properties": {
@@ -3037,6 +3495,21 @@
3037
3495
  "x-immutable": true,
3038
3496
  "description": "(Immutable) FK \u2192 Company document ID. Scopes all queries."
3039
3497
  },
3498
+ "siteId": {
3499
+ "x-immutable": true,
3500
+ "x-note": "D43 / ADR-003: optional site attribution. Absent/null means company-wide (legacy). When set, the order is attributed to a specific site for analytics and site-scoped dashboards. No migration \u2014 existing orders keep null. Flat path; Order continues to live at `companies/{companyId}/orders/{orderId}`.",
3501
+ "x-see": {
3502
+ "decisions": [
3503
+ "D43"
3504
+ ]
3505
+ },
3506
+ "x-when": "Set at order creation when the client knows which site the order originated from (e.g. SR Single, Lifesense). Leave null for legacy/company-wide orders. Composite index: (companyId, siteId, createdAt).",
3507
+ "description": "(Immutable, Optional) FK \u2192 Site document ID (D43 / ADR-003). null = company-wide.",
3508
+ "type": [
3509
+ "string",
3510
+ "null"
3511
+ ]
3512
+ },
3040
3513
  "orderNumber": {
3041
3514
  "type": "string",
3042
3515
  "readOnly": true,
@@ -3626,6 +4099,7 @@
3626
4099
  "id": "bk_abc123def456",
3627
4100
  "uid": "user_u8x92kqm",
3628
4101
  "companyId": "comp_xyz789",
4102
+ "siteId": null,
3629
4103
  "orderNumber": "ORD-2026-0042",
3630
4104
  "status": "status",
3631
4105
  "paymentStatus": "paymentStatus",
@@ -4121,6 +4595,233 @@
4121
4595
  ],
4122
4596
  "description": "Per-date/per-slot booking session status (D19). Dashboard is sole writer; Mobile is read-only."
4123
4597
  },
4598
+ "site": {
4599
+ "type": "object",
4600
+ "properties": {
4601
+ "id": {
4602
+ "readOnly": true,
4603
+ "description": "(Read-only) Firestore document ID, auto-generated.",
4604
+ "type": [
4605
+ "string",
4606
+ "null"
4607
+ ]
4608
+ },
4609
+ "companyId": {
4610
+ "type": "string",
4611
+ "x-immutable": true,
4612
+ "description": "(Immutable) FK \u2192 Company document ID. Scopes all site sub-collections."
4613
+ },
4614
+ "name": {
4615
+ "type": "string",
4616
+ "description": "Human-readable site name shown in dashboards."
4617
+ },
4618
+ "description": {
4619
+ "description": "Optional freeform description.",
4620
+ "type": [
4621
+ "string",
4622
+ "null"
4623
+ ]
4624
+ },
4625
+ "status": {
4626
+ "$ref": "#/definitions/site-status",
4627
+ "description": "Lifecycle status (D41). Clients filter by ACTIVE.",
4628
+ "x-note": "ACTIVE = live and serving traffic. INACTIVE = intentionally disabled by operators. EXPIRED = past expiresAt (derived when isExpired is true; may also be stored explicitly). ARCHIVED = soft-deleted; retained for audit but not listed by default.",
4629
+ "x-see": {
4630
+ "decisions": [
4631
+ "D41"
4632
+ ]
4633
+ },
4634
+ "x-when": "Written by operators via dashboard or by server triggers (expiration). Clients filter by ACTIVE."
4635
+ },
4636
+ "deploymentLinks": {
4637
+ "type": "array",
4638
+ "items": {
4639
+ "type": "object",
4640
+ "properties": {
4641
+ "label": {
4642
+ "type": "string",
4643
+ "description": "Human-readable label shown in dashboards (e.g. \"Production\", \"Staging\")."
4644
+ },
4645
+ "url": {
4646
+ "type": "string",
4647
+ "description": "Absolute URL or store link."
4648
+ },
4649
+ "type": {
4650
+ "$ref": "#/definitions/deployment-link-type",
4651
+ "description": "Link category \u2014 drives icon/handler selection.",
4652
+ "x-note": "Lowercase by convention \u2014 deployment link types are display-oriented labels, not lifecycle states.",
4653
+ "x-see": {
4654
+ "decisions": [
4655
+ "D41"
4656
+ ]
4657
+ }
4658
+ },
4659
+ "isPrimary": {
4660
+ "description": "If true, this link is used as the canonical deployment URL for the Site.",
4661
+ "type": "boolean"
4662
+ }
4663
+ },
4664
+ "required": [
4665
+ "label",
4666
+ "url",
4667
+ "type"
4668
+ ],
4669
+ "additionalProperties": false,
4670
+ "description": "Deployment link entry embedded on Site.deploymentLinks[] (D41)."
4671
+ },
4672
+ "description": "Ordered list of deployment URLs (web, mobile, PWA, store links)."
4673
+ },
4674
+ "expiresAt": {
4675
+ "anyOf": [
4676
+ {
4677
+ "$ref": "#/definitions/firestore-timestamp"
4678
+ },
4679
+ {
4680
+ "type": "null"
4681
+ }
4682
+ ],
4683
+ "description": "Optional expiration timestamp. When set and elapsed, `isExpired` flips true and status typically moves to EXPIRED."
4684
+ },
4685
+ "isExpired": {
4686
+ "readOnly": true,
4687
+ "description": "(Read-only) Derived \u2014 true when `expiresAt` is in the past. Maintained by server trigger.",
4688
+ "type": [
4689
+ "boolean",
4690
+ "null"
4691
+ ]
4692
+ },
4693
+ "createdAt": {
4694
+ "anyOf": [
4695
+ {
4696
+ "$ref": "#/definitions/firestore-timestamp"
4697
+ },
4698
+ {
4699
+ "type": "null"
4700
+ }
4701
+ ],
4702
+ "readOnly": true,
4703
+ "description": "(Read-only) Server-generated creation timestamp."
4704
+ },
4705
+ "updatedAt": {
4706
+ "anyOf": [
4707
+ {
4708
+ "$ref": "#/definitions/firestore-timestamp"
4709
+ },
4710
+ {
4711
+ "type": "null"
4712
+ }
4713
+ ],
4714
+ "readOnly": true,
4715
+ "description": "(Read-only) Server-generated update timestamp."
4716
+ },
4717
+ "createdBy": {
4718
+ "type": "string",
4719
+ "x-immutable": true,
4720
+ "description": "(Immutable) FK \u2192 User/staff UID who created the site."
4721
+ },
4722
+ "analyticsEnabled": {
4723
+ "type": "boolean",
4724
+ "description": "Feature flag \u2014 when false, clients should not emit analytics events for this site."
4725
+ },
4726
+ "lastAnalyticsSync": {
4727
+ "anyOf": [
4728
+ {
4729
+ "$ref": "#/definitions/firestore-timestamp"
4730
+ },
4731
+ {
4732
+ "type": "null"
4733
+ }
4734
+ ],
4735
+ "readOnly": true,
4736
+ "description": "(Read-only) Last time the analytics rollup pipeline refreshed `cachedMetrics`."
4737
+ },
4738
+ "cachedMetrics": {
4739
+ "readOnly": true,
4740
+ "denormalized": true,
4741
+ "x-note": "Updated by the rollup pipeline (D42). Readers should treat this as a hint; authoritative counts live in analytics_daily/analytics_hourly.",
4742
+ "x-see": {
4743
+ "decisions": [
4744
+ "D41",
4745
+ "D42"
4746
+ ]
4747
+ },
4748
+ "description": "(Read-only, Denormalized) Cached metrics snapshot for quick dashboard rendering.",
4749
+ "type": [
4750
+ "object",
4751
+ "null"
4752
+ ],
4753
+ "properties": {
4754
+ "totalEvents": {
4755
+ "type": "integer",
4756
+ "minimum": -9007199254740991,
4757
+ "maximum": 9007199254740991,
4758
+ "description": "All-time event count cached on the Site."
4759
+ },
4760
+ "totalPageViews": {
4761
+ "type": "integer",
4762
+ "minimum": -9007199254740991,
4763
+ "maximum": 9007199254740991,
4764
+ "description": "All-time `page_view` + `screen_view` count."
4765
+ },
4766
+ "totalSessions": {
4767
+ "type": "integer",
4768
+ "minimum": -9007199254740991,
4769
+ "maximum": 9007199254740991,
4770
+ "description": "All-time distinct session count."
4771
+ },
4772
+ "totalOrders": {
4773
+ "type": "integer",
4774
+ "minimum": -9007199254740991,
4775
+ "maximum": 9007199254740991,
4776
+ "description": "All-time order count attributed to this site (via `Order.siteId`, D43)."
4777
+ },
4778
+ "lastEventAt": {
4779
+ "$ref": "#/definitions/firestore-timestamp",
4780
+ "description": "Timestamp of the most recent analytics event seen for this site."
4781
+ }
4782
+ },
4783
+ "required": [
4784
+ "totalEvents",
4785
+ "totalPageViews",
4786
+ "totalSessions",
4787
+ "totalOrders"
4788
+ ],
4789
+ "additionalProperties": false
4790
+ }
4791
+ },
4792
+ "required": [
4793
+ "companyId",
4794
+ "name",
4795
+ "status",
4796
+ "deploymentLinks",
4797
+ "createdBy",
4798
+ "analyticsEnabled"
4799
+ ],
4800
+ "additionalProperties": false,
4801
+ "description": "Site model (D41 / ING-304). Collection: companies/{companyId}/sites/{siteId}. Per-company product surface. Sub-collections (magic_link_requests, allowed_users, payments, analytics_events, analytics_daily, analytics_hourly, analytics_backfills) live under this document per D40/D42.",
4802
+ "example": {
4803
+ "id": null,
4804
+ "companyId": "comp_xyz789",
4805
+ "name": "Amadou Diallo",
4806
+ "description": null,
4807
+ "status": "status",
4808
+ "deploymentLinks": [
4809
+ {
4810
+ "label": "label",
4811
+ "url": "https://storage.example.com/url.jpg",
4812
+ "type": "phone"
4813
+ }
4814
+ ],
4815
+ "expiresAt": "expiresAt",
4816
+ "isExpired": null,
4817
+ "createdAt": "createdAt",
4818
+ "updatedAt": "updatedAt",
4819
+ "createdBy": "staff_k0f1",
4820
+ "analyticsEnabled": true,
4821
+ "lastAnalyticsSync": "lastAnalyticsSync",
4822
+ "cachedMetrics": null
4823
+ }
4824
+ },
4124
4825
  "site-payment": {
4125
4826
  "type": "object",
4126
4827
  "properties": {
@@ -4198,6 +4899,23 @@
4198
4899
  "paidAt": "pai_ref123"
4199
4900
  }
4200
4901
  },
4902
+ "site-status": {
4903
+ "type": "string",
4904
+ "enum": [
4905
+ "ACTIVE",
4906
+ "INACTIVE",
4907
+ "EXPIRED",
4908
+ "ARCHIVED"
4909
+ ],
4910
+ "description": "Lifecycle status for a Site (D41). Drives whether the site is reachable and whether analytics/payments flow.",
4911
+ "x-note": "ACTIVE = live and serving traffic. INACTIVE = intentionally disabled by operators. EXPIRED = past expiresAt (derived when isExpired is true; may also be stored explicitly). ARCHIVED = soft-deleted; retained for audit but not listed by default.",
4912
+ "x-see": {
4913
+ "decisions": [
4914
+ "D41"
4915
+ ]
4916
+ },
4917
+ "x-when": "Written by operators via dashboard or by server triggers (expiration). Clients filter by ACTIVE."
4918
+ },
4201
4919
  "ticket": {
4202
4920
  "type": "object",
4203
4921
  "properties": {