@ingenx-io/valets-schema-mcp-server 0.2.3 → 0.2.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 (76) hide show
  1. package/README.md +26 -0
  2. package/data/docs/collections/firestore-paths.md +32 -20
  3. package/data/docs/enums/app-status.md +24 -0
  4. package/data/docs/enums/attention-status.md +2 -2
  5. package/data/docs/enums/booking-status.md +2 -2
  6. package/data/docs/enums/customer-payment-status.md +2 -2
  7. package/data/docs/enums/customer-payment-target-type.md +2 -2
  8. package/data/docs/enums/delivery-type.md +2 -2
  9. package/data/docs/enums/deployment-link-type.md +2 -2
  10. package/data/docs/enums/event-status.md +2 -2
  11. package/data/docs/enums/fulfillment-status.md +2 -2
  12. package/data/docs/enums/loyalty-transaction-type.md +2 -2
  13. package/data/docs/enums/notification-channel.md +2 -2
  14. package/data/docs/enums/notification-entity-type.md +2 -2
  15. package/data/docs/enums/notification-status.md +2 -2
  16. package/data/docs/enums/order-status.md +2 -2
  17. package/data/docs/enums/outbound-message-format.md +2 -2
  18. package/data/docs/enums/outbound-message-purpose.md +2 -2
  19. package/data/docs/enums/outbound-message-status.md +2 -2
  20. package/data/docs/enums/payment-method.md +2 -2
  21. package/data/docs/enums/payment-proof-status.md +2 -2
  22. package/data/docs/enums/payment-status.md +2 -2
  23. package/data/docs/enums/pending-issue.md +2 -2
  24. package/data/docs/enums/return-status.md +2 -2
  25. package/data/docs/enums/session-status.md +2 -2
  26. package/data/docs/enums/site-status.md +2 -2
  27. package/data/docs/enums/stocktake-frequency.md +2 -2
  28. package/data/docs/enums/stocktake-item-status.md +2 -2
  29. package/data/docs/enums/stocktake-status.md +2 -2
  30. package/data/docs/enums/ticket-status.md +2 -2
  31. package/data/docs/enums/waba-label.md +2 -2
  32. package/data/docs/enums/whatsapp-button-sub-type.md +2 -2
  33. package/data/docs/enums/whatsapp-template-component.md +2 -2
  34. package/data/docs/enums/whatsapp-template-status.md +2 -2
  35. package/data/docs/index.md +9 -5
  36. package/data/docs/models/allowed-user.md +7 -7
  37. package/data/docs/models/analytics-backfill.md +7 -7
  38. package/data/docs/models/analytics-daily.md +6 -6
  39. package/data/docs/models/analytics-event.md +7 -7
  40. package/data/docs/models/analytics-hourly.md +6 -6
  41. package/data/docs/models/app-payment.md +200 -0
  42. package/data/docs/models/app.md +561 -0
  43. package/data/docs/models/booking-version.md +2 -2
  44. package/data/docs/models/booking.md +127 -127
  45. package/data/docs/models/customer-payment-allocation.md +20 -20
  46. package/data/docs/models/customer-payment.md +23 -23
  47. package/data/docs/models/customer.md +11 -11
  48. package/data/docs/models/event.md +22 -22
  49. package/data/docs/models/loyalty-config.md +4 -4
  50. package/data/docs/models/loyalty-reward.md +3 -3
  51. package/data/docs/models/loyalty-status.md +6 -6
  52. package/data/docs/models/loyalty-transaction.md +2 -2
  53. package/data/docs/models/magic-link-request.md +9 -9
  54. package/data/docs/models/metrics-current.md +169 -37
  55. package/data/docs/models/metrics-daily.md +172 -40
  56. package/data/docs/models/metrics-monthly.md +172 -40
  57. package/data/docs/models/notification-record.md +3 -3
  58. package/data/docs/models/order-item.md +6 -6
  59. package/data/docs/models/order.md +78 -78
  60. package/data/docs/models/sale.md +18 -18
  61. package/data/docs/models/site-payment.md +2 -2
  62. package/data/docs/models/site.md +2 -2
  63. package/data/docs/models/stocktake-item.md +4 -4
  64. package/data/docs/models/stocktake.md +5 -5
  65. package/data/docs/models/ticket.md +3 -3
  66. package/data/docs/models/user.md +249 -0
  67. package/data/docs/models/whatsapp-inbound-message.md +2 -2
  68. package/data/docs/models/whatsapp-outbound-lifecycle-event.md +2 -2
  69. package/data/docs/models/whatsapp-outbound-message.md +6 -6
  70. package/data/docs/models/whatsapp-template.md +2 -2
  71. package/data/static/cookbook.json +150 -0
  72. package/data/static/llms.txt +179 -33
  73. package/data/static/openapi.yaml +626 -60
  74. package/data/static/schemas.json +680 -69
  75. package/index.js +32 -0
  76. 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-05-27T15:51:06.117627+00:00",
