@ingenx-io/valets-schema-mcp-server 0.1.1 → 0.1.3

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 (43) hide show
  1. package/data/docs/collections/firestore-paths.md +49 -0
  2. package/data/docs/decisions/migrations.md +56 -0
  3. package/data/docs/decisions/summary.md +78 -0
  4. package/data/docs/enums/booking-status.md +26 -0
  5. package/data/docs/enums/customer-payment-status.md +26 -0
  6. package/data/docs/enums/customer-payment-target-type.md +23 -0
  7. package/data/docs/enums/delivery-type.md +23 -0
  8. package/data/docs/enums/event-status.md +30 -0
  9. package/data/docs/enums/fulfillment-status.md +32 -0
  10. package/data/docs/enums/loyalty-transaction-type.md +32 -0
  11. package/data/docs/enums/order-status.md +65 -0
  12. package/data/docs/enums/payment-method.md +36 -0
  13. package/data/docs/enums/payment-proof-status.md +23 -0
  14. package/data/docs/enums/payment-status.md +34 -0
  15. package/data/docs/enums/return-status.md +32 -0
  16. package/data/docs/enums/session-status.md +32 -0
  17. package/data/docs/enums/ticket-status.md +29 -0
  18. package/data/docs/index.md +102 -0
  19. package/data/docs/models/booking-version.md +295 -0
  20. package/data/docs/models/booking.md +1754 -0
  21. package/data/docs/models/customer-payment-allocation.md +336 -0
  22. package/data/docs/models/customer-payment.md +392 -0
  23. package/data/docs/models/customer.md +475 -0
  24. package/data/docs/models/event.md +386 -0
  25. package/data/docs/models/loyalty-config.md +317 -0
  26. package/data/docs/models/loyalty-reward.md +236 -0
  27. package/data/docs/models/loyalty-status.md +328 -0
  28. package/data/docs/models/loyalty-transaction.md +326 -0
  29. package/data/docs/models/metrics-current.md +532 -0
  30. package/data/docs/models/metrics-daily.md +548 -0
  31. package/data/docs/models/metrics-monthly.md +548 -0
  32. package/data/docs/models/order-item.md +361 -0
  33. package/data/docs/models/order.md +1637 -0
  34. package/data/docs/models/payment-summary.md +123 -0
  35. package/data/docs/models/sale.md +540 -0
  36. package/data/docs/models/ticket.md +405 -0
  37. package/data/docs/triggers/event-ticket-triggers.md +204 -0
  38. package/data/docs/triggers/loyalty-automation.md +123 -0
  39. package/data/static/decisions.json +966 -0
  40. package/data/static/llms.txt +1056 -0
  41. package/data/static/openapi.yaml +3090 -0
  42. package/data/static/schemas.json +4055 -0
  43. package/package.json +1 -1
