yadflow 2.6.0 → 2.8.0

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 (107) hide show
  1. package/CHANGELOG.md +2 -11
  2. package/README.md +30 -5
  3. package/bin/yad.mjs +36 -1
  4. package/cli/docs.mjs +298 -0
  5. package/cli/manifest.mjs +6 -1
  6. package/cli/roster.mjs +164 -0
  7. package/cli/setup.mjs +128 -2
  8. package/package.json +3 -4
  9. package/skills/sdlc/config.yaml +19 -0
  10. package/skills/sdlc/install.sh +1 -1
  11. package/skills/sdlc/module-help.csv +4 -0
  12. package/skills/yad-connect-docs/SKILL.md +132 -0
  13. package/skills/yad-connect-docs/references/docs-registry.md +74 -0
  14. package/skills/yad-connect-repos/SKILL.md +4 -0
  15. package/skills/yad-connect-repos/references/hub-config.md +3 -1
  16. package/skills/yad-docs/SKILL.md +159 -0
  17. package/skills/yad-docs/references/data-mapping.md +75 -0
  18. package/skills/yad-docs/references/theme-map.md +69 -0
  19. package/skills/yad-docs/templates/app/README.md +31 -0
  20. package/skills/yad-docs/templates/app/eslint.config.js +23 -0
  21. package/skills/yad-docs/templates/app/index.html +17 -0
  22. package/skills/yad-docs/templates/app/package-lock.json +4030 -0
  23. package/skills/yad-docs/templates/app/package.json +35 -0
  24. package/skills/yad-docs/templates/app/public/favicon.svg +28 -0
  25. package/skills/yad-docs/templates/app/public/logo.svg +39 -0
  26. package/skills/yad-docs/templates/app/public/vite.svg +1 -0
  27. package/skills/yad-docs/templates/app/src/App.tsx +98 -0
  28. package/skills/yad-docs/templates/app/src/components/Auth/LoginPage.tsx +101 -0
  29. package/skills/yad-docs/templates/app/src/components/Canvas/AnimatedMessage.tsx +101 -0
  30. package/skills/yad-docs/templates/app/src/components/Canvas/ConnectionLine.tsx +90 -0
  31. package/skills/yad-docs/templates/app/src/components/Canvas/FlowCanvas.tsx +216 -0
  32. package/skills/yad-docs/templates/app/src/components/Canvas/SystemComponent.tsx +153 -0
  33. package/skills/yad-docs/templates/app/src/components/Controls/PlaybackBar.tsx +284 -0
  34. package/skills/yad-docs/templates/app/src/components/Controls/StepDetail.tsx +167 -0
  35. package/skills/yad-docs/templates/app/src/components/DetailPanel/HandlerLogicSnippet.tsx +41 -0
  36. package/skills/yad-docs/templates/app/src/components/DetailPanel/RequestPayloadPreview.tsx +46 -0
  37. package/skills/yad-docs/templates/app/src/components/DetailPanel/RightPanel.tsx +88 -0
  38. package/skills/yad-docs/templates/app/src/components/DetailPanel/StatusCard.tsx +76 -0
  39. package/skills/yad-docs/templates/app/src/components/DetailPanel/TriggerEventCard.tsx +45 -0
  40. package/skills/yad-docs/templates/app/src/components/DocLayout/DocPageShell.tsx +80 -0
  41. package/skills/yad-docs/templates/app/src/components/DocLayout/DocSectionCard.tsx +55 -0
  42. package/skills/yad-docs/templates/app/src/components/DocLayout/DocTableOfContents.tsx +79 -0
  43. package/skills/yad-docs/templates/app/src/components/DocLayout/RoleCard.tsx +67 -0
  44. package/skills/yad-docs/templates/app/src/components/DocSections/ApiReferenceSection.tsx +108 -0
  45. package/skills/yad-docs/templates/app/src/components/DocSections/CancelabilitySection.tsx +73 -0
  46. package/skills/yad-docs/templates/app/src/components/DocSections/CriticalRunbookSection.tsx +177 -0
  47. package/skills/yad-docs/templates/app/src/components/DocSections/DataMigrationSection.tsx +102 -0
  48. package/skills/yad-docs/templates/app/src/components/DocSections/DbSchemaSection.tsx +98 -0
  49. package/skills/yad-docs/templates/app/src/components/DocSections/DeploymentGuideSection.tsx +104 -0
  50. package/skills/yad-docs/templates/app/src/components/DocSections/DriverIntegrationSection.tsx +127 -0
  51. package/skills/yad-docs/templates/app/src/components/DocSections/ExecutiveSummarySection.tsx +69 -0
  52. package/skills/yad-docs/templates/app/src/components/DocSections/FlowOverviewSection.tsx +73 -0
  53. package/skills/yad-docs/templates/app/src/components/DocSections/FlowPathsChecklistSection.tsx +96 -0
  54. package/skills/yad-docs/templates/app/src/components/DocSections/MiddlewareChainSection.tsx +107 -0
  55. package/skills/yad-docs/templates/app/src/components/DocSections/MonitoringAlertingSection.tsx +106 -0
  56. package/skills/yad-docs/templates/app/src/components/DocSections/NotificationLocalizationSection.tsx +102 -0
  57. package/skills/yad-docs/templates/app/src/components/DocSections/PMRoadmapSection.tsx +133 -0
  58. package/skills/yad-docs/templates/app/src/components/DocSections/PerformanceTestingSection.tsx +91 -0
  59. package/skills/yad-docs/templates/app/src/components/DocSections/RiderIntegrationSection.tsx +99 -0
  60. package/skills/yad-docs/templates/app/src/components/DocSections/SecuritySection.tsx +74 -0
  61. package/skills/yad-docs/templates/app/src/components/DocSections/StatusMachineSection.tsx +90 -0
  62. package/skills/yad-docs/templates/app/src/components/DocSections/TestPlanSection.tsx +163 -0
  63. package/skills/yad-docs/templates/app/src/components/Logs/SystemLogsTerminal.tsx +126 -0
  64. package/skills/yad-docs/templates/app/src/components/Navigation/TopNavBar.tsx +90 -0
  65. package/skills/yad-docs/templates/app/src/components/Reference/BullMQJobsList.tsx +60 -0
  66. package/skills/yad-docs/templates/app/src/components/Reference/DecisionTreeView.tsx +49 -0
  67. package/skills/yad-docs/templates/app/src/components/Reference/DeeplinkActionsChips.tsx +69 -0
  68. package/skills/yad-docs/templates/app/src/components/Reference/DriverUIStatesTable.tsx +61 -0
  69. package/skills/yad-docs/templates/app/src/components/Reference/FeatureFlagMatrix.tsx +73 -0
  70. package/skills/yad-docs/templates/app/src/components/Reference/RiderUIStatesTable.tsx +61 -0
  71. package/skills/yad-docs/templates/app/src/components/Reference/RulesLegendPanel.tsx +217 -0
  72. package/skills/yad-docs/templates/app/src/components/Reference/StakeholderToggle.tsx +41 -0
  73. package/skills/yad-docs/templates/app/src/components/Reference/TroubleshootingSection.tsx +93 -0
  74. package/skills/yad-docs/templates/app/src/components/Sidebar/PathSelector.tsx +148 -0
  75. package/skills/yad-docs/templates/app/src/components/Sidebar/SidebarFooter.tsx +40 -0
  76. package/skills/yad-docs/templates/app/src/components/Sidebar/StepList.tsx +234 -0
  77. package/skills/yad-docs/templates/app/src/components/shared/Badge.tsx +28 -0
  78. package/skills/yad-docs/templates/app/src/components/shared/CommandPalette.tsx +213 -0
  79. package/skills/yad-docs/templates/app/src/components/shared/Icon.tsx +21 -0
  80. package/skills/yad-docs/templates/app/src/components/shared/Tooltip.tsx +42 -0
  81. package/skills/yad-docs/templates/app/src/data/components.ts +74 -0
  82. package/skills/yad-docs/templates/app/src/data/docSections.ts +231 -0
  83. package/skills/yad-docs/templates/app/src/data/paths.ts +2319 -0
  84. package/skills/yad-docs/templates/app/src/data/referenceData.ts +392 -0
  85. package/skills/yad-docs/templates/app/src/data/roles.ts +145 -0
  86. package/skills/yad-docs/templates/app/src/data/types.ts +79 -0
  87. package/skills/yad-docs/templates/app/src/hooks/useAnimationQueue.ts +41 -0
  88. package/skills/yad-docs/templates/app/src/hooks/usePlayback.ts +100 -0
  89. package/skills/yad-docs/templates/app/src/hooks/useStakeholderFilter.ts +10 -0
  90. package/skills/yad-docs/templates/app/src/index.css +121 -0
  91. package/skills/yad-docs/templates/app/src/main.tsx +13 -0
  92. package/skills/yad-docs/templates/app/src/pages/RoleSelectPage.tsx +34 -0
  93. package/skills/yad-docs/templates/app/src/pages/StakeholderDocPage.tsx +98 -0
  94. package/skills/yad-docs/templates/app/src/pages/SubPathDetailPage.tsx +282 -0
  95. package/skills/yad-docs/templates/app/src/store/useAuthStore.ts +42 -0
  96. package/skills/yad-docs/templates/app/src/store/useFlowStore.ts +197 -0
  97. package/skills/yad-docs/templates/app/src/utils/iconMap.ts +46 -0
  98. package/skills/yad-docs/templates/app/tsconfig.app.json +28 -0
  99. package/skills/yad-docs/templates/app/tsconfig.json +7 -0
  100. package/skills/yad-docs/templates/app/tsconfig.node.json +26 -0
  101. package/skills/yad-docs/templates/app/vite.config.ts +10 -0
  102. package/skills/yad-docs-overview/SKILL.md +131 -0
  103. package/skills/yad-docs-overview/references/pipeline-model.md +102 -0
  104. package/skills/yad-docs-sync/SKILL.md +99 -0
  105. package/skills/yad-docs-sync/references/staleness.md +81 -0
  106. package/skills/yad-hub-bridge/references/login-roster.md +1 -0
  107. package/docs/index.html +0 -1323