4
+ "generated": "2026-05-30T12:42:51.842715+00:00",
5
5
  "schemas": {
6
6
  "allowed-user": {
7
7
  "type": "object",
@@ -19,10 +19,10 @@
19
19
  "x-immutable": true,
20
20
  "description": "(Immutable) FK \u2192 Company document ID."
21
21
  },
22
- "siteId": {
22
+ "appId": {
23
23
  "type": "string",
24
24
  "x-immutable": true,
25
- "description": "(Immutable) FK \u2192 Site document ID (D40 sub-tenant scope)."
25
+ "description": "(Immutable) FK \u2192 App document ID (D40 sub-tenant scope, renamed per D44)."
26
26
  },
27
27
  "contact": {
28
28
  "type": "string",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "required": [
54
54
  "companyId",
55
- "siteId",
55
+ "appId",
56
56
  "contact",
57
57
  "tier",
58
58
  "amount",
@@ -61,11 +61,11 @@
61
61
  "paidAt"
62
62
  ],
63
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.",
64
+ "description": "AllowedUser model (D40 / ING-304). Collection: companies/{companyId}/apps/{appId}/allowed_users/{contactId}. Authoritative paid-access allowlist. Upsert semantics \u2014 tier upgrades overwrite. Source of truth for access checks and referral code resolution.",
65
65
  "example": {
66
66
  "id": null,
67
67
  "companyId": "comp_xyz789",
68
- "siteId": "sit_ref123",
68
+ "appId": "app_ref123",
69
69
  "contact": "contact",
70
70
  "tier": "Gold",
71
71
  "amount": 45000,
@@ -90,7 +90,7 @@
90
90
  "x-immutable": true,
91
91
  "description": "(Immutable) FK \u2192 Company document ID."
92
92
  },
93
- "siteId": {
93
+ "appId": {
94
94
  "type": "string",
95
95
  "x-immutable": true,
96
96
  "description": "(Immutable) FK \u2192 Site document ID (D40)."
@@ -180,7 +180,7 @@
180
180
  },
181
181
  "required": [
182
182
  "companyId",
183
- "siteId",
183
+ "appId",
184
184
  "status",
185
185
  "from",
186
186
  "to",
@@ -191,11 +191,11 @@
191
191
  "startedAt"
192
192
  ],
193
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.",
194
+ "description": "AnalyticsBackfill run (D42 / ING-304). Collection: companies/{companyId}/apps/{appId}/analytics_backfills/{runId}. Tracks admin-triggered rollup backfill jobs; supports dry-run.",
195
195
  "example": {
196
196
  "id": null,
197
197
  "companyId": "comp_xyz789",
198
- "siteId": "sit_ref123",
198
+ "appId": "app_ref123",
199
199
  "status": "pending",
200
200
  "from": "from",
201
201
  "to": "to",
@@ -297,7 +297,7 @@
297
297
  "x-immutable": true,
298
298
  "description": "(Immutable) FK \u2192 Company document ID."
299
299
  },
300
- "siteId": {
300
+ "appId": {
301
301
  "type": "string",
302
302
  "x-immutable": true,
303
303
  "description": "(Immutable) FK \u2192 Site document ID (D40)."
@@ -335,12 +335,12 @@
335
335
  "errors",
336
336
  "eventCounts",
337
337
  "companyId",
338
- "siteId",
338
+ "appId",
339
339
  "date",
340
340
  "computedAt"
341
341
  ],
342
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.",
343
+ "description": "AnalyticsDaily rollup (D42 / ING-304). Collection: companies/{companyId}/apps/{appId}/analytics_daily/{YYYY-MM-DD}. Idempotent set/merge \u2014 reruns overwrite. Fall back to raw analytics_events for uncovered slices.",
344
344
  "example": {
345
345
  "totalEvents": 0,
346
346
  "pageViews": 0,
@@ -354,7 +354,7 @@
354
354
  "eventCounts": {},
355
355
  "id": null,
356
356
  "companyId": "comp_xyz789",
357
- "siteId": "sit_ref123",
357
+ "appId": "app_ref123",
358
358
  "date": "2026-02-15",
359
359
  "computedAt": "computedAt",
360
360
  "sourceEventCount": null
@@ -376,10 +376,10 @@
376
376
  "x-immutable": true,
377
377
  "description": "(Immutable) FK \u2192 Company document ID."
378
378
  },
379
- "siteId": {
379
+ "appId": {
380
380
  "type": "string",
381
381
  "x-immutable": true,
382
- "description": "(Immutable) FK \u2192 Site document ID (D40 sub-tenant scope)."
382
+ "description": "(Immutable) FK \u2192 App document ID (D40 sub-tenant scope, renamed per D44)."
383
383
  },
384
384
  "eventId": {
385
385
  "type": "string",
@@ -536,7 +536,7 @@
536
536
  },
537
537
  "required": [
538
538
  "companyId",
539
- "siteId",
539
+ "appId",
540
540
  "eventId",
541
541
  "eventName",
542
542
  "timestamp",
@@ -547,11 +547,11 @@
547
547
  "context"
548
548
  ],
549
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.",
550
+ "description": "AnalyticsEvent model (D40 / ING-304). Collection: companies/{companyId}/apps/{appId}/analytics_events/{eventId}. Append-only, immutable product/behavior event stream. Event names are free strings; canonical vocabulary in CANONICAL_ANALYTICS_EVENT_NAMES.",
551
551
  "example": {
552
552
  "id": null,
553
553
  "companyId": "comp_xyz789",
554
- "siteId": "sit_ref123",
554
+ "appId": "app_ref123",
555
555
  "eventId": "eve_ref123",
556
556
  "eventName": "eventName",
557
557
  "timestamp": "timestamp",
@@ -645,7 +645,7 @@
645
645
  "x-immutable": true,
646
646
  "description": "(Immutable) FK \u2192 Company document ID."
647
647
  },
648
- "siteId": {
648
+ "appId": {
649
649
  "type": "string",
650
650
  "x-immutable": true,
651
651
  "description": "(Immutable) FK \u2192 Site document ID (D40)."
@@ -690,33 +690,356 @@
690
690
  "errors",
691
691
  "eventCounts",
692
692
  "companyId",
693
- "siteId",
693
+ "appId",
694
694
  "date",
695
695
  "hour",
696
696
  "computedAt"
697
697
  ],
698
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.",
699
+ "description": "AnalyticsHourly rollup (D42 / ING-304). Collection: companies/{companyId}/apps/{appId}/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
+ "appId": "app_ref123",
714
+ "date": "2026-02-15",
715
+ "hour": 0,
716
+ "computedAt": "computedAt",
717
+ "sourceEventCount": null
718
+ }
719
+ },
720
+ "app": {
721
+ "type": "object",
722
+ "properties": {
723
+ "id": {
724
+ "readOnly": true,
725
+ "description": "(Read-only) Firestore document ID, auto-generated.",
726
+ "type": [
727
+ "string",
728
+ "null"
729
+ ]
730
+ },
731
+ "companyId": {
732
+ "type": "string",
733
+ "x-immutable": true,
734
+ "description": "(Immutable) FK \u2192 Company document ID. Scopes all app sub-collections."
735
+ },
736
+ "name": {
737
+ "type": "string",
738
+ "description": "Human-readable app name shown in dashboards."
739
+ },
740
+ "description": {
741
+ "description": "Optional freeform description.",
742
+ "type": [
743
+ "string",
744
+ "null"
745
+ ]
746
+ },
747
+ "status": {
748
+ "$ref": "#/definitions/app-status",
749
+ "description": "Lifecycle status (D41/D44). Clients filter by ACTIVE.",
750
+ "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.",
751
+ "x-see": {
752
+ "decisions": [
753
+ "D41",
754
+ "D44"
755
+ ]
756
+ },
757
+ "x-when": "Written by operators via dashboard or by server triggers (expiration). Clients filter by ACTIVE."
758
+ },
759
+ "deploymentLinks": {
760
+ "type": "array",
761
+ "items": {
762
+ "type": "object",
763
+ "properties": {
764
+ "label": {
765
+ "type": "string",
766
+ "description": "Human-readable label shown in dashboards (e.g. \"Production\", \"Staging\")."
767
+ },
768
+ "url": {
769
+ "type": "string",
770
+ "description": "Absolute URL or store link."
771
+ },
772
+ "type": {
773
+ "$ref": "#/definitions/deployment-link-type",
774
+ "description": "Link category \u2014 drives icon/handler selection.",
775
+ "x-note": "Lowercase by convention \u2014 deployment link types are display-oriented labels, not lifecycle states.",
776
+ "x-see": {
777
+ "decisions": [
778
+ "D41"
779
+ ]
780
+ }
781
+ },
782
+ "isPrimary": {
783
+ "description": "If true, this link is used as the canonical deployment URL for the App.",
784
+ "type": "boolean"
785
+ }
786
+ },
787
+ "required": [
788
+ "label",
789
+ "url",
790
+ "type"
791
+ ],
792
+ "additionalProperties": false,
793
+ "description": "Deployment link entry embedded on App.deploymentLinks[] (D41)."
794
+ },
795
+ "description": "Ordered list of deployment URLs (web, mobile, PWA, store links)."
796
+ },
797
+ "expiresAt": {
798
+ "anyOf": [
799
+ {
800
+ "$ref": "#/definitions/firestore-timestamp"
801
+ },
802
+ {
803
+ "type": "null"
804
+ }
805
+ ],
806
+ "description": "Optional expiration timestamp. When set and elapsed, `isExpired` flips true and status typically moves to EXPIRED."
807
+ },
808
+ "isExpired": {
809
+ "readOnly": true,
810
+ "description": "(Read-only) Derived \u2014 true when `expiresAt` is in the past. Maintained by server trigger.",
811
+ "type": [
812
+ "boolean",
813
+ "null"
814
+ ]
815
+ },
816
+ "createdAt": {
817
+ "anyOf": [
818
+ {
819
+ "$ref": "#/definitions/firestore-timestamp"
820
+ },
821
+ {
822
+ "type": "null"
823
+ }
824
+ ],
825
+ "readOnly": true,
826
+ "description": "(Read-only) Server-generated creation timestamp."
827
+ },
828
+ "updatedAt": {
829
+ "anyOf": [
830
+ {
831
+ "$ref": "#/definitions/firestore-timestamp"
832
+ },
833
+ {
834
+ "type": "null"
835
+ }
836
+ ],
837
+ "readOnly": true,
838
+ "description": "(Read-only) Server-generated update timestamp."
839
+ },
840
+ "createdBy": {
841
+ "type": "string",
842
+ "x-immutable": true,
843
+ "description": "(Immutable) FK \u2192 User/staff UID who created the app."
844
+ },
845
+ "analyticsEnabled": {
846
+ "type": "boolean",
847
+ "description": "Feature flag \u2014 when false, clients should not emit analytics events for this app."
848
+ },
849
+ "lastAnalyticsSync": {
850
+ "anyOf": [
851
+ {
852
+ "$ref": "#/definitions/firestore-timestamp"
853
+ },
854
+ {
855
+ "type": "null"
856
+ }
857
+ ],
858
+ "readOnly": true,
859
+ "description": "(Read-only) Last time the analytics rollup pipeline refreshed `cachedMetrics`."
860
+ },
861
+ "cachedMetrics": {
862
+ "readOnly": true,
863
+ "denormalized": true,
864
+ "x-note": "Updated by the rollup pipeline (D42). Readers should treat this as a hint; authoritative counts live in analytics_daily/analytics_hourly.",
865
+ "x-see": {
866
+ "decisions": [
867
+ "D41",
868
+ "D42"
869
+ ]
870
+ },
871
+ "description": "(Read-only, Denormalized) Cached metrics snapshot for quick dashboard rendering.",
872
+ "type": [
873
+ "object",
874
+ "null"
875
+ ],
876
+ "properties": {
877
+ "totalEvents": {
878
+ "type": "integer",
879
+ "minimum": -9007199254740991,
880
+ "maximum": 9007199254740991,
881
+ "description": "All-time event count cached on the App."
882
+ },
883
+ "totalPageViews": {
884
+ "type": "integer",
885
+ "minimum": -9007199254740991,
886
+ "maximum": 9007199254740991,
887
+ "description": "All-time `page_view` + `screen_view` count."
888
+ },
889
+ "totalSessions": {
890
+ "type": "integer",
891
+ "minimum": -9007199254740991,
892
+ "maximum": 9007199254740991,
893
+ "description": "All-time distinct session count."
894
+ },
895
+ "totalOrders": {
896
+ "type": "integer",
897
+ "minimum": -9007199254740991,
898
+ "maximum": 9007199254740991,
899
+ "description": "All-time order count attributed to this app (via `Order.appId`, D43)."
900
+ },
901
+ "lastEventAt": {
902
+ "$ref": "#/definitions/firestore-timestamp",
903
+ "description": "Timestamp of the most recent analytics event seen for this app."
904
+ }
905
+ },
906
+ "required": [
907
+ "totalEvents",
908
+ "totalPageViews",
909
+ "totalSessions",
910
+ "totalOrders"
911
+ ],
912
+ "additionalProperties": false
913
+ }
914
+ },
915
+ "required": [
916
+ "companyId",
917
+ "name",
918
+ "status",
919
+ "deploymentLinks",
920
+ "createdBy",
921
+ "analyticsEnabled"
922
+ ],
923
+ "additionalProperties": false,
924
+ "description": "App model (D41 / ING-304). Collection: companies/{companyId}/apps/{appId}. 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.",
925
+ "example": {
926
+ "id": null,
927
+ "companyId": "comp_xyz789",
928
+ "name": "Amadou Diallo",
929
+ "description": null,
930
+ "status": "status",
931
+ "deploymentLinks": [
932
+ {
933
+ "label": "label",
934
+ "url": "https://storage.example.com/url.jpg",
935
+ "type": "phone"
936
+ }
937
+ ],
938
+ "expiresAt": "expiresAt",
939
+ "isExpired": null,
940
+ "createdAt": "createdAt",
941
+ "updatedAt": "updatedAt",
942
+ "createdBy": "staff_k0f1",
943
+ "analyticsEnabled": true,
944
+ "lastAnalyticsSync": "lastAnalyticsSync",
945
+ "cachedMetrics": null
946
+ }
947
+ },
948
+ "app-payment": {
949
+ "type": "object",
950
+ "properties": {
951
+ "id": {
952
+ "readOnly": true,
953
+ "description": "(Read-only) Firestore document ID, auto-generated.",
954
+ "type": [
955
+ "string",
956
+ "null"
957
+ ]
958
+ },
959
+ "companyId": {
960
+ "type": "string",
961
+ "x-immutable": true,
962
+ "description": "(Immutable) FK \u2192 Company document ID."
963
+ },
964
+ "appId": {
965
+ "type": "string",
966
+ "x-immutable": true,
967
+ "description": "(Immutable) FK \u2192 App document ID (D40 sub-tenant scope, renamed from siteId per D44)."
968
+ },
969
+ "contact": {
970
+ "type": "string",
971
+ "description": "Email or E.164 phone number. Matches AllowedUser.contact."
972
+ },
973
+ "sessionId": {
974
+ "type": "string",
975
+ "description": "Payment provider checkout session ID (e.g. Wave). Usable for dedup across dual writes."
976
+ },
977
+ "transactionId": {
978
+ "type": "string",
979
+ "description": "Payment provider transaction ID."
980
+ },
981
+ "tier": {
982
+ "type": "string",
983
+ "description": "Access tier. Free string per app (ING-304 open question)."
984
+ },
985
+ "amount": {
986
+ "type": "number",
987
+ "description": "Amount paid. Generalized from amount_xof per D40 decision."
988
+ },
989
+ "currency": {
990
+ "default": "XOF",
991
+ "description": "Currency code (ISO 4217). Defaults to XOF for legacy SR-Single parity.",
992
+ "type": "string"
993
+ },
994
+ "paidAt": {
995
+ "$ref": "#/definitions/firestore-timestamp",
996
+ "description": "RFC3339Nano UTC when payment was completed."
997
+ }
998
+ },
999
+ "required": [
1000
+ "companyId",
1001
+ "appId",
1002
+ "contact",
1003
+ "sessionId",
1004
+ "transactionId",
1005
+ "tier",
1006
+ "amount",
1007
+ "currency",
1008
+ "paidAt"
1009
+ ],
1010
+ "additionalProperties": false,
1011
+ "description": "AppPayment model (D40 / ING-304). Collection: companies/{companyId}/apps/{appId}/payments/{paymentId}. Immutable append-only transaction ledger. Distinct from AllowedUser (access state) and CustomerPayment (D22 \u2014 customer-level billing).",
700
1012
  "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
1013
  "id": null,
712
1014
  "companyId": "comp_xyz789",
713
- "siteId": "sit_ref123",
714
- "date": "2026-02-15",
715
- "hour": 0,
716
- "computedAt": "computedAt",
717
- "sourceEventCount": null
1015
+ "appId": "app_ref123",
1016
+ "contact": "contact",
1017
+ "sessionId": "ses_ref123",
1018
+ "transactionId": "tra_ref123",
1019
+ "tier": "Gold",
1020
+ "amount": 45000,
1021
+ "currency": "XOF",
1022
+ "paidAt": "pai_ref123"
718
1023
  }
719
1024
  },
1025
+ "app-status": {
1026
+ "type": "string",
1027
+ "enum": [
1028
+ "ACTIVE",
1029
+ "INACTIVE",
1030
+ "EXPIRED",
1031
+ "ARCHIVED"
1032
+ ],
1033
+ "description": "Lifecycle status for an App (formerly Site \u2014 renamed per decision #40 / D44). Drives whether the app is reachable and whether analytics/payments flow.",
1034
+ "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.",
1035
+ "x-see": {
1036
+ "decisions": [
1037
+ "D41",
1038
+ "D44"
1039
+ ]
1040
+ },
1041
+ "x-when": "Written by operators via dashboard or by server triggers (expiration). Clients filter by ACTIVE."
1042
+ },
720
1043
  "attention-status": {
721
1044
  "type": "string",
722
1045
  "enum": [
@@ -826,7 +1149,7 @@
826
1149
  },
827
1150
  "_deletedAt": {
828
1151
  "$ref": "#/definitions/firestore-timestamp",
829
- "description": "Firestore Timestamp serialized representation"
1152
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
830
1153
  },
831
1154
  "_deletedBy": {
832
1155
  "description": "FK \u2192 User/staff UID who soft-deleted this item.",
@@ -834,7 +1157,7 @@
834
1157
  },
835
1158
  "_lastModifiedAt": {
836
1159
  "$ref": "#/definitions/firestore-timestamp",
837
- "description": "Firestore Timestamp serialized representation"
1160
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
838
1161
  },
839
1162
  "_lastModifiedBy": {
840
1163
  "description": "FK \u2192 User/staff UID who last modified this item.",
@@ -916,7 +1239,7 @@
916
1239
  },
917
1240
  "additionalProperties": {
918
1241
  "$ref": "#/definitions/firestore-timestamp",
919
- "description": "Firestore Timestamp serialized representation"
1242
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
920
1243
  }
921
1244
  },
922
1245
  "slotStatusUpdatedBy": {
@@ -934,7 +1257,7 @@
934
1257
  },
935
1258
  "_deletedAt": {
936
1259
  "$ref": "#/definitions/firestore-timestamp",
937
- "description": "Firestore Timestamp serialized representation"
1260
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
938
1261
  },
939
1262
  "_deletedBy": {
940
1263
  "description": "FK \u2192 User/staff UID who soft-deleted this item.",
@@ -942,7 +1265,7 @@
942
1265
  },
943
1266
  "_lastModifiedAt": {
944
1267
  "$ref": "#/definitions/firestore-timestamp",
945
- "description": "Firestore Timestamp serialized representation"
1268
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
946
1269
  },
947
1270
  "_lastModifiedBy": {
948
1271
  "description": "FK \u2192 User/staff UID who last modified this item.",
@@ -1151,7 +1474,7 @@
1151
1474
  },
1152
1475
  "_deletedAt": {
1153
1476
  "$ref": "#/definitions/firestore-timestamp",
1154
- "description": "Firestore Timestamp serialized representation"
1477
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
1155
1478
  },
1156
1479
  "_deletedBy": {
1157
1480
  "description": "FK \u2192 User/staff UID who soft-deleted this item.",
@@ -1159,7 +1482,7 @@
1159
1482
  },
1160
1483
  "_lastModifiedAt": {
1161
1484
  "$ref": "#/definitions/firestore-timestamp",
1162
- "description": "Firestore Timestamp serialized representation"
1485
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
1163
1486
  },
1164
1487
  "_lastModifiedBy": {
1165
1488
  "description": "FK \u2192 User/staff UID who last modified this item.",
@@ -1243,7 +1566,7 @@
1243
1566
  "type": "null"
1244
1567
  }
1245
1568
  ],
1246
- "description": "Firestore Timestamp serialized representation"
1569
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
1247
1570
  },
1248
1571
  "paymentProofUrl": {
1249
1572
  "description": "URL to uploaded payment proof image/document.",
@@ -1272,7 +1595,7 @@
1272
1595
  "type": "null"
1273
1596
  }
1274
1597
  ],
1275
- "description": "Firestore Timestamp serialized representation"
1598
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
1276
1599
  },
