@ingenx-io/valets-schema-mcp-server 0.1.3 → 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 +56 -3
  2. package/data/docs/enums/attention-status.md +24 -0
  3. package/data/docs/enums/booking-status.md +2 -2
  4. package/data/docs/enums/customer-payment-status.md +2 -2
  5. package/data/docs/enums/customer-payment-target-type.md +2 -2
  6. package/data/docs/enums/delivery-type.md +2 -2
  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 +31 -0
  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 +15 -3
  21. package/data/docs/models/allowed-user.md +188 -0
  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 +533 -0
  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 +285 -0
  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 +200 -0
  44. package/data/docs/models/site.md +561 -0
  45. package/data/docs/models/ticket.md +2 -2
  46. package/data/static/llms.txt +362 -2
  47. package/data/static/openapi.yaml +1068 -0
  48. package/data/static/schemas.json +1229 -44
  49. package/package.json +1 -1
@@ -1,8 +1,740 @@
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-06T19:27:48.749605+00:00",
4
+ "generated": "2026-04-18T22:24:55.497156+00:00",
5
5
  "schemas": {
6
+ "allowed-user": {
7
+ "type": "object",
8
+ "properties": {
9
+ "id": {
10
+ "readOnly": true,
11
+ "description": "(Read-only) Firestore document ID = contact identifier (email or E.164 phone), URL-escaped.",
12
+ "type": [
13
+ "string",
14
+ "null"
15
+ ]
16
+ },
17
+ "companyId": {
18
+ "type": "string",
19
+ "x-immutable": true,
20
+ "description": "(Immutable) FK \u2192 Company document ID."
21
+ },
22
+ "siteId": {
23
+ "type": "string",
24
+ "x-immutable": true,
25
+ "description": "(Immutable) FK \u2192 Site document ID (D40 sub-tenant scope)."
26
+ },
27
+ "contact": {
28
+ "type": "string",
29
+ "description": "Email or E.164 phone number. Canonical identifier for this user within the site."
30
+ },
31
+ "tier": {
32
+ "type": "string",
33
+ "description": "Access tier. Free string per site (ING-304 open question \u2014 tier values are site-specific today)."
34
+ },
35
+ "amount": {
36
+ "type": "number",
37
+ "description": "Amount paid. Generalized from amount_xof per D40 decision."
38
+ },
39
+ "currency": {
40
+ "default": "XOF",
41
+ "description": "Currency code (ISO 4217). Defaults to XOF for legacy SR-Single parity.",
42
+ "type": "string"
43
+ },
44
+ "transactionId": {
45
+ "type": "string",
46
+ "description": "Payment provider transaction ID (e.g. Wave)."
47
+ },
48
+ "paidAt": {
49
+ "$ref": "#/definitions/firestore-timestamp",
50
+ "description": "RFC3339Nano UTC when payment was completed."
51
+ }
52
+ },
53
+ "required": [
54
+ "companyId",
55
+ "siteId",
56
+ "contact",
57
+ "tier",
58
+ "amount",
59
+ "currency",
60
+ "transactionId",
61
+ "paidAt"
62
+ ],
63
+ "additionalProperties": false,
64
+ "description": "AllowedUser model (D40 / ING-304). Collection: companies/{companyId}/sites/{siteId}/allowed_users/{contactId}. Authoritative paid-access allowlist. Upsert semantics \u2014 tier upgrades overwrite. Source of truth for access checks and referral code resolution.",
65
+ "example": {
66
+ "id": null,
67
+ "companyId": "comp_xyz789",
68
+ "siteId": "sit_ref123",
69
+ "contact": "contact",
70
+ "tier": "Gold",
71
+ "amount": 45000,
72
+ "currency": "XOF",
73
+ "transactionId": "tra_ref123",
74
+ "paidAt": "pai_ref123"
75
+ }
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
+ },
363
+ "analytics-event": {
364
+ "type": "object",
365
+ "properties": {
366
+ "id": {
367
+ "readOnly": true,
368
+ "description": "(Read-only) Firestore document ID. Equal to eventId.",
369
+ "type": [
370
+ "string",
371
+ "null"
372
+ ]
373
+ },
374
+ "companyId": {
375
+ "type": "string",
376
+ "x-immutable": true,
377
+ "description": "(Immutable) FK \u2192 Company document ID."
378
+ },
379
+ "siteId": {
380
+ "type": "string",
381
+ "x-immutable": true,
382
+ "description": "(Immutable) FK \u2192 Site document ID (D40 sub-tenant scope)."
383
+ },
384
+ "eventId": {
385
+ "type": "string",
386
+ "x-immutable": true,
387
+ "description": "(Immutable) Client-generated UUID. Matches document ID."
388
+ },
389
+ "eventName": {
390
+ "type": "string",
391
+ "description": "Event name. Free string; canonical vocabulary is encouraged (see CANONICAL_ANALYTICS_EVENT_NAMES)."
392
+ },
393
+ "timestamp": {
394
+ "type": "string",
395
+ "description": "Client-side ISO 8601 \u2014 \"when it happened on device\". Can diverge from serverTimestamp for queued offline events."
396
+ },
397
+ "serverTimestamp": {
398
+ "$ref": "#/definitions/firestore-timestamp",
399
+ "description": "(Read-only) Firestore serverTimestamp() \u2014 authoritative for ordering.",
400
+ "readOnly": true
401
+ },
402
+ "sessionId": {
403
+ "type": "string",
404
+ "description": "30-minute rolling session identifier."
405
+ },
406
+ "userId": {
407
+ "anyOf": [
408
+ {
409
+ "type": "string"
410
+ },
411
+ {
412
+ "type": "null"
413
+ }
414
+ ],
415
+ "description": "Stable user ID if identified; explicitly null (not omitted) for anonymous sessions \u2014 simplifies querying."
416
+ },
417
+ "properties": {
418
+ "type": "object",
419
+ "propertyNames": {
420
+ "type": "string"
421
+ },
422
+ "additionalProperties": {},
423
+ "description": "Free-form event-specific payload. Contract is at the event-name level. PII posture: follow-up issue pending."
424
+ },
425
+ "context": {
426
+ "type": "object",
427
+ "properties": {
428
+ "url": {
429
+ "description": "Full URL at event time.",
430
+ "type": "string"
431
+ },
432
+ "origin": {
433
+ "description": "Protocol + host.",
434
+ "type": "string"
435
+ },
436
+ "hostname": {
437
+ "description": "Hostname only \u2014 useful for deployment identification.",
438
+ "type": "string"
439
+ },
440
+ "page": {
441
+ "description": "Current route/pathname.",
442
+ "type": "string"
443
+ },
444
+ "title": {
445
+ "description": "Page title.",
446
+ "type": "string"
447
+ },
448
+ "referrer": {
449
+ "description": "Referrer URL.",
450
+ "type": "string"
451
+ },
452
+ "deviceType": {
453
+ "type": "string",
454
+ "enum": [
455
+ "mobile",
456
+ "tablet",
457
+ "desktop"
458
+ ]
459
+ },
460
+ "platform": {
461
+ "description": "OS/platform string.",
462
+ "type": "string"
463
+ },
464
+ "screenWidth": {
465
+ "description": "Viewport width (flattened from screen.width).",
466
+ "type": "integer",
467
+ "minimum": -9007199254740991,
468
+ "maximum": 9007199254740991
469
+ },
470
+ "screenHeight": {
471
+ "description": "Viewport height (flattened from screen.height).",
472
+ "type": "integer",
473
+ "minimum": -9007199254740991,
474
+ "maximum": 9007199254740991
475
+ },
476
+ "appVersion": {
477
+ "description": "Consuming app version.",
478
+ "type": "string"
479
+ },
480
+ "online": {
481
+ "description": "Connectivity state at event time.",
482
+ "type": "boolean"
483
+ },
484
+ "locale": {
485
+ "description": "User locale.",
486
+ "type": "string"
487
+ },
488
+ "timezone": {
489
+ "description": "IANA timezone.",
490
+ "type": "string"
491
+ },
492
+ "utm": {
493
+ "description": "UTM parameters; null when not a campaign-landed session.",
494
+ "anyOf": [
495
+ {
496
+ "type": "object",
497
+ "properties": {
498
+ "source": {
499
+ "type": "string"
500
+ },
501
+ "medium": {
502
+ "type": "string"
503
+ },
504
+ "campaign": {
505
+ "type": "string"
506
+ },
507
+ "term": {
508
+ "type": "string"
509
+ },
510
+ "content": {
511
+ "type": "string"
512
+ }
513
+ },
514
+ "additionalProperties": false
515
+ },
516
+ {
517
+ "type": "null"
518
+ }
519
+ ]
520
+ }
521
+ },
522
+ "additionalProperties": false,
523
+ "description": "Environment context at event time (nested per D40)."
524
+ },
525
+ "userTraits": {
526
+ "description": "Optional \u2014 included only when includeUserTraits: true on the write. PII posture: follow-up issue pending.",
527
+ "type": [
528
+ "object",
529
+ "null"
530
+ ],
531
+ "propertyNames": {
532
+ "type": "string"
533
+ },
534
+ "additionalProperties": {}
535
+ }
536
+ },
537
+ "required": [
538
+ "companyId",
539
+ "siteId",
540
+ "eventId",
541
+ "eventName",
542
+ "timestamp",
543
+ "serverTimestamp",
544
+ "sessionId",
545
+ "userId",
546
+ "properties",
547
+ "context"
548
+ ],
549
+ "additionalProperties": false,
550
+ "description": "AnalyticsEvent model (D40 / ING-304). Collection: companies/{companyId}/sites/{siteId}/analytics_events/{eventId}. Append-only, immutable product/behavior event stream. Event names are free strings; canonical vocabulary in CANONICAL_ANALYTICS_EVENT_NAMES.",
551
+ "example": {
552
+ "id": null,
553
+ "companyId": "comp_xyz789",
554
+ "siteId": "sit_ref123",
555
+ "eventId": "eve_ref123",
556
+ "eventName": "eventName",
557
+ "timestamp": "timestamp",
558
+ "serverTimestamp": "serverTimestamp",
559
+ "sessionId": "ses_ref123",
560
+ "userId": "use_ref123",
561
+ "properties": {},
562
+ "context": {},
563
+ "userTraits": null
564
+ }
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
+ },
720
+ "attention-status": {
721
+ "type": "string",
722
+ "enum": [
723
+ "ACTIVE",
724
+ "STALE",
725
+ "ON_HOLD",
726
+ "ESCALATED"
727
+ ],
728
+ "description": "Operational attention status for Order and Booking (D39). Controls home-page queue assignment. Server-owned \u2014 set via triggers or callable fn only.",
729
+ "x-note": "ACTIVE = normal urgent queue (default). STALE = idle past threshold, moved to review queue \u2014 excluded from pendingOrdersCount. ON_HOLD = intentionally paused by manager via callable fn. ESCALATED = ignored past second threshold.",
730
+ "x-see": {
731
+ "decisions": [
732
+ "D39"
733
+ ]
734
+ },
735
+ "x-when": "Set by server automation or callable fn. Mobile and Dashboard read this field; never write it directly. ON_HOLD is the only human-initiated transition \u2014 goes through a dedicated callable function.",
736
+ "x-status": "proposed"
737
+ },
6
738
  "booking": {
7
739
  "type": "object",
8
740
  "properties": {
@@ -1332,6 +2064,24 @@
1332
2064
  ],
1333
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."
1334
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
+ },
1335
2085
  "event": {
1336
2086
  "type": "object",
1337
2087
  "properties": {
@@ -1961,6 +2711,122 @@
1961
2711
  ],
1962
2712
  "description": "Loyalty point transaction type (D07). SCREAMING_SNAKE past tense."
1963
2713
  },
2714
+ "magic-link-request": {
2715
+ "type": "object",
2716
+ "properties": {
2717
+ "id": {
2718
+ "readOnly": true,
2719
+ "description": "(Read-only) Firestore document ID. 16-byte random hex.",
2720
+ "type": [
2721
+ "string",
2722
+ "null"
2723
+ ]
2724
+ },
2725
+ "companyId": {
2726
+ "type": "string",
2727
+ "x-immutable": true,
2728
+ "description": "(Immutable) FK \u2192 Company document ID."
2729
+ },
2730
+ "siteId": {
2731
+ "type": "string",
2732
+ "x-immutable": true,
2733
+ "description": "(Immutable) FK \u2192 Site document ID (D40 sub-tenant scope)."
2734
+ },
2735
+ "email": {
2736
+ "type": "string",
2737
+ "description": "Populated if identifier is email; empty string if phone. Mutually exclusive with phone per document."
2738
+ },
2739
+ "phone": {
2740
+ "type": "string",
2741
+ "description": "Populated if identifier is E.164 format (starts with +); empty string if email."
2742
+ },
2743
+ "ip": {
2744
+ "type": "string",
2745
+ "description": "Client IP from X-Forwarded-For or RemoteAddr."
2746
+ },
2747
+ "allowed": {
2748
+ "type": "boolean",
2749
+ "description": "Whether the user passed the access control check."
2750
+ },
2751
+ "link": {
2752
+ "type": "string",
2753
+ "description": "Magic-link URL \u2014 non-empty only when DEV_MODE=true; empty in production (audit integrity)."
2754
+ },
2755
+ "tier": {
2756
+ "type": "string",
2757
+ "description": "Access tier selected by the user. Free string per site (ING-304 open question \u2014 tier values are site-specific today)."
2758
+ },
2759
+ "authStatusReason": {
2760
+ "type": "string",
2761
+ "description": "Outcome reason code: whitelist | allowed_users | payment_redirect | payment_complete | not_allowed | redirected_to_payment."
2762
+ },
2763
+ "referredBy": {
2764
+ "type": "string",
2765
+ "description": "Contact identifier of the referrer, resolved from submitted referral_code; empty if none."
2766
+ },
2767
+ "requestedAt": {
2768
+ "$ref": "#/definitions/firestore-timestamp",
2769
+ "description": "RFC3339Nano UTC at request time."
2770
+ },
2771
+ "verifyReason": {
2772
+ "description": "Verify outcome reason: mirrors authStatusReason on success, or link_expired | link_invalid | not_allowed.",
2773
+ "type": [
2774
+ "string",
2775
+ "null"
2776
+ ]
2777
+ },
2778
+ "verified": {
2779
+ "description": "Whether the verify attempt succeeded.",
2780
+ "type": [
2781
+ "boolean",
2782
+ "null"
2783
+ ]
2784
+ },
2785
+ "verifiedAt": {
2786
+ "anyOf": [
2787
+ {
2788
+ "$ref": "#/definitions/firestore-timestamp"
2789
+ },
2790
+ {
2791
+ "type": "null"
2792
+ }
2793
+ ],
2794
+ "description": "RFC3339Nano UTC at verify time."
2795
+ }
2796
+ },
2797
+ "required": [
2798
+ "companyId",
2799
+ "siteId",
2800
+ "email",
2801
+ "phone",
2802
+ "ip",
2803
+ "allowed",
2804
+ "link",
2805
+ "tier",
2806
+ "authStatusReason",
2807
+ "referredBy",
2808
+ "requestedAt"
2809
+ ],
2810
+ "additionalProperties": false,
2811
+ "description": "MagicLinkRequest model (D40 / ING-304). Collection: companies/{companyId}/sites/{siteId}/magic_link_requests/{requestId}. Authentication audit log \u2014 every request is logged regardless of outcome. Two-stage write: Log() then LogVerify().",
2812
+ "example": {
2813
+ "id": null,
2814
+ "companyId": "comp_xyz789",
2815
+ "siteId": "sit_ref123",
2816
+ "email": "amadou@example.com",
2817
+ "phone": "+225 07 00 11 22",
2818
+ "ip": "ip",
2819
+ "allowed": true,
2820
+ "link": "link",
2821
+ "tier": "Gold",
2822
+ "authStatusReason": "Customer request",
2823
+ "referredBy": "referredBy",
2824
+ "requestedAt": "requestedAt",
2825
+ "verifyReason": null,
2826
+ "verified": null,
2827
+ "verifiedAt": "verifiedAt"
2828
+ }
2829
+ },
1964
2830
  "metrics-current": {
1965
2831
  "type": "object",
1966
2832
  "properties": {
@@ -2629,6 +3495,21 @@
2629
3495
  "x-immutable": true,
2630
3496
  "description": "(Immutable) FK \u2192 Company document ID. Scopes all queries."
2631
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
+ },
2632
3513
  "orderNumber": {
2633
3514
  "type": "string",
2634
3515
  "readOnly": true,
@@ -3218,6 +4099,7 @@
3218
4099
  "id": "bk_abc123def456",
3219
4100
  "uid": "user_u8x92kqm",
3220
4101
  "companyId": "comp_xyz789",
4102
+ "siteId": null,
3221
4103
  "orderNumber": "ORD-2026-0042",
3222
4104
  "status": "status",
3223
4105
  "paymentStatus": "paymentStatus",
@@ -3550,6 +4432,31 @@
3550
4432
  ],
3551
4433
  "description": "Payment lifecycle status (D01 amended). Used by Order, Sale/Purchase, Booking."
3552
4434
  },
4435
+ "pending-issue": {
4436
+ "type": "string",
4437
+ "enum": [
4438
+ "PAYMENT_PROOF_PENDING",
4439
+ "PAYMENT_PROOF_REJECTED",
4440
+ "AMOUNT_DISCREPANCY",
4441
+ "RETURN_UNRESOLVED",
4442
+ "OVERDUE_DELIVERY",
4443
+ "SESSION_OVERDUE",
4444
+ "PAYMENT_INCOMPLETE",
4445
+ "CANCELLATION_REQUESTED",
4446
+ "UNREFUNDED_CANCELLATION",
4447
+ "UPCOMING_UNPAID",
4448
+ "NO_SHOW_UNRESOLVED"
4449
+ ],
4450
+ "description": "Specific detected conditions on an Order or Booking requiring human action (D39). Stored as an array \u2014 multiple issues can be active simultaneously. Server-owned.",
4451
+ "x-note": "Shared: PAYMENT_PROOF_PENDING (proof uploaded, awaiting review), PAYMENT_PROOF_REJECTED (proof rejected, needs resubmission), AMOUNT_DISCREPANCY (amountPaid does not reconcile with amount/paymentStatus). Order-only: RETURN_UNRESOLVED (return/exchange stuck), OVERDUE_DELIVERY (estimatedDeliveryDate past, not yet delivered). Booking-only: SESSION_OVERDUE (session date passed, status not updated), PAYMENT_INCOMPLETE (sessions completed but payment not recorded), CANCELLATION_REQUESTED (cancellation not actioned), UNREFUNDED_CANCELLATION (cancelled with amountPaid > 0 and no refund), UPCOMING_UNPAID (session within 48h, payment not recorded), NO_SHOW_UNRESOLVED (no-show without follow-up).",
4452
+ "x-see": {
4453
+ "decisions": [
4454
+ "D39"
4455
+ ]
4456
+ },
4457
+ "x-when": "Set and cleared by server automation or triggers. Never affects pendingOrdersCount or pendingBookingsCount \u2014 orthogonal to urgency. Multiple values can be active at once (stored as PendingIssue[] on Order/Booking).",
4458
+ "x-status": "proposed"
4459
+ },
3553
4460
  "return-status": {
3554
4461
  "type": "string",
3555
4462
  "enum": [
@@ -3688,6 +4595,327 @@
3688
4595
  ],
3689
4596
  "description": "Per-date/per-slot booking session status (D19). Dashboard is sole writer; Mobile is read-only."
3690
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
+ },
4825
+ "site-payment": {
4826
+ "type": "object",
4827
+ "properties": {
4828
+ "id": {
4829
+ "readOnly": true,
4830
+ "description": "(Read-only) Firestore document ID, auto-generated.",
4831
+ "type": [
4832
+ "string",
4833
+ "null"
4834
+ ]
4835
+ },
4836
+ "companyId": {
4837
+ "type": "string",
4838
+ "x-immutable": true,
4839
+ "description": "(Immutable) FK \u2192 Company document ID."
4840
+ },
4841
+ "siteId": {
4842
+ "type": "string",
4843
+ "x-immutable": true,
4844
+ "description": "(Immutable) FK \u2192 Site document ID (D40 sub-tenant scope)."
4845
+ },
4846
+ "contact": {
4847
+ "type": "string",
4848
+ "description": "Email or E.164 phone number. Matches AllowedUser.contact."
4849
+ },
4850
+ "sessionId": {
4851
+ "type": "string",
4852
+ "description": "Payment provider checkout session ID (e.g. Wave). Usable for dedup across dual writes."
4853
+ },
4854
+ "transactionId": {
4855
+ "type": "string",
4856
+ "description": "Payment provider transaction ID."
4857
+ },
4858
+ "tier": {
4859
+ "type": "string",
4860
+ "description": "Access tier. Free string per site (ING-304 open question)."
4861
+ },
4862
+ "amount": {
4863
+ "type": "number",
4864
+ "description": "Amount paid. Generalized from amount_xof per D40 decision."
4865
+ },
4866
+ "currency": {
4867
+ "default": "XOF",
4868
+ "description": "Currency code (ISO 4217). Defaults to XOF for legacy SR-Single parity.",
4869
+ "type": "string"
4870
+ },
4871
+ "paidAt": {
4872
+ "$ref": "#/definitions/firestore-timestamp",
4873
+ "description": "RFC3339Nano UTC when payment was completed."
4874
+ }
4875
+ },
4876
+ "required": [
4877
+ "companyId",
4878
+ "siteId",
4879
+ "contact",
4880
+ "sessionId",
4881
+ "transactionId",
4882
+ "tier",
4883
+ "amount",
4884
+ "currency",
4885
+ "paidAt"
4886
+ ],
4887
+ "additionalProperties": false,
4888
+ "description": "SitePayment model (D40 / ING-304). Collection: companies/{companyId}/sites/{siteId}/payments/{paymentId}. Immutable append-only transaction ledger. Distinct from AllowedUser (access state) and CustomerPayment (D22 \u2014 customer-level billing).",
4889
+ "example": {
4890
+ "id": null,
4891
+ "companyId": "comp_xyz789",
4892
+ "siteId": "sit_ref123",
4893
+ "contact": "contact",
4894
+ "sessionId": "ses_ref123",
4895
+ "transactionId": "tra_ref123",
4896
+ "tier": "Gold",
4897
+ "amount": 45000,
4898
+ "currency": "XOF",
4899
+ "paidAt": "pai_ref123"
4900
+ }
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
+ },
3691
4919
  "ticket": {
3692
4920
  "type": "object",
3693
4921
  "properties": {
@@ -3846,24 +5074,6 @@
3846
5074
  ],
3847
5075
  "description": "Event ticket status (D32). VALID = active and unused."
3848
5076
  },
3849
- "attention-status": {
3850
- "type": "string",
3851
- "enum": [
3852
- "ACTIVE",
3853
- "STALE",
3854
- "ON_HOLD",
3855
- "ESCALATED"
3856
- ],
3857
- "description": "Operational attention status for Order and Booking (D39). Controls home-page queue assignment. Server-owned \u2014 set via triggers or callable fn only.",
3858
- "x-note": "ACTIVE = normal urgent queue (default). STALE = idle past threshold, moved to review queue \u2014 excluded from pendingOrdersCount. ON_HOLD = intentionally paused by manager via callable fn. ESCALATED = ignored past second threshold.",
3859
- "x-see": {
3860
- "decisions": [
3861
- "D39"
3862
- ]
3863
- },
3864
- "x-when": "Set by server automation or callable fn. Mobile and Dashboard read this field; never write it directly. ON_HOLD is the only human-initiated transition \u2014 goes through a dedicated callable function.",
3865
- "x-status": "proposed"
3866
- },
3867
5077
  "payment-summary": {
3868
5078
  "type": "object",
3869
5079
  "properties": {
@@ -4025,31 +5235,6 @@
4025
5235
  "additionalProperties": false,
4026
5236
  "description": "Aggregated payment totals by method for a given period. Collection: companies/{companyId}/paymentSummaries/{period}. Server-owned \u2014 all fields are set by Cloud Function triggers on Order.payments[] and CustomerPayment writes. Clients must never write to this collection.",
4027
5237
  "x-status": "proposed"
4028
- },
4029
- "pending-issue": {
4030
- "type": "string",
4031
- "enum": [
4032
- "PAYMENT_PROOF_PENDING",
4033
- "PAYMENT_PROOF_REJECTED",
4034
- "AMOUNT_DISCREPANCY",
4035
- "RETURN_UNRESOLVED",
4036
- "OVERDUE_DELIVERY",
4037
- "SESSION_OVERDUE",
4038
- "PAYMENT_INCOMPLETE",
4039
- "CANCELLATION_REQUESTED",
4040
- "UNREFUNDED_CANCELLATION",
4041
- "UPCOMING_UNPAID",
4042
- "NO_SHOW_UNRESOLVED"
4043
- ],
4044
- "description": "Specific detected conditions on an Order or Booking requiring human action (D39). Stored as an array \u2014 multiple issues can be active simultaneously. Server-owned.",
4045
- "x-note": "Shared: PAYMENT_PROOF_PENDING (proof uploaded, awaiting review), PAYMENT_PROOF_REJECTED (proof rejected, needs resubmission), AMOUNT_DISCREPANCY (amountPaid does not reconcile with amount/paymentStatus). Order-only: RETURN_UNRESOLVED (return/exchange stuck), OVERDUE_DELIVERY (estimatedDeliveryDate past, not yet delivered). Booking-only: SESSION_OVERDUE (session date passed, status not updated), PAYMENT_INCOMPLETE (sessions completed but payment not recorded), CANCELLATION_REQUESTED (cancellation not actioned), UNREFUNDED_CANCELLATION (cancelled with amountPaid > 0 and no refund), UPCOMING_UNPAID (session within 48h, payment not recorded), NO_SHOW_UNRESOLVED (no-show without follow-up).",
4046
- "x-see": {
4047
- "decisions": [
4048
- "D39"
4049
- ]
4050
- },
4051
- "x-when": "Set and cleared by server automation or triggers. Never affects pendingOrdersCount or pendingBookingsCount \u2014 orthogonal to urgency. Multiple values can be active at once (stored as PendingIssue[] on Order/Booking).",
4052
- "x-status": "proposed"
4053
5238
  }
4054
5239
  }
4055
5240
  }