@@ -0,0 +1,67 @@
1
+ import { useNavigate } from 'react-router-dom';
2
+ import { Icon } from '../shared/Icon';
3
+ import type { RoleConfig } from '../../data/roles';
4
+
5
+ interface RoleCardProps {
6
+ role: RoleConfig;
7
+ }
8
+
9
+ export function RoleCard({ role }: RoleCardProps) {
10
+ const navigate = useNavigate();
11
+
12
+ return (
13
+ <button
14
+ onClick={() => navigate(`/docs/${role.slug}`)}
15
+ className="group text-left rounded-xl border p-5 transition-all hover:border-opacity-60"
16
+ style={{
17
+ background: 'rgba(20,17,24,0.5)',
18
+ borderColor: 'var(--color-border-default)',
19
+ }}
20
+ onMouseEnter={(e) => {
21
+ e.currentTarget.style.borderColor = `${role.color}50`;
22
+ e.currentTarget.style.background = `${role.color}08`;
23
+ }}
24
+ onMouseLeave={(e) => {
25
+ e.currentTarget.style.borderColor = 'var(--color-border-default)';
26
+ e.currentTarget.style.background = 'rgba(20,17,24,0.5)';
27
+ }}
28
+ >
29
+ <div className="flex items-start justify-between mb-3">
30
+ <div
31
+ className="w-11 h-11 rounded-xl flex items-center justify-center"
32
+ style={{ background: `${role.color}20`, color: role.color }}
33
+ >
34
+ <Icon name={role.icon} size={24} />
35
+ </div>
36
+ <Icon
37
+ name="arrow_forward"
38
+ size={18}
39
+ className="text-slate-600 group-hover:text-slate-300 transition-colors mt-1"
40
+ />
41
+ </div>
42
+ <h3 className="text-white font-bold font-display mb-1">{role.label}</h3>
43
+ <p className="text-xs text-slate-400 leading-relaxed mb-3">{role.description}</p>
44
+ <div className="flex items-center gap-2">
45
+ <span
46
+ className="text-[10px] font-bold px-2 py-0.5 rounded border"
47
+ style={{
48
+ color: role.color,
49
+ background: `${role.color}15`,
50
+ borderColor: `${role.color}25`,
51
+ }}
52
+ >
53
+ {role.sectionIds.length} sections
54
+ </span>
55
+ <span
56
+ className="text-[10px] font-medium px-2 py-0.5 rounded border text-slate-400"
57
+ style={{
58
+ background: 'rgba(255,255,255,0.03)',
59
+ borderColor: 'var(--color-border-default)',
60
+ }}
61
+ >
62
+ {role.relevantPathIds.length} flow paths
63
+ </span>
64
+ </div>
65
+ </button>
66
+ );
67
+ }
@@ -0,0 +1,108 @@
1
+ import { Icon } from '../shared/Icon';
2
+
3
+ const ENDPOINTS = [
4
+ {
5
+ method: 'PUT',
6
+ path: '/b2c/rider/services/v6/request',
7
+ description: 'Internal trip status update — triggers fnUpdateBookingStatus middleware',
8
+ middleware: [
9
+ 'validateTripStatus',
10
+ 'fnUpdateBookingStatus',
11
+ 'fnUpdateCancelBookingStatus',
12
+ 'fnCancelBookedTripScheduledJobs',
13
+ 'fnDeleteScheduledRideDac',
14
+ 'publishScheduledRideDacMW',
15
+ 'scheduleBookingJobs',
16
+ ],
17
+ category: 'rider',
18
+ },
19
+ {
20
+ method: 'PUT',
21
+ path: '/rider/trips/:tripId/booking-status',
22
+ description: 'Rider booking status update — confirmation, unconfirmation, cancellation',
23
+ middleware: [
24
+ 'validateBookingStatusTransition',
25
+ 'saveBookingStatusUpdate',
26
+ 'fnCancelBookedTripScheduledJobs',
27
+ 'fnDeleteScheduledRideDac',
28
+ 'publishScheduledRideDacMW',
29
+ 'sendBookingNotification',
30
+ ],
31
+ category: 'rider',
32
+ },
33
+ {
34
+ method: 'PUT',
35
+ path: '/b2c/driver/trips/:tripId/booking-status',
36
+ description: 'Driver booking status update — accept or cancel assigned ride',
37
+ middleware: [
38
+ 'validateDriverBookingTransition',
39
+ 'saveBookingStatusUpdate',
40
+ 'fnCancelBookedTripScheduledJobs',
41
+ 'publishRideDispatchBookingMW',
42
+ 'publishScheduledRideDacMW',
43
+ 'sendBookingNotification',
44
+ ],
45
+ category: 'driver',
46
+ },
47
+ ];
48
+
49
+ export function ApiReferenceSection() {
50
+ return (
51
+ <div className="space-y-4">
52
+ {ENDPOINTS.map((ep) => (
53
+ <div
54
+ key={ep.path}
55
+ className="rounded-xl border overflow-hidden"
56
+ style={{ background: 'rgba(20,17,24,0.5)', borderColor: 'var(--color-border-default)' }}
57
+ >
58
+ <div className="px-4 py-3 flex items-center gap-3 border-b" style={{ borderColor: 'var(--color-border-default)' }}>
59
+ <span
60
+ className="text-[10px] font-bold px-2 py-0.5 rounded"
61
+ style={{
62
+ background: ep.method === 'PUT' ? 'rgba(251,191,36,0.15)' : 'rgba(96,165,250,0.15)',
63
+ color: ep.method === 'PUT' ? '#fbbf24' : '#60a5fa',
64
+ }}
65
+ >
66
+ {ep.method}
67
+ </span>
68
+ <code className="text-sm text-slate-200 font-mono">{ep.path}</code>
69
+ <span
70
+ className="ml-auto text-[10px] uppercase tracking-wider font-semibold px-2 py-0.5 rounded"
71
+ style={{
72
+ color: ep.category === 'rider' ? '#7b59e6' : '#06b6d4',
73
+ background: ep.category === 'rider' ? 'rgba(123,89,230,0.1)' : 'rgba(6,182,212,0.1)',
74
+ }}
75
+ >
76
+ {ep.category}
77
+ </span>
78
+ </div>
79
+ <div className="px-4 py-2.5">
80
+ <p className="text-xs text-slate-400 mb-3">{ep.description}</p>
81
+ <div className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold mb-2">
82
+ Middleware Chain ({ep.middleware.length})
83
+ </div>
84
+ <div className="flex flex-wrap gap-1.5">
85
+ {ep.middleware.map((mw, i) => (
86
+ <span key={mw} className="inline-flex items-center gap-1">
87
+ <span
88
+ className="px-2 py-1 rounded text-[11px] font-mono border"
89
+ style={{
90
+ background: 'rgba(255,255,255,0.03)',
91
+ borderColor: 'var(--color-border-default)',
92
+ color: '#94a3b8',
93
+ }}
94
+ >
95
+ {mw}
96
+ </span>
97
+ {i < ep.middleware.length - 1 && (
98
+ <Icon name="arrow_forward" size={12} className="text-slate-600" />
99
+ )}
100
+ </span>
101
+ ))}
102
+ </div>
103
+ </div>
104
+ </div>
105
+ ))}
106
+ </div>
107
+ );
108
+ }
@@ -0,0 +1,73 @@
1
+ import { Icon } from '../shared/Icon';
2
+
3
+ const CANCELABILITY_RULES = [
4
+ { status: 'TRIP_BOOK_PENDING', riderCancel: true, driverCancel: false, opsCancel: true, note: 'Before assignment — rider or ops can cancel freely' },
5
+ { status: 'TRIP_BOOK_RIDER_CONFIRMED', riderCancel: true, driverCancel: false, opsCancel: true, note: 'Rider confirmed but no driver yet' },
6
+ { status: 'TRIP_BOOK_OPERATION_DRIVER_CONFIRMED', riderCancel: true, driverCancel: true, opsCancel: true, note: 'Both confirmed — either party can still cancel' },
7
+ { status: 'TRIP_BOOK_FINISHED', riderCancel: false, driverCancel: false, opsCancel: false, note: 'Terminal: trip completed' },
8
+ { status: 'TRIP_BOOK_OPERATION_RIDER_CANCELED', riderCancel: false, driverCancel: false, opsCancel: false, note: 'Terminal: rider cancelled' },
9
+ { status: 'TRIP_BOOK_OPERATION_DRIVER_CANCELED', riderCancel: false, driverCancel: false, opsCancel: false, note: 'Terminal: driver cancelled' },
10
+ { status: 'TRIP_BOOK_SYSTEM_CANCELED', riderCancel: false, driverCancel: false, opsCancel: false, note: 'Terminal: system auto-cancelled (timeout)' },
11
+ ];
12
+
13
+ function CancelBadge({ allowed }: { allowed: boolean }) {
14
+ return allowed ? (
15
+ <span className="inline-flex items-center gap-0.5 text-emerald-400 text-[11px] font-medium">
16
+ <Icon name="check_circle" size={13} /> Yes
17
+ </span>
18
+ ) : (
19
+ <span className="inline-flex items-center gap-0.5 text-slate-600 text-[11px]">
20
+ <Icon name="block" size={13} /> No
21
+ </span>
22
+ );
23
+ }
24
+
25
+ export function CancelabilitySection() {
26
+ return (
27
+ <div className="space-y-4">
28
+ <div
29
+ className="rounded-xl border p-4"
30
+ style={{ background: 'rgba(20,17,24,0.5)', borderColor: 'var(--color-border-default)' }}
31
+ >
32
+ <p className="text-sm text-slate-400 leading-relaxed">
33
+ Cancellation is only possible before a booking reaches a <strong className="text-white">terminal state</strong>.
34
+ The terminal status guard in <code className="text-xs bg-white/5 px-1 rounded text-slate-300">fnUpdateBookingStatus</code> prevents
35
+ any further transitions once a booking is finished, cancelled by rider/driver, or auto-cancelled by the system.
36
+ </p>
37
+ </div>
38
+
39
+ <div
40
+ className="rounded-xl border overflow-hidden"
41
+ style={{ borderColor: 'var(--color-border-default)', background: 'rgba(20,17,24,0.5)' }}
42
+ >
43
+ <table className="w-full text-sm">
44
+ <thead style={{ background: 'rgba(255,255,255,0.05)' }}>
45
+ <tr>
46
+ <th className="px-4 py-2.5 text-left text-xs font-semibold text-slate-400">Booking Status</th>
47
+ <th className="px-4 py-2.5 text-center text-xs font-semibold text-slate-400">Rider</th>
48
+ <th className="px-4 py-2.5 text-center text-xs font-semibold text-slate-400">Driver</th>
49
+ <th className="px-4 py-2.5 text-center text-xs font-semibold text-slate-400">Ops</th>
50
+ </tr>
51
+ </thead>
52
+ <tbody>
53
+ {CANCELABILITY_RULES.map((rule) => (
54
+ <tr
55
+ key={rule.status}
56
+ className="border-t hover:bg-white/5 transition-colors"
57
+ style={{ borderColor: 'var(--color-border-default)' }}
58
+ >
59
+ <td className="px-4 py-2.5">
60
+ <div className="font-mono text-xs text-slate-300">{rule.status}</div>
61
+ <div className="text-[10px] text-slate-500 mt-0.5">{rule.note}</div>
62
+ </td>
63
+ <td className="px-4 py-2.5 text-center"><CancelBadge allowed={rule.riderCancel} /></td>
64
+ <td className="px-4 py-2.5 text-center"><CancelBadge allowed={rule.driverCancel} /></td>
65
+ <td className="px-4 py-2.5 text-center"><CancelBadge allowed={rule.opsCancel} /></td>
66
+ </tr>
67
+ ))}
68
+ </tbody>
69
+ </table>
70
+ </div>
71
+ </div>
72
+ );
73
+ }
@@ -0,0 +1,177 @@
1
+ import { useState } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { Icon } from '../shared/Icon';
4
+
5
+ interface Incident {
6
+ title: string;
7
+ severity: 'P1' | 'P2' | 'P3';
8
+ symptoms: string[];
9
+ diagnosis: string[];
10
+ resolution: string[];
11
+ escalation: string;
12
+ }
13
+
14
+ const SEVERITY_COLORS = {
15
+ P1: { color: '#ef4444', bg: 'rgba(239,68,68,0.1)', border: 'rgba(239,68,68,0.2)' },
16
+ P2: { color: '#f59e0b', bg: 'rgba(245,158,11,0.1)', border: 'rgba(245,158,11,0.2)' },
17
+ P3: { color: '#60a5fa', bg: 'rgba(96,165,250,0.1)', border: 'rgba(96,165,250,0.2)' },
18
+ };
19
+
20
+ const INCIDENTS: Incident[] = [
21
+ {
22
+ title: 'Optimistic Lock Failures Spike',
23
+ severity: 'P2',
24
+ symptoms: [
25
+ 'High rate of modifiedCount: 0 in logs',
26
+ 'Booking statuses appear stuck or outdated',
27
+ 'Users report stale booking UI',
28
+ ],
29
+ diagnosis: [
30
+ 'Check for concurrent request patterns (multiple webhooks firing simultaneously)',
31
+ 'Verify MongoDB connection pool is not exhausted',
32
+ 'Check if any deployment introduced a new middleware that reads/writes booking_status',
33
+ ],
34
+ resolution: [
35
+ 'The system is designed to skip silently on lock failures — this is safe by design',
36
+ 'If persistent: check for infinite retry loops in upstream services',
37
+ 'Monitor BullMQ job queue for backed-up jobs that might be causing contention',
38
+ ],
39
+ escalation: 'Backend team lead → Staff engineer if rate exceeds 5% of requests',
40
+ },
41
+ {
42
+ title: 'BullMQ Jobs Stuck / Not Processing',
43
+ severity: 'P1',
44
+ symptoms: [
45
+ 'Riders not receiving confirmation notifications',
46
+ 'Timeouts not firing — expired bookings remain active',
47
+ 'BullMQ dashboard shows jobs in "waiting" state indefinitely',
48
+ ],
49
+ diagnosis: [
50
+ 'Check Redis connectivity (BullMQ depends on Redis)',
51
+ 'Verify worker processes are running (pm2 list or k8s pod status)',
52
+ 'Check ENABLE_BOOKING_JOBS flag is true in the environment',
53
+ ],
54
+ resolution: [
55
+ 'Restart BullMQ worker pods/processes',
56
+ 'If Redis is down: restore from backup, jobs will auto-retry on reconnect',
57
+ 'For stuck individual jobs: use BullMQ admin to manually remove and reschedule',
58
+ ],
59
+ escalation: 'Immediate: on-call backend engineer → infrastructure team if Redis related',
60
+ },
61
+ {
62
+ title: 'DAC Card Orphans',
63
+ severity: 'P3',
64
+ symptoms: [
65
+ 'Driver app shows stale action cards for cancelled/completed trips',
66
+ 'DAC delete Pub/Sub messages failing silently',
67
+ ],
68
+ diagnosis: [
69
+ 'Check GCP Pub/Sub topic health and subscription backlog',
70
+ 'Verify PUB_SUB_DRIVER_ACTIONS_TOPIC environment variable',
71
+ 'Check DAC service logs for processing errors',
72
+ ],
73
+ resolution: [
74
+ 'DAC operations are fire-and-forget — orphans resolve when driver refreshes app',
75
+ 'For bulk cleanup: run a script that queries terminal-status trips and publishes delete messages',
76
+ 'Long-term: add a TTL-based auto-cleanup in DAC service',
77
+ ],
78
+ escalation: 'Driver platform team if affecting > 100 drivers',
79
+ },
80
+ {
81
+ title: 'Notification Delivery Failures',
82
+ severity: 'P2',
83
+ symptoms: [
84
+ 'Riders/drivers not receiving push notifications',
85
+ 'sendBookingNotificationWithConfig errors in logs',
86
+ 'FCM/APNS error codes in notification service logs',
87
+ ],
88
+ diagnosis: [
89
+ 'Check FCM/APNS service status',
90
+ 'Verify device tokens are valid and not expired',
91
+ 'Check notification service rate limits',
92
+ ],
93
+ resolution: [
94
+ 'Notifications are fire-and-forget — core booking flow continues regardless',
95
+ 'For critical notifications: trigger manual re-send through ops dashboard',
96
+ 'If FCM key expired: rotate in GCP console and redeploy',
97
+ ],
98
+ escalation: 'Notifications team → Mobile platform team if device token issues',
99
+ },
100
+ ];
101
+
102
+ export function CriticalRunbookSection() {
103
+ const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
104
+
105
+ return (
106
+ <div className="space-y-3">
107
+ {INCIDENTS.map((incident, i) => {
108
+ const isExpanded = expandedIndex === i;
109
+ const sc = SEVERITY_COLORS[incident.severity];
110
+ return (
111
+ <div
112
+ key={i}
113
+ className="rounded-xl border overflow-hidden transition-colors"
114
+ style={{
115
+ borderColor: isExpanded ? sc.border : 'var(--color-border-default)',
116
+ background: 'rgba(20,17,24,0.5)',
117
+ }}
118
+ >
119
+ <button
120
+ onClick={() => setExpandedIndex(isExpanded ? null : i)}
121
+ className="w-full px-4 py-3 flex items-center gap-3 hover:bg-white/5 transition-colors"
122
+ >
123
+ <span
124
+ className="text-[10px] font-bold px-2 py-0.5 rounded shrink-0"
125
+ style={{ background: sc.bg, color: sc.color, border: `1px solid ${sc.border}` }}
126
+ >
127
+ {incident.severity}
128
+ </span>
129
+ <span className="text-sm text-slate-200 font-medium text-left flex-1">{incident.title}</span>
130
+ <Icon name={isExpanded ? 'expand_less' : 'expand_more'} size={18} className="text-slate-500" />
131
+ </button>
132
+ <AnimatePresence>
133
+ {isExpanded && (
134
+ <motion.div
135
+ initial={{ height: 0, opacity: 0 }}
136
+ animate={{ height: 'auto', opacity: 1 }}
137
+ exit={{ height: 0, opacity: 0 }}
138
+ transition={{ duration: 0.2 }}
139
+ className="overflow-hidden"
140
+ >
141
+ <div className="px-4 pb-4 space-y-4 border-t" style={{ borderColor: 'var(--color-border-default)' }}>
142
+ {[
143
+ { title: 'Symptoms', items: incident.symptoms, icon: 'warning', color: '#fbbf24' },
144
+ { title: 'Diagnosis', items: incident.diagnosis, icon: 'search', color: '#60a5fa' },
145
+ { title: 'Resolution', items: incident.resolution, icon: 'build', color: '#22c55e' },
146
+ ].map((section) => (
147
+ <div key={section.title} className="pt-3">
148
+ <div className="flex items-center gap-1.5 mb-2">
149
+ <Icon name={section.icon} size={14} className={`text-[${section.color}]`} />
150
+ <span className="text-[10px] uppercase tracking-wider font-semibold" style={{ color: section.color }}>
151
+ {section.title}
152
+ </span>
153
+ </div>
154
+ <ul className="space-y-1.5">
155
+ {section.items.map((item, j) => (
156
+ <li key={j} className="flex items-start gap-2">
157
+ <span className="text-slate-600 mt-1">-</span>
158
+ <span className="text-xs text-slate-400">{item}</span>
159
+ </li>
160
+ ))}
161
+ </ul>
162
+ </div>
163
+ ))}
164
+ <div className="pt-2">
165
+ <span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold">Escalation: </span>
166
+ <span className="text-xs text-slate-400">{incident.escalation}</span>
167
+ </div>
168
+ </div>
169
+ </motion.div>
170
+ )}
171
+ </AnimatePresence>
172
+ </div>
173
+ );
174
+ })}
175
+ </div>
176
+ );
177
+ }
@@ -0,0 +1,102 @@
1
+ import { Icon } from '../shared/Icon';
2
+
3
+ const MIGRATION_STEPS = [
4
+ {
5
+ phase: 'Pre-migration',
6
+ steps: [
7
+ 'Verify booking_status field exists on Trip schema (additive — no breaking changes)',
8
+ 'Run validation query to count trips with is_booked: true that lack booking_status',
9
+ 'Backup: snapshot the trips collection before running backfill',
10
+ ],
11
+ },
12
+ {
13
+ phase: 'Backfill Script',
14
+ steps: [
15
+ 'Query all trips where is_booked: true AND booking_status is null',
16
+ 'For each trip, compute booking_status from current trip_status using TRIP_STATUS_TO_BOOKING_STATUS map',
17
+ 'Use bulkWrite with ordered: false for parallel processing',
18
+ 'Set booking_status_history to [{ status: computed_status, timestamp: trip.updatedAt }]',
19
+ ],
20
+ },
21
+ {
22
+ phase: 'Validation',
23
+ steps: [
24
+ 'Count trips where is_booked: true AND booking_status is null — should be 0',
25
+ 'Verify booking_status values are all valid enum values',
26
+ 'Spot-check 10 random trips: compare trip_status to booking_status for consistency',
27
+ 'Run the full QA test plan against staging with backfilled data',
28
+ ],
29
+ },
30
+ ];
31
+
32
+ const VALIDATION_QUERIES = [
33
+ {
34
+ label: 'Count un-migrated trips',
35
+ query: 'db.trips.countDocuments({ is_booked: true, booking_status: { $exists: false } })',
36
+ },
37
+ {
38
+ label: 'Verify valid statuses',
39
+ query: 'db.trips.distinct("booking_status", { is_booked: true })',
40
+ },
41
+ {
42
+ label: 'Check for null booking_status',
43
+ query: 'db.trips.countDocuments({ is_booked: true, booking_status: null })',
44
+ },
45
+ ];
46
+
47
+ export function DataMigrationSection() {
48
+ return (
49
+ <div className="space-y-5">
50
+ <div
51
+ className="rounded-xl border p-5"
52
+ style={{ background: 'rgba(20,17,24,0.5)', borderColor: 'var(--color-border-default)' }}
53
+ >
54
+ <p className="text-sm text-slate-400 leading-relaxed">
55
+ The <code className="text-xs bg-white/5 px-1.5 py-0.5 rounded text-slate-300">booking_status</code> field is
56
+ additive — it does not replace or modify any existing fields. Existing trips without this field continue to work normally.
57
+ Migration is only needed if you want historical trips to show booking status in the UI.
58
+ </p>
59
+ </div>
60
+
61
+ {MIGRATION_STEPS.map((phase) => (
62
+ <div
63
+ key={phase.phase}
64
+ className="rounded-xl border overflow-hidden"
65
+ style={{ background: 'rgba(20,17,24,0.5)', borderColor: 'var(--color-border-default)' }}
66
+ >
67
+ <div
68
+ className="px-4 py-2.5 border-b"
69
+ style={{ borderColor: 'var(--color-border-default)', background: 'rgba(255,255,255,0.03)' }}
70
+ >
71
+ <span className="text-sm font-bold text-slate-200">{phase.phase}</span>
72
+ </div>
73
+ <ul className="p-4 space-y-2">
74
+ {phase.steps.map((step, i) => (
75
+ <li key={i} className="flex items-start gap-2.5">
76
+ <Icon name="chevron_right" size={14} className="text-slate-600 mt-0.5 shrink-0" />
77
+ <span className="text-xs text-slate-400">{step}</span>
78
+ </li>
79
+ ))}
80
+ </ul>
81
+ </div>
82
+ ))}
83
+
84
+ <div
85
+ className="rounded-xl border p-4"
86
+ style={{ background: 'rgba(20,17,24,0.5)', borderColor: 'var(--color-border-default)' }}
87
+ >
88
+ <h4 className="text-sm font-bold text-slate-200 mb-3">Validation Queries</h4>
89
+ <div className="space-y-2">
90
+ {VALIDATION_QUERIES.map((q) => (
91
+ <div key={q.label}>
92
+ <span className="text-[10px] uppercase tracking-wider text-slate-500 font-semibold">{q.label}</span>
93
+ <pre className="mt-1 text-[11px] font-mono text-emerald-400 bg-black/30 rounded-lg px-3 py-2 overflow-x-auto">
94
+ {q.query}
95
+ </pre>
96
+ </div>
97
+ ))}
98
+ </div>
99
+ </div>
100
+ </div>
101
+ );
102
+ }
@@ -0,0 +1,98 @@
1
+ import { Icon } from '../shared/Icon';
2
+
3
+ const SCHEMA_FIELDS = [
4
+ { field: 'booking_status', type: 'String', description: 'Current booking lifecycle status (enum)', indexed: true, example: 'TRIP_BOOK_PENDING' },
5
+ { field: 'booking_status_history', type: 'Array<Object>', description: 'Audit trail of all status transitions', indexed: false, example: '[{ status, timestamp, reason? }]' },
6
+ { field: 'is_booked', type: 'Boolean', description: 'Flag indicating this is a scheduled ride', indexed: true, example: 'true' },
7
+ { field: 'booked_for', type: 'Date', description: 'Scheduled pickup time', indexed: true, example: '2024-12-25T14:00:00Z' },
8
+ ];
9
+
10
+ const INDEXES = [
11
+ { name: 'booking_status_1', fields: '{ booking_status: 1 }', purpose: 'Query trips by booking status' },
12
+ { name: 'is_booked_1_booking_status_1', fields: '{ is_booked: 1, booking_status: 1 }', purpose: 'Compound: find booked trips in specific status' },
13
+ { name: 'booked_for_1', fields: '{ booked_for: 1 }', purpose: 'Query trips by scheduled time (BullMQ job scheduling)' },
14
+ ];
15
+
16
+ const QUERY_PATTERNS = [
17
+ { name: 'Optimistic Lock Update', query: "Trip.updateOne(\n { _id: tripId, booking_status: currentStatus },\n { $set: { booking_status: newStatus }, $push: { booking_status_history: entry } }\n)", description: 'Core update pattern — only succeeds if status hasn\'t changed since read' },
18
+ { name: 'Find Pending Bookings', query: "Trip.find(\n { is_booked: true, booking_status: 'TRIP_BOOK_PENDING' }\n)", description: 'Used by BullMQ job scheduler to find trips needing confirmation' },
19
+ { name: 'Terminal Status Guard', query: "const TERMINAL = ['TRIP_BOOK_FINISHED', 'TRIP_BOOK_OPERATION_RIDER_CANCELED',\n 'TRIP_BOOK_OPERATION_DRIVER_CANCELED', 'TRIP_BOOK_SYSTEM_CANCELED'];\nif (TERMINAL.includes(trip.booking_status)) return;", description: 'Skip middleware if booking is already in terminal state' },
20
+ ];
21
+
22
+ export function DbSchemaSection() {
23
+ return (
24
+ <div className="space-y-5">
25
+ <div
26
+ className="rounded-xl border overflow-hidden"
27
+ style={{ borderColor: 'var(--color-border-default)', background: 'rgba(20,17,24,0.5)' }}
28
+ >
29
+ <div className="px-4 py-3 border-b flex items-center gap-2" style={{ borderColor: 'var(--color-border-default)', background: 'rgba(255,255,255,0.03)' }}>
30
+ <Icon name="storage" size={16} className="text-emerald-400" />
31
+ <span className="text-sm font-bold text-slate-200">Trip Schema — Booking Fields</span>
32
+ </div>
33
+ <table className="w-full text-sm">
34
+ <thead>
35
+ <tr style={{ background: 'rgba(255,255,255,0.05)' }}>
36
+ <th className="px-4 py-2 text-left text-xs font-semibold text-slate-400">Field</th>
37
+ <th className="px-4 py-2 text-left text-xs font-semibold text-slate-400">Type</th>
38
+ <th className="px-4 py-2 text-left text-xs font-semibold text-slate-400">Description</th>
39
+ <th className="px-4 py-2 text-center text-xs font-semibold text-slate-400">Indexed</th>
40
+ </tr>
41
+ </thead>
42
+ <tbody>
43
+ {SCHEMA_FIELDS.map((f) => (
44
+ <tr key={f.field} className="border-t hover:bg-white/5 transition-colors" style={{ borderColor: 'var(--color-border-default)' }}>
45
+ <td className="px-4 py-2.5 font-mono text-xs text-cyan-400">{f.field}</td>
46
+ <td className="px-4 py-2.5 font-mono text-[11px] text-slate-400">{f.type}</td>
47
+ <td className="px-4 py-2.5 text-xs text-slate-400">{f.description}</td>
48
+ <td className="px-4 py-2.5 text-center">
49
+ {f.indexed ? (
50
+ <Icon name="check_circle" size={14} className="text-emerald-400" />
51
+ ) : (
52
+ <Icon name="remove" size={14} className="text-slate-600" />
53
+ )}
54
+ </td>
55
+ </tr>
56
+ ))}
57
+ </tbody>
58
+ </table>
59
+ </div>
60
+
61
+ <div
62
+ className="rounded-xl border p-4"
63
+ style={{ background: 'rgba(20,17,24,0.5)', borderColor: 'var(--color-border-default)' }}
64
+ >
65
+ <h4 className="text-sm font-bold text-slate-200 mb-3">Indexes</h4>
66
+ <div className="space-y-2">
67
+ {INDEXES.map((idx) => (
68
+ <div key={idx.name} className="flex items-start gap-3 p-2 rounded-lg" style={{ background: 'rgba(255,255,255,0.02)' }}>
69
+ <Icon name="sort" size={14} className="text-amber-400 mt-0.5 shrink-0" />
70
+ <div>
71
+ <code className="text-xs font-mono text-slate-300">{idx.fields}</code>
72
+ <p className="text-[11px] text-slate-500 mt-0.5">{idx.purpose}</p>
73
+ </div>
74
+ </div>
75
+ ))}
76
+ </div>
77
+ </div>
78
+
79
+ <div
80
+ className="rounded-xl border p-4"
81
+ style={{ background: 'rgba(20,17,24,0.5)', borderColor: 'var(--color-border-default)' }}
82
+ >
83
+ <h4 className="text-sm font-bold text-slate-200 mb-3">Common Query Patterns</h4>
84
+ <div className="space-y-4">
85
+ {QUERY_PATTERNS.map((q) => (
86
+ <div key={q.name}>
87
+ <div className="text-xs font-semibold text-slate-300 mb-1">{q.name}</div>
88
+ <p className="text-[11px] text-slate-500 mb-2">{q.description}</p>
89
+ <pre className="text-[11px] font-mono text-emerald-400 bg-black/30 rounded-lg px-3 py-2 overflow-x-auto whitespace-pre-wrap">
90
+ {q.query}
91
+ </pre>
92
+ </div>
93
+ ))}
94
+ </div>
95
+ </div>
96
+ </div>
97
+ );
98
+ }