1277
1600
  "paymentProofAddedBy": {
1278
1601
  "description": "FK \u2192 User/staff UID who uploaded the payment proof.",
@@ -1297,7 +1620,7 @@
1297
1620
  "type": "null"
1298
1621
  }
1299
1622
  ],
1300
- "description": "Firestore Timestamp serialized representation"
1623
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
1301
1624
  },
1302
1625
  "paymentProofRejectionReason": {
1303
1626
  "type": [
@@ -1334,7 +1657,7 @@
1334
1657
  "type": "null"
1335
1658
  }
1336
1659
  ],
1337
- "description": "Firestore Timestamp serialized representation"
1660
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
1338
1661
  },
1339
1662
  "cancelledByRole": {
1340
1663
  "type": [
@@ -1656,7 +1979,7 @@
1656
1979
  "properties": {
1657
1980
  "timestamp": {
1658
1981
  "$ref": "#/definitions/firestore-timestamp",
1659
- "description": "Firestore Timestamp serialized representation"
1982
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
1660
1983
  },
1661
1984
  "type": {
1662
1985
  "type": "string",
@@ -1815,7 +2138,7 @@
1815
2138
  },
1816
2139
  "paymentDate": {
1817
2140
  "$ref": "#/definitions/firestore-timestamp",
1818
- "description": "Firestore Timestamp serialized representation"
2141
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
1819
2142
  },
1820
2143
  "paymentMethod": {
1821
2144
  "$ref": "#/definitions/payment-method",
@@ -1983,7 +2306,7 @@
1983
2306
  "type": "null"
1984
2307
  }
1985
2308
  ],
1986
- "description": "Firestore Timestamp serialized representation"
2309
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
1987
2310
  },
1988
2311
  "createdBy": {
1989
2312
  "type": "string",
@@ -2112,7 +2435,7 @@
2112
2435
  },
2113
2436
  "startDate": {
2114
2437
  "$ref": "#/definitions/firestore-timestamp",
2115
- "description": "Firestore Timestamp serialized representation"
2438
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
2116
2439
  },
2117
2440
  "endDate": {
2118
2441
  "anyOf": [
@@ -2123,7 +2446,7 @@
2123
2446
  "type": "null"
2124
2447
  }
2125
2448
  ],
2126
- "description": "Firestore Timestamp serialized representation"
2449
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
2127
2450
  },