@@ -0,0 +1,966 @@
1
+ {
2
+ "generated": "2026-02-28",
3
+ "source": "DATA_MODEL_DECISION_WORKBOOK.md",
4
+ "decisions": [
5
+ {
6
+ "id": "D00",
7
+ "title": "Shared schema package approach",
8
+ "decision": "Zod as source of truth → TS direct import (Dashboard/Firebase) + JSON Schema → Dart codegen (Mobile)",
9
+ "rationale": "Prerequisite for all type alignment work",
10
+ "refs": [
11
+ "CROSS_CUTTING §Insight 1",
12
+ "all alignment docs"
13
+ ],
14
+ "queue": "priority",
15
+ "status": "locked",
16
+ "date": "2026-02-26",
17
+ "owner": "User (chat)",
18
+ "notes": "Zod → JSON Schema → Dart (quicktype). See §9 for full spec.",
19
+ "schema_refs": [],
20
+ "wave": 1
21
+ },
22
+ {
23
+ "id": "D01",
24
+ "title": "Canonical PaymentStatus enum set",
25
+ "decision": "Use billing.ts set + REFUND_PROCESSING: PENDING, PAID, PARTIALLY_PAID, FAILED, REFUND_PROCESSING, REFUNDED, PARTIALLY_REFUNDED (7 values)",
26
+ "rationale": "Blocks orders/bookings/billing parity and migrations",
27
+ "refs": [
28
+ "ORDER#2",
29
+ "BOOKING_MODEL_ALIGNMENT §10"
30
+ ],
31
+ "queue": "priority",
32
+ "status": "locked",
33
+ "date": "2026-02-27",
34
+ "owner": "User (chat)",
35
+ "notes": "7-value PaymentStatus: PENDING, PAID, PARTIALLY_PAID, FAILED, REFUND_PROCESSING, REFUNDED, PARTIALLY_REFUNDED. Added REFUND_PROCESSING for refund-in-progress transitional state.",
36
+ "schema_refs": [
37
+ "payment-status"
38
+ ],
39
+ "wave": 1
40
+ },
41
+ {
42
+ "id": "D02",
43
+ "title": "Canonical PaymentMethod enum set",
44
+ "decision": "Unified set with African + global methods (CASH, CREDIT_CARD, ORANGE_MONEY, WAVE, MTN_MONEY, MOOV_MONEY, BANK_TRANSFER, PAYPAL, STRIPE, OTHER)",
45
+ "rationale": "Blocks sales/payment interoperability",
46
+ "refs": [
47
+ "SALES#1",
48
+ "SALES#7"
49
+ ],
50
+ "queue": "priority",
51
+ "status": "locked",
52
+ "date": "2026-02-27",
53
+ "owner": "User (chat)",
54
+ "notes": "Use unified payment method set (African + global)",
55
+ "schema_refs": [
56
+ "payment-method"
57
+ ],
58
+ "wave": 1
59
+ },
60
+ {
61
+ "id": "D03",
62
+ "title": "CONFIRMED vs PROCESSING for order lifecycle",
63
+ "decision": "Keep both with explicit semantics (CONFIRMED = accepted/committed; PROCESSING = in preparation/fulfillment)",
64
+ "rationale": "Active cross-platform status mismatch",
65
+ "refs": [
66
+ "ORDER#1"
67
+ ],
68
+ "queue": "priority",
69
+ "status": "locked",
70
+ "date": "2026-02-27",
71
+ "owner": "User (chat)",
72
+ "notes": "Keep both: CONFIRMED = accepted/committed, PROCESSING = in preparation. See §8 State Machine. Skip-CONFIRMED allowed but not ideal. Stale-order alerts per business config.",
73
+ "schema_refs": [
74
+ "order-status"
75
+ ],
76
+ "wave": 1
77
+ },
78
+ {
79
+ "id": "D04",
80
+ "title": "Canonical timestamp/status field casing",
81
+ "decision": "Locked: Single convention — SCREAMING_SNAKE for all lifecycle timestamp fields. Migrate Mobile camelCase.",
82
+ "rationale": "Blocks migration and serializer consistency",
83
+ "refs": [
84
+ "ORDER#3",
85
+ "EVENTS#1"
86
+ ],
87
+ "queue": "priority",
88
+ "status": "locked",
89
+ "date": "2026-02-27",
90
+ "owner": "User (chat)",
91
+ "notes": "Single convention: SCREAMING_SNAKE for all lifecycle timestamp fields. Migrate Mobile camelCase fields (processingOn → PROCESSING_ON, etc.)",
92
+ "schema_refs": [
93
+ "order"
94
+ ],
95
+ "wave": 1
96
+ },
97
+ {
98
+ "id": "D05",
99
+ "title": "Canonical purchase amount field",
100
+ "decision": "Locked: amount is only persisted field. total is a read-only getter in code (not written to Firestore).",
101
+ "rationale": "Blocks dual-write cleanup",
102
+ "refs": [
103
+ "SALES#3"
104
+ ],
105
+ "queue": "priority",
106
+ "status": "locked",
107
+ "date": "2026-02-27",
108
+ "owner": "User (chat)",
109
+ "notes": "amount is the only persisted field. total becomes a read-only getter in code (not written to Firestore). Mobile stops writing total.",
110
+ "schema_refs": [
111
+ "sale"
112
+ ],
113
+ "wave": 1
114
+ },
115
+ {
116
+ "id": "D06",
117
+ "title": "Canonical sales collection path",
118
+ "decision": "Locked (revised): Keep Firestore collection as purchases/. Use code-level alias (SalesService wrapping purchases collection). Single company-wide path; eliminate customer-scoped duplicate.",
119
+ "rationale": "Blocks data fragmentation and query strategy",
120
+ "refs": [
121
+ "SALES#4"
122
+ ],
123
+ "queue": "priority",
124
+ "status": "locked",
125
+ "date": "2026-02-27",
126
+ "owner": "User (chat)",
127
+ "notes": "Keep Firestore collection as purchases/. Use code-level alias (SalesService wrapping purchases collection). Avoids high-cost collection rename.",
128
+ "schema_refs": [
129
+ "sale"
130
+ ],
131
+ "wave": 1
132
+ },
133
+ {
134
+ "id": "D07",
135
+ "title": "Canonical loyalty transaction type values",
136
+ "decision": "SCREAMING_SNAKE past tense: EARNED, REDEEMED, ADJUSTED, EXPIRED, BONUS, REFUND",
137
+ "rationale": "Blocks dashboard/mobile readability",
138
+ "refs": [
139
+ "CUSTOMER#1"
140
+ ],
141
+ "queue": "priority",
142
+ "status": "locked",
143
+ "date": "2026-02-27",
144
+ "owner": "User (chat)",
145
+ "notes": "SCREAMING_SNAKE: EARNED, REDEEMED, ADJUSTED, EXPIRED, BONUS, REFUND. Aligned with system-wide D04 convention. Migration required for existing lowercase values.",
146
+ "schema_refs": [
147
+ "loyalty-transaction-type"
148
+ ],
149
+ "wave": 1
150
+ },
151
+ {
152
+ "id": "D08",
153
+ "title": "Canonical location for customer points",
154
+ "decision": "customers/{id}/loyalty/status is source of truth; customer doc gets optional derived summary only",
155
+ "rationale": "Blocks drift and trigger design",
156
+ "refs": [
157
+ "CUSTOMER#2"
158
+ ],
159
+ "queue": "priority",
160
+ "status": "locked",
161
+ "date": "2026-02-27",
162
+ "owner": "User (chat)",
163
+ "notes": "loyalty/status as source of truth",
164
+ "schema_refs": [
165
+ "customer",
166
+ "loyalty-status"
167
+ ],
168
+ "wave": 1
169
+ },
170
+ {
171
+ "id": "D09",
172
+ "title": "Canonical startDate/endDate type for bookings",
173
+ "decision": "number (YYYYMMDD)",
174
+ "rationale": "Blocks query consistency and typings",
175
+ "refs": [
176
+ "BOOKING#2"
177
+ ],
178
+ "queue": "priority",
179
+ "status": "locked",
180
+ "date": "2026-02-27",
181
+ "owner": "User (chat)",
182
+ "notes": "number YYYYMMDD; requested pros/cons follow-up captured below",
183
+ "schema_refs": [
184
+ "booking"
185
+ ],
186
+ "wave": 1
187
+ },
188
+ {
189
+ "id": "D10",
190
+ "title": "Canonical booking add-on IDs",
191
+ "decision": "Descriptive IDs: recording, photo",
192
+ "rationale": "Blocks label mapping and migration",
193
+ "refs": [
194
+ "BOOKING#1"
195
+ ],
196
+ "queue": "priority",
197
+ "status": "locked",
198
+ "date": "2026-02-27",
199
+ "owner": "User (chat)",
200
+ "notes": "Implement additively with legacy compatibility, avoid disruptive rewrite",
201
+ "schema_refs": [
202
+ "booking"
203
+ ],
204
+ "wave": 1
205
+ },
206
+ {
207
+ "id": "D11",
208
+ "title": "Canonical price field names in booking slots",
209
+ "decision": "price, notMainPurposePrice",
210
+ "rationale": "Blocks legacy alias cleanup",
211
+ "refs": [
212
+ "BOOKING#6"
213
+ ],
214
+ "queue": "priority",
215
+ "status": "locked",
216
+ "date": "2026-02-27",
217
+ "owner": "User (chat)",
218
+ "notes": "Canonical names: price, notMainPurposePrice. Read fallbacks for 1-2 cycles, then remove.",
219
+ "schema_refs": [
220
+ "booking"
221
+ ],
222
+ "wave": 1
223
+ },
224
+ {
225
+ "id": "D12",
226
+ "title": "Payments canonical location model",
227
+ "decision": "payments[] on Sale; allow denormalized snapshot on Order when linked",
228
+ "rationale": "Blocks overlap between OrderPayment and PaymentTransaction",
229
+ "refs": [
230
+ "SALES#2",
231
+ "ORDER#5"
232
+ ],
233
+ "queue": "priority",
234
+ "status": "locked",
235
+ "date": "2026-02-27",
236
+ "owner": "User (chat)",
237
+ "notes": "Canonical on sale, optional order snapshot when linked",
238
+ "schema_refs": [
239
+ "order",
240
+ "sale"
241
+ ],
242
+ "wave": 2
243
+ },
244
+ {
245
+ "id": "D13",
246
+ "title": "Mobile order status support breadth",
247
+ "decision": "Full 7-value OrderStatus typed enum (per D34 decomposition); fulfillmentStatus and returnStatus as optional typed enums",
248
+ "refs": [
249
+ "ORDER#6",
250
+ "D34"
251
+ ],
252
+ "queue": "second-wave",
253
+ "status": "locked",
254
+ "date": "2026-02-26",
255
+ "owner": "User (chat)",
256
+ "notes": "OrderStatus typed enum: PENDING, CONFIRMED, PROCESSING, READY, COMPLETED, CANCELLED, EXPIRED (7 values). Extended statuses removed — now separate fields per D34.",
257
+ "schema_refs": [
258
+ "order-status"
259
+ ],
260
+ "wave": 2
261
+ },
262
+ {
263
+ "id": "D14",
264
+ "title": "Keep Mobile order override fields (calculatedTotal, manualTotal, totalOverridden)",
265
+ "decision": "Keep temporarily as optional fields; review removal after 2 release cycles",
266
+ "refs": [
267
+ "ORDER#4"
268
+ ],
269
+ "queue": "second-wave",
270
+ "status": "locked",
271
+ "date": "2026-02-27",
272
+ "owner": "User (chat)",
273
+ "notes": "Keep mobile override fields temporarily",
274
+ "schema_refs": [
275
+ "order"
276
+ ],
277
+ "wave": 2
278
+ },
279
+ {
280
+ "id": "D15",
281
+ "title": "Should SaleMargin be server-side",
282
+ "decision": "Yes, move computation to Firebase trigger/service",
283
+ "refs": [
284
+ "SALES#5"
285
+ ],
286
+ "queue": "second-wave",
287
+ "status": "locked",
288
+ "date": "2026-02-27",
289
+ "owner": "User (chat)",
290
+ "notes": "Move SaleMargin server-side",
291
+ "schema_refs": [
292
+ "sale"
293
+ ],
294
+ "wave": 3
295
+ },
296
+ {
297
+ "id": "D16",
298
+ "title": "Mobile CRM scope",
299
+ "decision": "Read-only first; no full CRM authoring",
300
+ "refs": [
301
+ "SALES#6"
302
+ ],
303
+ "queue": "second-wave",
304
+ "status": "locked",
305
+ "date": "2026-02-27",
306
+ "owner": "User (chat)",
307
+ "notes": "Mobile CRM stays read-only first",
308
+ "schema_refs": [
309
+ "customer"
310
+ ],
311
+ "wave": 2
312
+ },
313
+ {
314
+ "id": "D17",
315
+ "title": "Mobile CRDT write behavior for bookings",
316
+ "decision": "Read CRDT tombstones now; keep hard-delete writes short-term; schedule soft-delete parity later",
317
+ "refs": [
318
+ "BOOKING#3"
319
+ ],
320
+ "queue": "second-wave",
321
+ "status": "locked",
322
+ "date": "2026-02-27",
323
+ "owner": "User (chat)",
324
+ "notes": "Prioritize parity",
325
+ "schema_refs": [
326
+ "booking"
327
+ ],
328
+ "wave": 2
329
+ },
330
+ {
331
+ "id": "D18",
332
+ "title": "Firebase BookingVersion ownership",
333
+ "decision": "Yes, server-side version generation on write",
334
+ "refs": [
335
+ "BOOKING#4"
336
+ ],
337
+ "queue": "second-wave",
338
+ "status": "locked",
339
+ "date": "2026-02-27",
340
+ "owner": "User (chat)",
341
+ "notes": "Requested context on purpose/current handling",
342
+ "schema_refs": [
343
+ "booking",
344
+ "booking-version"
345
+ ],
346
+ "wave": 3
347
+ },
348
+ {
349
+ "id": "D19",
350
+ "title": "Mobile session status transitions",
351
+ "decision": "Read-only: Mobile displays session statuses but cannot transition them. Dashboard remains sole writer.",
352
+ "refs": [
353
+ "BOOKING#5"
354
+ ],
355
+ "queue": "second-wave",
356
+ "status": "locked",
357
+ "date": "2026-02-26",
358
+ "owner": "User (chat)",
359
+ "notes": "Read-only first. Mobile displays per-session statuses but cannot transition them. Dashboard remains sole writer.",
360
+ "schema_refs": [
361
+ "session-status"
362
+ ],
363
+ "wave": 2
364
+ },
365
+ {
366
+ "id": "D20",
367
+ "title": "createdFromBackend behavior for dashboard-created bookings",
368
+ "decision": "Dashboard sets flag explicitly where notification suppression is desired",
369
+ "refs": [
370
+ "BOOKING#7"
371
+ ],
372
+ "queue": "second-wave",
373
+ "status": "locked",
374
+ "date": "2026-02-27",
375
+ "owner": "User (chat)",
376
+ "notes": "Requested context on why a decision is needed",
377
+ "schema_refs": [
378
+ "booking"
379
+ ],
380
+ "wave": 3
381
+ },
382
+ {
383
+ "id": "D21",
384
+ "title": "Loyalty config canonical field names",
385
+ "decision": "Adopt Mobile naming: pointsPerCurrency, pointsExpirationDays, etc.",
386
+ "refs": [
387
+ "CUSTOMER#3"
388
+ ],
389
+ "queue": "second-wave",
390
+ "status": "locked",
391
+ "date": "2026-02-27",
392
+ "owner": "User (chat)",
393
+ "notes": "Deeper loyalty-module dive requested later",
394
+ "schema_refs": [
395
+ "loyalty-config"
396
+ ],
397
+ "wave": 2
398
+ },
399
+ {
400
+ "id": "D22",
401
+ "title": "Mobile customer payment feature scope",
402
+ "decision": "Trusted-party capture: cashier/manager roles create payments with mandatory audit fields; advanced allocation/edit/refund stays Dashboard-only",
403
+ "refs": [
404
+ "CUSTOMER#4"
405
+ ],
406
+ "queue": "second-wave",
407
+ "status": "locked",
408
+ "date": "2026-02-26",
409
+ "owner": "User (chat)",
410
+ "notes": "Trusted-party capture: cashier/manager roles can create payments from Mobile with mandatory audit fields (recordedBy, recordedByName, timestamp, reference). Advanced allocation/edit/refund stays Dashboard-only.",
411
+ "schema_refs": [
412
+ "customer-payment"
413
+ ],
414
+ "wave": 4
415
+ },
416
+ {
417
+ "id": "D23",
418
+ "title": "Firebase loyalty automation",
419
+ "decision": "Yes (award, expire, validate server-side)",
420
+ "refs": [
421
+ "CUSTOMER#5"
422
+ ],
423
+ "queue": "second-wave",
424
+ "status": "locked",
425
+ "date": "2026-02-27",
426
+ "owner": "User (chat)",
427
+ "notes": "Server-side loyalty automation approved",
428
+ "schema_refs": [
429
+ "loyalty-transaction"
430
+ ],
431
+ "wave": 3
432
+ },
433
+ {
434
+ "id": "D24",
435
+ "title": "Customer reference naming standardization",
436
+ "decision": "Standardize for new writes only; document mapping for legacy data",
437
+ "refs": [
438
+ "CUSTOMER#6"
439
+ ],
440
+ "queue": "second-wave",
441
+ "status": "locked",
442
+ "date": "2026-02-27",
443
+ "owner": "User (chat)",
444
+ "notes": "Standardize names for new writes",
445
+ "schema_refs": [
446
+ "customer",
447
+ "booking",
448
+ "order"
449
+ ],
450
+ "wave": 2
451
+ },
452
+ {
453
+ "id": "D25",
454
+ "title": "communicationEntries storage shape",
455
+ "decision": "Keep as array unless cardinality/perf issues arise",
456
+ "refs": [
457
+ "CUSTOMER#7"
458
+ ],
459
+ "queue": "second-wave",
460
+ "status": "locked",
461
+ "date": "2026-02-26",
462
+ "owner": "User (chat)",
463
+ "notes": "Keep as embedded array on Customer document. Move to subcollection only if cardinality/perf becomes a problem.",
464
+ "schema_refs": [
465
+ "customer"
466
+ ],
467
+ "wave": 1
468
+ },
469
+ {
470
+ "id": "D26",
471
+ "title": "Event/Ticket support in dashboard",
472
+ "decision": "Yes, if cross-platform parity is a product goal this cycle",
473
+ "refs": [
474
+ "EVENTS#3"
475
+ ],
476
+ "queue": "second-wave",
477
+ "status": "locked",
478
+ "date": "2026-02-27",
479
+ "owner": "User (chat)",
480
+ "notes": "Dashboard should be superset of functionality",
481
+ "schema_refs": [
482
+ "event"
483
+ ],
484
+ "wave": 4
485
+ },
486
+ {
487
+ "id": "D27",
488
+ "title": "Dashboard ticket scanning",
489
+ "decision": "Optional; keep mobile-first scanning initially",
490
+ "refs": [
491
+ "EVENTS#4"
492
+ ],
493
+ "queue": "second-wave",
494
+ "status": "locked",
495
+ "date": "2026-02-27",
496
+ "owner": "User (chat)",
497
+ "notes": "Keep mobile-first scanning initially",
498
+ "schema_refs": [
499
+ "ticket"
500
+ ],
501
+ "wave": 4
502
+ },
503
+ {
504
+ "id": "D28",
505
+ "title": "Firebase Event/Ticket triggers",
506
+ "decision": "Yes (notifications, counters, metrics)",
507
+ "refs": [
508
+ "EVENTS#5"
509
+ ],
510
+ "queue": "second-wave",
511
+ "status": "locked",
512
+ "date": "2026-02-26",
513
+ "owner": "User (chat)",
514
+ "notes": "Add Firebase Event/Ticket triggers when Dashboard Event support (D26) is built. Not before — Events are Mobile-only today.",
515
+ "schema_refs": [
516
+ "event",
517
+ "ticket"
518
+ ],
519
+ "wave": 4
520
+ },
521
+ {
522
+ "id": "D29",
523
+ "title": "Ticket-to-customer linkage model",
524
+ "decision": "Add optional customerId + keep denormalized fields for compatibility",
525
+ "refs": [
526
+ "EVENTS#6"
527
+ ],
528
+ "queue": "second-wave",
529
+ "status": "locked",
530
+ "date": "2026-02-27",
531
+ "owner": "User (chat)",
532
+ "notes": "Link tickets to customer with compatibility",
533
+ "schema_refs": [
534
+ "ticket"
535
+ ],
536
+ "wave": 2
537
+ },
538
+ {
539
+ "id": "D30",
540
+ "title": "Ticket tiers/types support timing",
541
+ "decision": "Defer until business requirement is confirmed",
542
+ "refs": [
543
+ "EVENTS#7"
544
+ ],
545
+ "queue": "second-wave",
546
+ "status": "locked",
547
+ "date": "2026-02-27",
548
+ "owner": "User (chat)",
549
+ "notes": "Defer ticket tiers",
550
+ "schema_refs": [
551
+ "ticket"
552
+ ],
553
+ "wave": null
554
+ },
555
+ {
556
+ "id": "D31",
557
+ "title": "LegacyTicketMapper deprecation gate",
558
+ "decision": "Remove only after migration confirms zero legacy docs",
559
+ "refs": [
560
+ "EVENTS#8"
561
+ ],
562
+ "queue": "second-wave",
563
+ "status": "locked",
564
+ "date": "2026-02-27",
565
+ "owner": "User (chat)",
566
+ "notes": "Deprecate legacy mapper after data migration completion",
567
+ "schema_refs": [
568
+ "ticket"
569
+ ],
570
+ "wave": 5
571
+ },
572
+ {
573
+ "id": "D32",
574
+ "title": "Event/Ticket status casing",
575
+ "decision": "Move to SCREAMING_SNAKE with fallback reads during migration",
576
+ "refs": [
577
+ "EVENTS#1"
578
+ ],
579
+ "queue": "second-wave",
580
+ "status": "locked",
581
+ "date": "2026-02-27",
582
+ "owner": "User (chat)",
583
+ "notes": "Migrate status casing with fallback",
584
+ "schema_refs": [
585
+ "event-status",
586
+ "ticket-status"
587
+ ],
588
+ "wave": 1
589
+ },
590
+ {
591
+ "id": "D33",
592
+ "title": "Deterministic IV in QR encryption",
593
+ "decision": "Deferred — not essential for current cycle",
594
+ "refs": [
595
+ "EVENTS#2"
596
+ ],
597
+ "queue": "second-wave",
598
+ "status": "deferred",
599
+ "date": "2026-02-27",
600
+ "owner": "User (chat)",
601
+ "notes": "Not essential for current cycle",
602
+ "schema_refs": [],
603
+ "wave": null
604
+ },
605
+ {
606
+ "id": "D34",
607
+ "title": "Order status decomposition into orthogonal fields",
608
+ "decision": "Decompose 20-value OrderStatus into 4 fields: orderStatus (7), paymentStatus (7, D01), fulfillmentStatus (6, new), returnStatus (6, new)",
609
+ "refs": [
610
+ "ORDER cross-cutting"
611
+ ],
612
+ "queue": "second-wave",
613
+ "status": "locked",
614
+ "date": "2026-02-26",
615
+ "owner": "User (chat)",
616
+ "notes": "Decompose into 4 orthogonal status fields. See §10 for full spec. Supersedes old \"extended statuses\" in §8.",
617
+ "schema_refs": [
618
+ "order",
619
+ "order-status",
620
+ "payment-status",
621
+ "fulfillment-status",
622
+ "return-status"
623
+ ],
624
+ "wave": 1
625
+ },
626
+ {
627
+ "id": "D35",
628
+ "title": "Migration framework approach",
629
+ "decision": "CLI + callable hybrid: CLI for heavy (>100K docs), Firebase callable for incremental/per-company. Shared progress tracking in Firestore.",
630
+ "refs": [
631
+ "CROSS_CUTTING §Insight 3"
632
+ ],
633
+ "queue": "second-wave",
634
+ "status": "locked",
635
+ "date": "2026-02-26",
636
+ "owner": "User (chat)",
637
+ "notes": "Both CLI + callable. CLI for heavy one-time migrations (>100K docs). Firebase callable for incremental/per-company runs. Shared progress tracking in Firestore. See §12.",
638
+ "schema_refs": [],
639
+ "wave": 1
640
+ },
641
+ {
642
+ "id": "D36",
643
+ "title": "Firebase architectural role: relay vs enforcer",
644
+ "decision": "Log-and-heal (Waves 1-3) → strict enforcement (post-Wave 3). Validate on write, auto-normalize known patterns.",
645
+ "refs": [
646
+ "CROSS_CUTTING §Insight 2"
647
+ ],
648
+ "queue": "second-wave",
649
+ "status": "locked",
650
+ "date": "2026-02-26",
651
+ "owner": "User (chat)",
652
+ "notes": "Log-and-heal now → strict later. Validate on write, log issues, auto-normalize known patterns. Switch to strict enforcement after migration waves complete. See §13.",
653
+ "schema_refs": [],
654
+ "wave": 1
655
+ },
656
+ {
657
+ "id": "D37",
658
+ "title": "Execution wave plan approval",
659
+ "decision": "5-wave rollout: Foundation → Adoption → Server-Side Logic → Feature Parity → Cleanup",
660
+ "refs": [
661
+ "CROSS_CUTTING §Part 3"
662
+ ],
663
+ "queue": "second-wave",
664
+ "status": "locked",
665
+ "date": "2026-02-26",
666
+ "owner": "User (chat)",
667
+ "notes": "Approved as proposed: 5-wave phased rollout (Foundation → Adoption → Server-Side Logic → Feature Parity → Cleanup). See §14.",
668
+ "schema_refs": [],
669
+ "wave": 1
670
+ }
671
+ ],
672
+ "implementation_gaps": [
673
+ {
674
+ "id": "IG-1",
675
+ "summary": "Cross-Platform Contract Testing",
676
+ "wave": 1,
677
+ "acceptance_criteria": "Shared JSON fixtures exist for all models; read/write round-trip tests pass across all 3 platforms",
678
+ "refs": [
679
+ "ORDER §5.1-5.2",
680
+ "SALES §6.1-6.2"
681
+ ]
682
+ },
683
+ {
684
+ "id": "IG-2",
685
+ "summary": "Firestore Security Rules Generation",
686
+ "wave": 3,
687
+ "acceptance_criteria": "Security rules enforce basic type checks for critical collections; generated or manually aligned with @valets/schema",
688
+ "refs": [
689
+ "ORDER §5.1",
690
+ "SALES §6.2"
691
+ ]
692
+ },
693
+ {
694
+ "id": "IG-3",
695
+ "summary": "Booking Model Convergence (Mobile)",
696
+ "wave": 2,
697
+ "acceptance_criteria": "Single canonical Booking class on Mobile generated from @valets/schema; BookingData page class removed or reduced to UI-only state",
698
+ "refs": [
699
+ "BOOKING gap #4"
700
+ ]
701
+ },
702
+ {
703
+ "id": "IG-4",
704
+ "summary": "Order Payments Model Ownership",
705
+ "wave": 2,
706
+ "acceptance_criteria": "OrderPayment → PaymentTransaction field mapping documented; sync rules for Sale↔Order denormalized snapshot implemented",
707
+ "refs": [
708
+ "ORDER §4.3",
709
+ "SALES §Phase 3",
710
+ "D12"
711
+ ]
712
+ },
713
+ {
714
+ "id": "IG-5",
715
+ "summary": "Purchases Collection Path Consolidation",
716
+ "wave": 1,
717
+ "acceptance_criteria": "All purchase documents exist in single purchases/ path; customer-scoped duplicates removed; references rewritten",
718
+ "refs": [
719
+ "SALES gap",
720
+ "D06",
721
+ "MIG-04"
722
+ ]
723
+ },
724
+ {
725
+ "id": "IG-6",
726
+ "summary": "Booking CRDT Interoperability",
727
+ "wave": 2,
728
+ "acceptance_criteria": "Mobile fromMap filters _deleted items; conflict resolution documented; timeline for soft-delete parity set",
729
+ "refs": [
730
+ "BOOKING gap #2",
731
+ "D17"
732
+ ]
733
+ },
734
+ {
735
+ "id": "IG-7",
736
+ "summary": "Customer Payments Rollout Spec",
737
+ "wave": 4,
738
+ "acceptance_criteria": "Trusted roles defined; audit field requirements in @valets/schema; server-side validation rules deployed",
739
+ "refs": [
740
+ "CUSTOMER gap #3",
741
+ "D22"
742
+ ]
743
+ },
744
+ {
745
+ "id": "IG-8",
746
+ "summary": "Notification Behavior Policy",
747
+ "wave": 3,
748
+ "acceptance_criteria": "createdFromBackend behavior documented and consistently applied; notification regression tests in CI",
749
+ "refs": [
750
+ "BOOKING §D20",
751
+ "ORDER §8"
752
+ ]
753
+ },
754
+ {
755
+ "id": "IG-9",
756
+ "summary": "Event/Ticket Crypto + Legacy Cutover",
757
+ "wave": 5,
758
+ "acceptance_criteria": "LegacyTicketMapper removal gate query exists; key rotation policy documented; IV migration plan ready when triggered",
759
+ "refs": [
760
+ "EVENTS gaps #3/#5",
761
+ "D31",
762
+ "D33"
763
+ ]
764
+ },
765
+ {
766
+ "id": "IG-10",
767
+ "summary": "LoyaltyReward / LoyaltyConfig Parity",
768
+ "wave": 2,
769
+ "acceptance_criteria": "Mobile-only loyalty fields catalogued; canonical set defined in @valets/schema; Dashboard config UI spec created",
770
+ "refs": [
771
+ "CUSTOMER gaps #6/#8"
772
+ ]
773
+ },
774
+ {
775
+ "id": "IG-11",
776
+ "summary": "slotKitTypes Firebase Type Parity",
777
+ "wave": 2,
778
+ "acceptance_criteria": "slotKitTypes field added to Firebase Booking type definition; validated in contract tests",
779
+ "refs": [
780
+ "BOOKING gap #8"
781
+ ]
782
+ },
783
+ {
784
+ "id": "IG-12",
785
+ "summary": "bookingId Mobile Purchase Parity",
786
+ "wave": 2,
787
+ "acceptance_criteria": "bookingId field added to Mobile Purchase model; links sales to bookings across platforms",
788
+ "refs": [
789
+ "SALES gap #7"
790
+ ]
791
+ },
792
+ {
793
+ "id": "IG-13",
794
+ "summary": "companyId Firebase Quote/Opportunity Parity",
795
+ "wave": 2,
796
+ "acceptance_criteria": "companyId field added to Firebase Quote and Opportunity type definitions; multi-tenant queries work",
797
+ "refs": [
798
+ "SALES gap #8"
799
+ ]
800
+ }
801
+ ],
802
+ "migrations": [
803
+ {
804
+ "id": "MIG-01",
805
+ "name": "Timestamp field casing",
806
+ "type": "data",
807
+ "collection": "orders",
808
+ "decision": "D04",
809
+ "description": "Rename processingOn → PROCESSING_ON, completedOn → COMPLETED_ON, cancelledOn → CANCELLED_ON, etc.",
810
+ "wave": 1
811
+ },
812
+ {
813
+ "id": "MIG-02",
814
+ "name": "Payment method normalization",
815
+ "type": "data",
816
+ "collection": "purchases",
817
+ "decision": "D02",
818
+ "description": "In payments[] array: CARD → CREDIT_CARD, OM → ORANGE_MONEY, MTN → MTN_MONEY, MOOV → MOOV_MONEY",
819
+ "wave": 1
820
+ },
821
+ {
822
+ "id": "MIG-03",
823
+ "name": "PaymentStatus consolidation",
824
+ "type": "data",
825
+ "collection": "orders, purchases, bookings",
826
+ "decision": "D01",
827
+ "description": "UNPAID → PENDING, PARTIAL → PARTIALLY_PAID (Dashboard's conflicting enum). Bookings also carry payment status.",
828
+ "wave": 1
829
+ },
830
+ {
831
+ "id": "MIG-04",
832
+ "name": "Purchases path consolidation",
833
+ "type": "data",
834
+ "collection": "customers/*/purchases → purchases",
835
+ "decision": "D06",
836
+ "description": "Move customer-scoped purchase docs to company-wide path. Deduplicate. Rewrite references. See IG-5 (§15) for acceptance criteria.",
837
+ "wave": 1
838
+ },
839
+ {
840
+ "id": "MIG-05",
841
+ "name": "Loyalty transaction type normalization",
842
+ "type": "data",
843
+ "collection": "loyaltyTransactions",
844
+ "decision": "D07",
845
+ "description": "earn/earned → EARNED, redeem/redeemed → REDEEMED, adjust/adjusted → ADJUSTED, expire/expired → EXPIRED, bonus → BONUS, refund → REFUND",
846
+ "wave": 1
847
+ },
848
+ {
849
+ "id": "MIG-06",
850
+ "name": "Loyalty config field renaming",
851
+ "type": "data",
852
+ "collection": "loyaltySettings/config",
853
+ "decision": "D21",
854
+ "description": "pointsPerCurrencyUnit → pointsPerCurrency, pointsExpiryMonths → pointsExpirationDays (convert months×30)",
855
+ "wave": 1
856
+ },
857
+ {
858
+ "id": "MIG-07",
859
+ "name": "Booking add-on ID normalization",
860
+ "type": "data",
861
+ "collection": "bookings (nested slots)",
862
+ "decision": "D10",
863
+ "description": "Numeric IDs ('1', '2') → descriptive ('recording', 'photo'). Additive with legacy compat.",
864
+ "wave": 1
865
+ },
866
+ {
867
+ "id": "MIG-08",
868
+ "name": "Booking price field renaming",
869
+ "type": "data",
870
+ "collection": "bookings (nested slots)",
871
+ "decision": "D11",
872
+ "description": "standardPrice → price, fullPrice → notMainPurposePrice",
873
+ "wave": 1
874
+ },
875
+ {
876
+ "id": "MIG-09",
877
+ "name": "Event status casing",
878
+ "type": "data",
879
+ "collection": "events",
880
+ "decision": "D32",
881
+ "description": "draft → DRAFT, active → ACTIVE, cancelled → CANCELLED, completed → COMPLETED",
882
+ "wave": 1
883
+ },
884
+ {
885
+ "id": "MIG-10",
886
+ "name": "Ticket status casing",
887
+ "type": "data",
888
+ "collection": "events/*/tickets",
889
+ "decision": "D32",
890
+ "description": "valid → VALID, used → USED, cancelled → CANCELLED",
891
+ "wave": 1
892
+ },
893
+ {
894
+ "id": "MIG-11",
895
+ "name": "Order status decomposition",
896
+ "type": "data",
897
+ "collection": "orders",
898
+ "decision": "D34",
899
+ "description": "Split extended statuses into orderStatus + paymentStatus + fulfillmentStatus + returnStatus per §10 mapping table",
900
+ "wave": 1
901
+ },
902
+ {
903
+ "id": "MIG-12",
904
+ "name": "Customer points source of truth",
905
+ "type": "data",
906
+ "collection": "customers, loyalty/status",
907
+ "decision": "D08",
908
+ "description": "Verify loyalty/status populated for all customers; flag discrepancies with denormalized customer doc fields",
909
+ "wave": 1
910
+ },
911
+ {
912
+ "id": "COD-01",
913
+ "name": "Stop writing total",
914
+ "type": "code",
915
+ "platform": "Mobile",
916
+ "decision": "D05",
917
+ "description": "Mobile stops writing total field on purchases. total becomes read-only getter.",
918
+ "wave": 1
919
+ },
920
+ {
921
+ "id": "COD-02",
922
+ "name": "SalesService alias",
923
+ "type": "code",
924
+ "platform": "All",
925
+ "decision": "D06",
926
+ "description": "Code-level alias wrapping purchases collection. No Firestore rename.",
927
+ "wave": 1
928
+ },
929
+ {
930
+ "id": "COD-03",
931
+ "name": "Add missing BookingStatus values",
932
+ "type": "code",
933
+ "platform": "Mobile",
934
+ "decision": "—",
935
+ "description": "Add CANCELLATION_REQUESTED, COMPLETED_MIXED to Mobile enum",
936
+ "wave": 1
937
+ },
938
+ {
939
+ "id": "COD-04",
940
+ "name": "Add SessionStatus awareness",
941
+ "type": "code",
942
+ "platform": "Mobile, Firebase",
943
+ "decision": "D19",
944
+ "description": "Mobile reads and displays SessionStatus. Read-only first.",
945
+ "wave": 1
946
+ },
947
+ {
948
+ "id": "MIG-13",
949
+ "name": "Legacy ticket format migration",
950
+ "type": "data",
951
+ "collection": "events/*/tickets",
952
+ "decision": "D31",
953
+ "description": "Migrate userName/userEmail/userPhone/ticketStatus variants to canonical field names. Run only after audit confirms scope.",
954
+ "wave": 5
955
+ },
956
+ {
957
+ "id": "MIG-14",
958
+ "name": "Remove fallback read paths",
959
+ "type": "data",
960
+ "collection": "All platforms",
961
+ "decision": "Various",
962
+ "description": "Remove old field name readers after fallback removal gate is met (see §1 Transition Protocol).",
963
+ "wave": 5
964
+ }
965
+ ]
966
+ }