2128
2451
  "status": {
2129
2452
  "$ref": "#/definitions/event-status",
@@ -2727,10 +3050,10 @@
2727
3050
  "x-immutable": true,
2728
3051
  "description": "(Immutable) FK \u2192 Company document ID."
2729
3052
  },
2730
- "siteId": {
3053
+ "appId": {
2731
3054
  "type": "string",
2732
3055
  "x-immutable": true,
2733
- "description": "(Immutable) FK \u2192 Site document ID (D40 sub-tenant scope)."
3056
+ "description": "(Immutable) FK \u2192 App document ID (D40 sub-tenant scope, renamed per D44)."
2734
3057
  },
2735
3058
  "email": {
2736
3059
  "type": "string",
@@ -2796,7 +3119,7 @@
2796
3119
  },
2797
3120
  "required": [
2798
3121
  "companyId",
2799
- "siteId",
3122
+ "appId",
2800
3123
  "email",
2801
3124
  "phone",
2802
3125
  "ip",
@@ -2808,11 +3131,11 @@
2808
3131
  "requestedAt"
2809
3132
  ],
2810
3133
  "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().",
3134
+ "description": "MagicLinkRequest model (D40 / ING-304). Collection: companies/{companyId}/apps/{appId}/magic_link_requests/{requestId}. Authentication audit log \u2014 every request is logged regardless of outcome. Two-stage write: Log() then LogVerify().",
2812
3135
  "example": {
2813
3136
  "id": null,
2814
3137
  "companyId": "comp_xyz789",
2815
- "siteId": "sit_ref123",
3138
+ "appId": "app_ref123",
2816
3139
  "email": "amadou@example.com",
2817
3140
  "phone": "+225 07 00 11 22",
2818
3141
  "ip": "ip",
@@ -2971,6 +3294,65 @@
2971
3294
  "readOnly": true,
2972
3295
  "description": "(Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments. Always full recalc."
2973
3296
  },
3297
+ "escalatedOrdersCount": {
3298
+ "readOnly": true,
3299
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3300
+ "description": "(Read-only, Optional) Orders currently in an escalated state. Present once the current aggregator rewrites the document.",
3301
+ "type": [
3302
+ "integer",
3303
+ "null"
3304
+ ],
3305
+ "minimum": -9007199254740991,
3306
+ "maximum": 9007199254740991
3307
+ },
3308
+ "staleOrdersCount": {
3309
+ "readOnly": true,
3310
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3311
+ "description": "(Read-only, Optional) Orders that have been in an incomplete status beyond the stale threshold.",
3312
+ "type": [
3313
+ "integer",
3314
+ "null"
3315
+ ],
3316
+ "minimum": -9007199254740991,
3317
+ "maximum": 9007199254740991
3318
+ },
3319
+ "staleBookingsCount": {
3320
+ "readOnly": true,
3321
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3322
+ "description": "(Read-only, Optional) Bookings that have been in an incomplete status beyond the stale threshold.",
3323
+ "type": [
3324
+ "integer",
3325
+ "null"
3326
+ ],
3327
+ "minimum": -9007199254740991,
3328
+ "maximum": 9007199254740991
3329
+ },
3330
+ "onHoldOrdersCount": {
3331
+ "readOnly": true,
3332
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3333
+ "description": "(Read-only, Optional) Orders currently in an on-hold state.",
3334
+ "type": [
3335
+ "integer",
3336
+ "null"
3337
+ ],
3338
+ "minimum": -9007199254740991,
3339
+ "maximum": 9007199254740991
3340
+ },
3341
+ "paymentsByMethod": {
3342
+ "readOnly": true,
3343
+ "x-note": "Always {} until the first real payment is recorded. Present on all tenants once rewritten by current aggregator (#8).",
3344
+ "description": "(Read-only, Optional) Map of PaymentMethod \u2192 total amount for the current day. Empty map {} until first payment.",
3345
+ "type": [
3346
+ "object",
3347
+ "null"
3348
+ ],
3349
+ "propertyNames": {
3350
+ "type": "string"
3351
+ },
3352
+ "additionalProperties": {
3353
+ "type": "number"
3354
+ }
3355
+ },
2974
3356
  "computedForDay": {
2975
3357
  "type": "string",
2976
3358
  "readOnly": true,
@@ -3035,6 +3417,11 @@
3035
3417
  "lowStockItemsCount": 2,
3036
3418
  "activeRecurringPaymentsCount": 2,
3037
3419
  "monthlyRecurringRevenue": 0,
3420
+ "escalatedOrdersCount": null,
3421
+ "staleOrdersCount": null,
3422
+ "staleBookingsCount": null,
3423
+ "onHoldOrdersCount": null,
3424
+ "paymentsByMethod": null,
3038
3425
  "computedForDay": "computedForDay",
3039
3426
  "generatedAt": "generatedAt"
3040
3427
  }
@@ -3183,6 +3570,65 @@
3183
3570
  "readOnly": true,
3184
3571
  "description": "(Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments. Always full recalc."
3185
3572
  },
3573
+ "escalatedOrdersCount": {
3574
+ "readOnly": true,
3575
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3576
+ "description": "(Read-only, Optional) Orders currently in an escalated state. Present once the current aggregator rewrites the document.",
3577
+ "type": [
3578
+ "integer",
3579
+ "null"
3580
+ ],
3581
+ "minimum": -9007199254740991,
3582
+ "maximum": 9007199254740991
3583
+ },
3584
+ "staleOrdersCount": {
3585
+ "readOnly": true,
3586
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3587
+ "description": "(Read-only, Optional) Orders that have been in an incomplete status beyond the stale threshold.",
3588
+ "type": [
3589
+ "integer",
3590
+ "null"
3591
+ ],
3592
+ "minimum": -9007199254740991,
3593
+ "maximum": 9007199254740991
3594
+ },
3595
+ "staleBookingsCount": {
3596
+ "readOnly": true,
3597
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3598
+ "description": "(Read-only, Optional) Bookings that have been in an incomplete status beyond the stale threshold.",
3599
+ "type": [
3600
+ "integer",
3601
+ "null"
3602
+ ],
3603
+ "minimum": -9007199254740991,
3604
+ "maximum": 9007199254740991
3605
+ },
3606
+ "onHoldOrdersCount": {
3607
+ "readOnly": true,
3608
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3609
+ "description": "(Read-only, Optional) Orders currently in an on-hold state.",
3610
+ "type": [
3611
+ "integer",
3612
+ "null"
3613
+ ],
3614
+ "minimum": -9007199254740991,
3615
+ "maximum": 9007199254740991
3616
+ },
3617
+ "paymentsByMethod": {
3618
+ "readOnly": true,
3619
+ "x-note": "Always {} until the first real payment is recorded. Present on all tenants once rewritten by current aggregator (#8).",
3620
+ "description": "(Read-only, Optional) Map of PaymentMethod \u2192 total amount for the current day. Empty map {} until first payment.",
3621
+ "type": [
3622
+ "object",
3623
+ "null"
3624
+ ],
3625
+ "propertyNames": {
3626
+ "type": "string"
3627
+ },
3628
+ "additionalProperties": {
3629
+ "type": "number"
3630
+ }
3631
+ },
3186
3632
  "computedForDay": {
3187
3633
  "type": "string",
3188
3634
  "readOnly": true,
@@ -3253,6 +3699,11 @@
3253
3699
  "lowStockItemsCount": 2,
3254
3700
  "activeRecurringPaymentsCount": 2,
3255
3701
  "monthlyRecurringRevenue": 0,
3702
+ "escalatedOrdersCount": null,
3703
+ "staleOrdersCount": null,
3704
+ "staleBookingsCount": null,
3705
+ "onHoldOrdersCount": null,
3706
+ "paymentsByMethod": null,
3256
3707
  "computedForDay": "computedForDay",
3257
3708
  "generatedAt": "generatedAt",
3258
3709
  "date": "2026-02-15"
@@ -3402,6 +3853,65 @@
3402
3853
  "readOnly": true,
3403
3854
  "description": "(Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments. Always full recalc."
3404
3855
  },
3856
+ "escalatedOrdersCount": {
3857
+ "readOnly": true,
3858
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3859
+ "description": "(Read-only, Optional) Orders currently in an escalated state. Present once the current aggregator rewrites the document.",
3860
+ "type": [
3861
+ "integer",
3862
+ "null"
3863
+ ],
3864
+ "minimum": -9007199254740991,
3865
+ "maximum": 9007199254740991
3866
+ },
3867
+ "staleOrdersCount": {
3868
+ "readOnly": true,
3869
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3870
+ "description": "(Read-only, Optional) Orders that have been in an incomplete status beyond the stale threshold.",
3871
+ "type": [
3872
+ "integer",
3873
+ "null"
3874
+ ],
3875
+ "minimum": -9007199254740991,
3876
+ "maximum": 9007199254740991
3877
+ },
3878
+ "staleBookingsCount": {
3879
+ "readOnly": true,
3880
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3881
+ "description": "(Read-only, Optional) Bookings that have been in an incomplete status beyond the stale threshold.",
3882
+ "type": [
3883
+ "integer",
3884
+ "null"
3885
+ ],
3886
+ "minimum": -9007199254740991,
3887
+ "maximum": 9007199254740991
3888
+ },
3889
+ "onHoldOrdersCount": {
3890
+ "readOnly": true,
3891
+ "x-note": "Absent on tenants whose metrics/current was last written before aggregator v2. Backfill required for universal presence (#8).",
3892
+ "description": "(Read-only, Optional) Orders currently in an on-hold state.",
3893
+ "type": [
3894
+ "integer",
3895
+ "null"
3896
+ ],
3897
+ "minimum": -9007199254740991,
3898
+ "maximum": 9007199254740991
3899
+ },
3900
+ "paymentsByMethod": {
3901
+ "readOnly": true,
3902
+ "x-note": "Always {} until the first real payment is recorded. Present on all tenants once rewritten by current aggregator (#8).",
3903
+ "description": "(Read-only, Optional) Map of PaymentMethod \u2192 total amount for the current day. Empty map {} until first payment.",
3904
+ "type": [
3905
+ "object",
3906
+ "null"
3907
+ ],
3908
+ "propertyNames": {
3909
+ "type": "string"
3910
+ },
3911
+ "additionalProperties": {
3912
+ "type": "number"
3913
+ }
3914
+ },
3405
3915
  "computedForDay": {
3406
3916
  "type": "string",
3407
3917
  "readOnly": true,
@@ -3472,6 +3982,11 @@
3472
3982
  "lowStockItemsCount": 2,
3473
3983
  "activeRecurringPaymentsCount": 2,
3474
3984
  "monthlyRecurringRevenue": 0,
3985
+ "escalatedOrdersCount": null,
3986
+ "staleOrdersCount": null,
3987
+ "staleBookingsCount": null,
3988
+ "onHoldOrdersCount": null,
3989
+ "paymentsByMethod": null,
3475
3990
  "computedForDay": "computedForDay",
3476
3991
  "generatedAt": "generatedAt",
3477
3992
  "month": "month"
@@ -3702,7 +4217,7 @@
3702
4217
  "createdAt"
3703
4218
  ],
3704
4219
  "additionalProperties": false,
3705
- "description": "NotificationRecord \u2014 backend-written, multi-channel notification audit log (GH#48). Paths: companies/{cid}/notifications/{id} (always) and companies/{cid}/orders/{oid}/notifications/{id} (dual-write when relatedEntity.type=order). Consumers read only. Distinct from WhatsappOutboundMessage (GH#43).",
4220
+ "description": "NotificationRecord \u2014 backend-written, multi-channel notification audit log (GH#48). Paths: companies/{cid}/notification_records/{id} (always) and companies/{cid}/orders/{oid}/notification_records/{id} (dual-write when relatedEntity.type=order). Consumers read only. Distinct from WhatsappOutboundMessage (GH#43). Collection renamed from notifications \u2192 notification_records per decision #55 (2026-05-30).",
3706
4221
  "example": {
3707
4222
  "id": null,
3708
4223
  "type": "phone",
@@ -3752,16 +4267,17 @@
3752
4267
  "x-immutable": true,
3753
4268
  "description": "(Immutable) FK \u2192 Company document ID. Scopes all queries."
3754
4269
  },
3755
- "siteId": {
4270
+ "appId": {
3756
4271
  "x-immutable": true,
3757
- "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}`.",
4272
+ "x-note": "D43 / ADR-003: optional app attribution (renamed from siteId per D44). Absent/null means company-wide (legacy). When set, the order is attributed to a specific app for analytics and app-scoped dashboards. No migration \u2014 existing orders keep null. Flat path; Order continues to live at `companies/{companyId}/orders/{orderId}`.",
3758
4273
  "x-see": {
3759
4274
  "decisions": [
3760
- "D43"
4275
+ "D43",
4276
+ "D44"
3761
4277
  ]
3762
4278
  },
3763
- "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).",
3764
- "description": "(Immutable, Optional) FK \u2192 Site document ID (D43 / ADR-003). null = company-wide.",
4279
+ "x-when": "Set at order creation when the client knows which app the order originated from (e.g. SR Single, Lifesense). Leave null for legacy/company-wide orders. Composite index: (companyId, appId, createdAt).",
4280
+ "description": "(Immutable, Optional) FK \u2192 App document ID (D43 / ADR-003, renamed from siteId per D44). null = company-wide.",
3765
4281
  "type": [
3766
4282
  "string",
3767
4283
  "null"
@@ -4039,7 +4555,7 @@
4039
4555
  },
4040
4556
  "orderDate": {
4041
4557
  "$ref": "#/definitions/firestore-timestamp",
4042
- "description": "Firestore Timestamp serialized representation"
4558
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
4043
4559
  },
4044
4560
  "PROCESSING_ON": {
4045
4561
  "anyOf": [
@@ -4113,7 +4629,7 @@
4113
4629
  "type": "null"
4114
4630
  }
4115
4631
  ],
4116
- "description": "Firestore Timestamp serialized representation"
4632
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
4117
4633
  },
4118
4634
  "shippingCost": {
4119
4635
  "type": [
@@ -4243,7 +4759,7 @@
4243
4759
  },
4244
4760
  "paymentDate": {
4245
4761
  "$ref": "#/definitions/firestore-timestamp",
4246
- "description": "Firestore Timestamp serialized representation"
4762
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
4247
4763
  },
4248
4764
  "referenceNumber": {
4249
4765
  "description": "Payment reference (receipt number, transaction ID, etc.).",
@@ -4356,7 +4872,7 @@
4356
4872
  "id": "bk_abc123def456",
4357
4873
  "uid": "user_u8x92kqm",
4358
4874
  "companyId": "comp_xyz789",
4359
- "siteId": null,
4875
+ "appId": null,
4360
4876
  "orderNumber": "ORD-2026-0042",
4361
4877
  "status": "status",
4362
4878
  "paymentStatus": "paymentStatus",
@@ -4834,7 +5350,7 @@
4834
5350
  },
4835
5351
  "purchaseDate": {
4836
5352
  "$ref": "#/definitions/firestore-timestamp",
4837
- "description": "Firestore Timestamp serialized representation"
5353
+ "description": "Firestore Timestamp \u2014 Admin SDK form: { _seconds, _nanoseconds }. See types/firestore.ts for REST API v1 and client SDK serialization notes (#10)."
4838
5354
  },
4839
5355
  "createdAt": {
4840
5356
  "anyOf": [
@@ -5814,6 +6330,101 @@
5814
6330
  ],
5815
6331
  "description": "Event ticket status (D32). VALID = active and unused."
5816
6332
  },
6333
+ "user": {
6334
+ "type": "object",
6335
+ "properties": {
6336
+ "id": {
6337
+ "readOnly": true,
6338
+ "description": "(Read-only) Firebase Auth UID. Matches the Firestore document ID.",
6339
+ "type": [
6340
+ "string",
6341
+ "null"
6342
+ ]
6343
+ },
6344
+ "displayName": {
6345
+ "description": "User display name from Firebase Auth or manually set.",
6346
+ "type": [
6347
+ "string",
6348
+ "null"
6349
+ ]
6350
+ },
6351
+ "email": {
6352
+ "description": "Email address. Present for email/Google auth users.",
6353
+ "type": [
6354
+ "string",
6355
+ "null"
6356
+ ]
6357
+ },
6358
+ "phoneE164": {
6359
+ "x-note": "E.164 format with leading + (e.g. +2250777471485). Canonical phone identity field (decision #27). Distinct from wa_id (Meta conversation key). Server must normalize at write time: 8-digit CI local \u2192 prepend +225.",
6360
+ "x-when": "Set when the user authenticated via phone (WhatsApp OTP) or when a staff member's phone is known. Required for WhatsApp OTP lookup \u2014 add a Firestore composite index on phoneE164.",
6361
+ "description": "E.164-normalized phone number (e.g. +2250777471485). Canonical phone identity field; used for WhatsApp OTP lookup via indexed query.",
6362
+ "type": [
6363
+ "string",
6364
+ "null"
6365
+ ]
6366
+ },
6367
+ "companyId": {
6368
+ "x-immutable": true,
6369
+ "description": "(Immutable) FK \u2192 Company document ID. Present for staff accounts scoped to a company.",
6370
+ "type": [
6371
+ "string",
6372
+ "null"
6373
+ ]
6374
+ },
6375
+ "role": {
6376
+ "description": "Staff role within the company (e.g. admin, manager, staff). Application-defined.",
6377
+ "type": [
6378
+ "string",
6379
+ "null"
6380
+ ]
6381
+ },
6382
+ "isActive": {
6383
+ "description": "Whether this user account is active. Inactive accounts are denied access.",
6384
+ "type": [
6385
+ "boolean",
6386
+ "null"
6387
+ ]
6388
+ },
6389
+ "createdAt": {
6390
+ "anyOf": [
6391
+ {
6392
+ "$ref": "#/definitions/firestore-timestamp"
6393
+ },
6394
+ {
6395
+ "type": "null"
6396
+ }
6397
+ ],
6398
+ "readOnly": true,
6399
+ "description": "(Read-only) When the user document was created."
6400
+ },
6401
+ "updatedAt": {
6402
+ "anyOf": [
6403
+ {
6404
+ "$ref": "#/definitions/firestore-timestamp"
6405
+ },
6406
+ {
6407
+ "type": "null"
6408
+ }
6409
+ ],
6410
+ "readOnly": true,
6411
+ "description": "(Read-only) When the user document was last updated."
6412
+ }
6413
+ },
6414
+ "additionalProperties": false,
6415
+ "description": "User / staff account. Collection: users/{uid}. Document ID is always the Firebase Auth UID (decision #27). phoneE164 is the canonical E.164 phone field for OTP lookup. Legacy phone-keyed documents must be migrated to uid-keyed docs with phoneE164 set.",
6416
+ "example": {
6417
+ "id": null,
6418
+ "displayName": null,
6419
+ "email": null,
6420
+ "phoneE164": null,
6421
+ "companyId": null,
6422
+ "role": null,
6423
+ "isActive": null,
6424
+ "createdAt": "createdAt",
6425
+ "updatedAt": "updatedAt"
6426
+ }
6427
+ },
5817
6428
  "waba-label": {
5818
6429
  "type": "string",
5819
6430
  "enum": [