@ak--47/dungeon-master 1.2.3 → 1.3.1

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 (89) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +51 -13
  3. package/dungeons/technical/ad-spend.js +2 -2
  4. package/dungeons/technical/anonymous-users.js +2 -2
  5. package/dungeons/technical/array-of-object-lookup.js +2 -4
  6. package/dungeons/technical/experiments.js +2 -2
  7. package/dungeons/technical/foobar.js +2 -2
  8. package/dungeons/technical/group-analytics.js +2 -2
  9. package/dungeons/technical/mirror-strategies.js +2 -2
  10. package/dungeons/technical/nested-objects.js +2 -2
  11. package/dungeons/technical/retention-cadence.js +2 -3
  12. package/dungeons/technical/sanity.js +2 -2
  13. package/dungeons/technical/scale-test.js +2 -2
  14. package/dungeons/technical/scd.js +2 -2
  15. package/dungeons/technical/simple.js +5 -6
  16. package/dungeons/technical/simplest-schema.json +5 -0
  17. package/dungeons/technical/simplest.js +2 -2
  18. package/dungeons/technical/text-generation.js +3 -3
  19. package/dungeons/vertical/ai-platform.js +858 -0
  20. package/dungeons/vertical/community.js +84 -40
  21. package/dungeons/vertical/crypto.js +830 -0
  22. package/dungeons/vertical/dating.js +744 -0
  23. package/dungeons/vertical/devtools.js +175 -69
  24. package/dungeons/vertical/ecommerce.js +242 -94
  25. package/dungeons/vertical/education.js +330 -313
  26. package/dungeons/vertical/fintech.js +442 -313
  27. package/dungeons/vertical/fitness.js +143 -61
  28. package/dungeons/vertical/food-delivery.js +327 -353
  29. package/dungeons/vertical/gaming.js +912 -382
  30. package/dungeons/vertical/healthcare.js +142 -63
  31. package/dungeons/vertical/insurance-application.js +170 -76
  32. package/dungeons/vertical/logistics.js +115 -20
  33. package/dungeons/vertical/marketplace.js +152 -58
  34. package/dungeons/vertical/media.js +248 -384
  35. package/dungeons/vertical/real-estate.js +781 -0
  36. package/dungeons/vertical/sass.js +255 -266
  37. package/dungeons/vertical/social.js +264 -206
  38. package/dungeons/vertical/travel.js +117 -41
  39. package/index.js +17 -17
  40. package/lib/core/config-validator.js +159 -31
  41. package/lib/core/context.js +10 -24
  42. package/lib/core/storage.js +6 -1
  43. package/lib/generators/events.js +11 -14
  44. package/lib/generators/funnels.js +12 -4
  45. package/lib/generators/mirror.js +3 -2
  46. package/lib/generators/product-names.js +1 -1
  47. package/lib/generators/scd.js +2 -1
  48. package/lib/generators/text.js +1 -1
  49. package/lib/orchestrators/user-loop.js +81 -47
  50. package/lib/templates/macro-presets.js +111 -0
  51. package/lib/templates/soup-presets.js +19 -36
  52. package/lib/utils/utils.js +71 -39
  53. package/package.json +8 -2
  54. package/scripts/smoke-test-all.mjs +162 -0
  55. package/scripts/verify-runner.mjs +72 -24
  56. package/types.d.ts +251 -51
  57. package/dungeons/technical/ad-spend-schema.json +0 -128
  58. package/dungeons/technical/anonymous-users-schema.json +0 -92
  59. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  60. package/dungeons/technical/experiments-schema.json +0 -203
  61. package/dungeons/technical/foobar-schema.json +0 -362
  62. package/dungeons/technical/group-analytics-schema.json +0 -241
  63. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  64. package/dungeons/technical/nested-objects-schema.json +0 -145
  65. package/dungeons/technical/retention-cadence-schema.json +0 -37
  66. package/dungeons/technical/sanity-schema.json +0 -185
  67. package/dungeons/technical/scale-test-schema.json +0 -70
  68. package/dungeons/technical/scd-schema.json +0 -467
  69. package/dungeons/technical/simple-schema.json +0 -362
  70. package/dungeons/technical/text-generation-schema.json +0 -1062
  71. package/dungeons/user/.gitkeep +0 -0
  72. package/dungeons/vertical/community-schema.json +0 -579
  73. package/dungeons/vertical/devtools-schema.json +0 -601
  74. package/dungeons/vertical/ecommerce-schema.json +0 -604
  75. package/dungeons/vertical/education-schema.json +0 -5686
  76. package/dungeons/vertical/fintech-schema.json +0 -630
  77. package/dungeons/vertical/fitness-schema.json +0 -530
  78. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  79. package/dungeons/vertical/gaming-schema.json +0 -438
  80. package/dungeons/vertical/healthcare-schema.json +0 -549
  81. package/dungeons/vertical/insurance-application-schema.json +0 -485
  82. package/dungeons/vertical/logistics-schema.json +0 -574
  83. package/dungeons/vertical/marketplace-schema.json +0 -533
  84. package/dungeons/vertical/media-schema.json +0 -4749
  85. package/dungeons/vertical/rpg-schema.json +0 -2491
  86. package/dungeons/vertical/rpg.js +0 -976
  87. package/dungeons/vertical/sass-schema.json +0 -3128
  88. package/dungeons/vertical/social-schema.json +0 -620
  89. package/dungeons/vertical/travel-schema.json +0 -580
@@ -2,7 +2,7 @@
2
2
  const SEED = "dm4-devtools";
3
3
  const num_days = 100;
4
4
  const num_users = 5_000;
5
- const avg_events_per_user = 120;
5
+ const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -16,9 +16,6 @@ import * as v from "ak-tools";
16
16
 
17
17
  dayjs.extend(utc);
18
18
  const chance = u.initChance(SEED);
19
- const NOW = dayjs();
20
- const DATASET_START = NOW.subtract(num_days, "days");
21
-
22
19
  /** @typedef {import("../../types").Dungeon} Config */
23
20
 
24
21
  // Generate consistent pipeline/repo IDs at module level
@@ -58,9 +55,17 @@ const repoIds = v.range(1, 150).map(() => `REPO_${v.uid(6)}`);
58
55
 
59
56
  /**
60
57
  * ===============================================================
61
- * ANALYTICS HOOKS (8 hooks)
58
+ * ANALYTICS HOOKS (10 hooks)
59
+ *
60
+ * Adds 10. BUILD-DEPLOY TIME-TO-CONVERT: enterprise/business 0.67x faster, free
61
+ * 1.33x slower (funnel-post). Discover via Build-Deploy Pipeline median TTC by tier.
62
+ * NOTE (funnel-post measurement): visible only via Mixpanel funnel median TTC.
63
+ * Cross-event MIN→MIN SQL queries on raw events do NOT show this.
62
64
  * ===============================================================
63
65
  *
66
+ * NOTE: All cohort effects are HIDDEN — discoverable only via behavioral
67
+ * cohorts or raw-prop breakdowns. No cohort flag is stamped on events.
68
+ *
64
69
  * ---------------------------------------------------------------
65
70
  * 1. BUILD FAILURE CASCADE (event hook)
66
71
  * ---------------------------------------------------------------
@@ -76,40 +81,31 @@ const repoIds = v.range(1, 150).map(() => `REPO_${v.uid(6)}`);
76
81
  * - Event: "build completed"
77
82
  * - Measure: Average of "build_duration_sec"
78
83
  * - Breakdown: "build_status"
79
- * - Expected: failed ~2x longer than success
80
- * (failed ~ 480s, success ~ 240s)
84
+ * - Expected: failed ~ 2x longer than success
81
85
  *
82
86
  * REAL-WORLD ANALOGUE: Failed CI builds run full test suites,
83
- * timeout waiting for flaky tests, and trigger retry cascades
84
- * that inflate overall build times.
87
+ * timeout, and trigger retry cascades that inflate build times.
85
88
  *
86
89
  * ---------------------------------------------------------------
87
- * 2. NIGHT DEPLOY RISK (event hook)
90
+ * 2. NIGHT DEPLOY RISK (everything hook)
88
91
  * ---------------------------------------------------------------
89
92
  *
90
- * PATTERN: Deployments between 10PM-6AM get status forced to
91
- * "failed" 40% of the time. Night deploys are risky because
92
- * fewer engineers are online to catch issues.
93
+ * PATTERN: Deployments between 10PM-6AM UTC get deploy_status forced
94
+ * to "failed" 40% of the time. No flag analyst breaks down by
95
+ * hour-of-day on deployment-completed events to discover the risk window.
93
96
  *
94
97
  * HOW TO FIND IT IN MIXPANEL:
95
98
  *
96
- * Report 1: Deploy Failure Rate by Hour
99
+ * Report 1: Deploy Failure Rate by Hour of Day
97
100
  * - Report type: Insights
98
101
  * - Event: "deployment completed"
99
102
  * - Measure: Total
100
103
  * - Filter: deploy_status = "failed"
101
- * - Breakdown: "is_night_deploy"
102
- * - Expected: is_night_deploy=true shows ~40% failure vs ~15% baseline
104
+ * - Breakdown: hour of day
105
+ * - Expected: 22:00-05:59 hours show ~ 40% failure vs ~ 15% baseline
103
106
  *
104
- * Report 2: Night Deploy Volume
105
- * - Report type: Insights
106
- * - Event: "deployment completed"
107
- * - Measure: Total
108
- * - Breakdown: "is_night_deploy"
109
- * - Expected: ~15-20% of deploys happen at night
110
- *
111
- * REAL-WORLD ANALOGUE: Night and weekend deploys have higher
112
- * failure rates due to skeleton crews and delayed incident response.
107
+ * REAL-WORLD ANALOGUE: Night deploys fail more due to skeleton crews
108
+ * and delayed incident response.
113
109
  *
114
110
  * ---------------------------------------------------------------
115
111
  * 3. COPILOT ADOPTION -> PR VELOCITY (everything hook)
@@ -153,26 +149,26 @@ const repoIds = v.range(1, 150).map(() => `REPO_${v.uid(6)}`);
153
149
  * in SRE/DevOps. Alert fatigue degrades response quality over time.
154
150
  *
155
151
  * ---------------------------------------------------------------
156
- * 5. OPEN SOURCE -> PAID CONVERSION (everything hook)
152
+ * 5. OPEN SOURCE POWER USAGE (everything hook)
157
153
  * ---------------------------------------------------------------
158
154
  *
159
- * PATTERN: OSS users with >15 total events get 30% of their events
160
- * converted to team-tier behavior (subscription_tier set to "team").
161
- * Power OSS users eventually hit limits and convert.
155
+ * PATTERN: OSS-segment users with >15 events get extra cloned build
156
+ * + deploy events in their later activity (representing power usage).
157
+ * Cloned events use unique offset timestamps. No flag — discover via
158
+ * cohort by segment + event count, observing per-user build/deploy volume.
162
159
  *
163
160
  * HOW TO FIND IT IN MIXPANEL:
164
161
  *
165
- * Report 1: Tier Migration for OSS Users
166
- * - Report type: Insights
167
- * - Event: any event
168
- * - Measure: Total
169
- * - Filter: segment = "oss_user"
170
- * - Breakdown: "subscription_tier"
171
- * - Expected: ~30% of active OSS user events show "team" tier
162
+ * Report 1: Builds per User — Active OSS Users
163
+ * - Report type: Insights (with cohort)
164
+ * - Cohort A: segment = "oss_user" AND events >= 15
165
+ * - Cohort B: segment = "oss_user" AND events < 15
166
+ * - Event: "build completed"
167
+ * - Measure: Total per user
168
+ * - Expected: A noticeably higher than B
172
169
  *
173
- * REAL-WORLD ANALOGUE: Open source users who exceed free-tier
174
- * limits (build minutes, private repos) organically convert to
175
- * paid plans.
170
+ * REAL-WORLD ANALOGUE: Power OSS users hit free-tier limits via
171
+ * heavy build/deploy volume.
176
172
  *
177
173
  * ---------------------------------------------------------------
178
174
  * 6. POST-OUTAGE RECOVERY (everything hook)
@@ -232,8 +228,38 @@ const repoIds = v.range(1, 150).map(() => `REPO_${v.uid(6)}`);
232
228
  * - Expected: enterprise ~ 60% vs free ~ 40% conversion
233
229
  *
234
230
  * REAL-WORLD ANALOGUE: Enterprise CI/CD customers get priority
235
- * runners, dedicated support, and SLA-backed uptime guarantees
236
- * that dramatically improve pipeline reliability.
231
+ * runners, dedicated support, and SLA-backed uptime guarantees.
232
+ *
233
+ * ---------------------------------------------------------------
234
+ * 9. BUILD-COUNT MAGIC NUMBER (everything hook)
235
+ * ---------------------------------------------------------------
236
+ *
237
+ * PATTERN: Users with 15-30 "build completed" events sit in the
238
+ * healthy CI sweet spot — 30% extra deploy events are cloned (unique
239
+ * timestamps). Users with 31+ builds suffer flaky-CI burnout; ~25%
240
+ * of their deploy events drop. No flag — discover by binning users
241
+ * on build-count and comparing per-user deploy volume.
242
+ *
243
+ * HOW TO FIND IT IN MIXPANEL:
244
+ *
245
+ * Report 1: Deploys per User by Build Bucket
246
+ * - Report type: Insights (with cohort)
247
+ * - Cohort A: users with 15-30 "build completed"
248
+ * - Cohort B: users with 0-14 "build completed"
249
+ * - Event: "deployment completed"
250
+ * - Measure: Total per user
251
+ * - Expected: A ~ 1.3x B
252
+ *
253
+ * Report 2: Deploys per User on Heavy Builders
254
+ * - Report type: Insights (with cohort)
255
+ * - Cohort C: users with >= 31 "build completed"
256
+ * - Cohort A: users with 15-30
257
+ * - Event: "deployment completed"
258
+ * - Measure: Total per user
259
+ * - Expected: C ~ 25% fewer deploys per user vs A
260
+ *
261
+ * REAL-WORLD ANALOGUE: Healthy CI cadence drives reliable deploys;
262
+ * runaway builds signal a flaky pipeline that scares teams off ships.
237
263
  *
238
264
  * ===============================================================
239
265
  * EXPECTED METRICS SUMMARY
@@ -245,18 +271,22 @@ const repoIds = v.range(1, 150).map(() => `REPO_${v.uid(6)}`);
245
271
  * Night Deploy Risk | deploy failure rate | 15% | 40% | 2.7x
246
272
  * Copilot PR Velocity | PRs/user | 3 | 4.5 | 1.5x
247
273
  * On-Call Fatigue | response_time_min | 30min | 90min | 3x
248
- * OSS -> Paid Conversion | team tier events | 0% | 30% | -
274
+ * OSS Power Usage | builds/user (active)| 1x | ~ 1.3x | 1.3x
249
275
  * Post-Outage Recovery | deploys/day | 50 | 100+ | 2x+
250
276
  * DevOps Lead Profiles | team_size | 10 | 30 | 3x
251
- * Enterprise Funnel Lift | funnel conversion | 40% | 60% | ~1.5x
277
+ * Enterprise Funnel Lift | funnel conversion | 40% | 60% | ~ 1.5x
278
+ * Build-Count Magic Number | sweet deploys/user | 1x | 1.3x | 1.3x
279
+ * Build-Count Magic Number | over deploys/user | 1x | 0.75x | -25%
252
280
  */
253
281
 
254
282
  /** @type {Config} */
255
283
  const config = {
256
284
  token,
257
285
  seed: SEED,
258
- numDays: num_days,
259
- numEvents: num_users * avg_events_per_user,
286
+ datasetStart: "2026-01-01T00:00:00Z",
287
+ datasetEnd: "2026-04-28T23:59:59Z",
288
+ // numDays: num_days,
289
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
260
290
  numUsers: num_users,
261
291
  hasAnonIds: false,
262
292
  hasSessionIds: true,
@@ -271,7 +301,6 @@ const config = {
271
301
  hasCampaigns: false,
272
302
  isAnonymous: false,
273
303
  hasAdSpend: false,
274
- percentUsersBornInDataset: 35,
275
304
  hasAvatar: true,
276
305
  concurrency: 1,
277
306
  writeToDisk: false,
@@ -318,7 +347,6 @@ const config = {
318
347
  deploy_status: ["success", "success", "success", "failed", "rolled_back"],
319
348
  environment: ["production", "production", "staging", "staging", "dev", "preview"],
320
349
  deploy_duration_sec: u.weighNumRange(15, 300, 0.4, 90),
321
- is_night_deploy: [false],
322
350
  preview_enabled: [false],
323
351
  },
324
352
  },
@@ -718,6 +746,27 @@ const config = {
718
746
 
719
747
  // -- Hook Function ----------------------------------------
720
748
  hook: function (record, type, meta) {
749
+ // HOOK 10 (T2C): BUILD-DEPLOY TIME-TO-CONVERT (funnel-post)
750
+ // Enterprise tier completes Build-Deploy Pipeline funnel 1.5x faster
751
+ // (factor 0.67); free tier 1.33x slower (factor 1.33).
752
+ if (type === "funnel-post") {
753
+ const segment = meta?.profile?.subscription_tier;
754
+ if (Array.isArray(record) && record.length > 1) {
755
+ const factor = (
756
+ segment === "enterprise" || segment === "business" ? 0.67 :
757
+ segment === "free" ? 1.33 :
758
+ 1.0
759
+ );
760
+ if (factor !== 1.0) {
761
+ for (let i = 1; i < record.length; i++) {
762
+ const prev = dayjs(record[i - 1].time);
763
+ const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
764
+ record[i].time = prev.add(newGap, "milliseconds").toISOString();
765
+ }
766
+ }
767
+ }
768
+ }
769
+
721
770
  // -- HOOK 7: DEVOPS LEAD PROFILE ENRICHMENT (user) --------
722
771
  // DevOps leads get team_size 10-50 and repos_connected 5-20.
723
772
  // Platform engineers get moderate boosts. Others stay at defaults.
@@ -748,21 +797,13 @@ const config = {
748
797
  record.build_duration_sec = Math.floor((record.build_duration_sec || 240) * 2);
749
798
  }
750
799
 
751
- // -- HOOK 2: NIGHT DEPLOY RISK (event) ----------------
752
- // Deployments between 10PM-6AM get forced to "failed" 40% of the time.
753
- if (record.event === "deployment completed") {
754
- const hour = dayjs(record.time).hour();
755
- if (hour >= 22 || hour < 6) {
756
- record.is_night_deploy = true;
757
- if (chance.bool({ likelihood: 40 })) {
758
- record.deploy_status = "failed";
759
- }
760
- }
761
- }
800
+ // (HOOK 2: NIGHT DEPLOY RISK moved to everything hook — hour checks
801
+ // must run after bunchIntoSessions redistributes timestamps)
762
802
  }
763
803
 
764
804
  // -- EVERYTHING HOOKS -------------------------------------
765
805
  if (type === "everything") {
806
+ const datasetStart = dayjs.unix(meta.datasetStart);
766
807
  let events = record;
767
808
  if (!events.length) return record;
768
809
  const profile = meta && meta.profile ? meta.profile : {};
@@ -776,6 +817,19 @@ const config = {
776
817
  if (profile.language) e.language = profile.language;
777
818
  });
778
819
 
820
+ // -- HOOK 2: NIGHT DEPLOY RISK -------------------------
821
+ // Deployments between 10PM-6AM get deploy_status forced to
822
+ // "failed" 40% of the time. No flag — analyst breaks down by
823
+ // hour-of-day on deployment completed events.
824
+ events.forEach(e => {
825
+ if (e.event === "deployment completed") {
826
+ const hour = new Date(e.time).getUTCHours();
827
+ if ((hour >= 22 || hour < 6) && chance.bool({ likelihood: 40 })) {
828
+ e.deploy_status = "failed";
829
+ }
830
+ }
831
+ });
832
+
779
833
  // -- HOOK 8: ENTERPRISE BUILD-DEPLOY FUNNEL LIFT ------
780
834
  // Free-tier users drop 35% of final funnel step events to
781
835
  // create visible conversion gap vs paid subscribers.
@@ -824,29 +878,81 @@ const config = {
824
878
  });
825
879
  }
826
880
 
827
- // -- HOOK 5: OPEN SOURCE -> PAID CONVERSION -----------
828
- // OSS users with >15 events get 30% of events converted to "team" tier.
881
+ // -- HOOK 5: OPEN SOURCE POWER USAGE ------------------
882
+ // OSS users with >15 events get extra cloned build + deploy events
883
+ // in their later activity (representing power usage that pushes them
884
+ // toward limits). No flag — discover via cohort by segment + event count.
829
885
  if (profile.segment === "oss_user" && events.length > 15) {
830
- const conversionPoint = Math.floor(events.length * 0.7);
831
- for (let i = conversionPoint; i < events.length; i++) {
832
- events[i].subscription_tier = "team";
886
+ const buildTemplate = events.find(e => e.event === "build completed");
887
+ const deployTemplate = events.find(e => e.event === "deployment completed");
888
+ if (buildTemplate || deployTemplate) {
889
+ const conversionPoint = Math.floor(events.length * 0.7);
890
+ const tail = events.slice(conversionPoint);
891
+ tail.forEach(e => {
892
+ const tBase = dayjs(e.time);
893
+ if (buildTemplate && chance.bool({ likelihood: 30 })) {
894
+ events.push({
895
+ ...buildTemplate,
896
+ time: tBase.add(chance.integer({ min: 5, max: 240 }), "minutes").toISOString(),
897
+ user_id: e.user_id,
898
+ });
899
+ }
900
+ if (deployTemplate && chance.bool({ likelihood: 20 })) {
901
+ events.push({
902
+ ...deployTemplate,
903
+ time: tBase.add(chance.integer({ min: 10, max: 240 }), "minutes").toISOString(),
904
+ user_id: e.user_id,
905
+ });
906
+ }
907
+ });
908
+ }
909
+ }
910
+
911
+ // -- HOOK 9: BUILD-COUNT MAGIC NUMBER (no flags) ------
912
+ // Sweet 15-30 builds → +30% deploys (clone with unique offset).
913
+ // Over 31+ → drop 25% of deploys (flaky CI burnout).
914
+ const buildCount = events.filter(e => e.event === "build completed").length;
915
+ if (buildCount >= 15 && buildCount <= 30) {
916
+ const deployTemplate = events.find(e => e.event === "deployment completed");
917
+ if (deployTemplate) {
918
+ const deploys = events.filter(e => e.event === "deployment completed");
919
+ const extras = Math.floor(deploys.length * 0.3);
920
+ for (let k = 0; k < extras; k++) {
921
+ const tpl = deploys[k % deploys.length];
922
+ events.push({
923
+ ...tpl,
924
+ time: dayjs(tpl.time).add(chance.integer({ min: 5, max: 360 }), "minutes").toISOString(),
925
+ user_id: tpl.user_id,
926
+ });
927
+ }
928
+ }
929
+ } else if (buildCount >= 31) {
930
+ for (let i = events.length - 1; i >= 0; i--) {
931
+ if (events[i].event === "deployment completed" && chance.bool({ likelihood: 25 })) {
932
+ events.splice(i, 1);
933
+ }
833
934
  }
834
935
  }
835
936
 
836
937
  // -- HOOK 6: POST-OUTAGE RECOVERY ---------------------
837
- // After outage ends (day 42.25), deployment events get cloned.
838
- const OUTAGE_END = DATASET_START.add(42.25, "days");
839
- const RECOVERY_END = DATASET_START.add(46, "days");
938
+ // After the outage volume rebound (d44-48), deployment events get
939
+ // aggressively cloned to produce a visible spike above baseline.
940
+ // Shifted later than outage end (d42.25) so natural volume has
941
+ // recovered from the 0.05x suppression before cloning kicks in.
942
+ const RECOVERY_START = datasetStart.add(44, "days");
943
+ const RECOVERY_END = datasetStart.add(48, "days");
840
944
  const deployEvents = events.filter(e => {
841
945
  if (e.event !== "deployment completed") return false;
842
946
  const t = dayjs(e.time);
843
- return t.isAfter(OUTAGE_END) && t.isBefore(RECOVERY_END);
947
+ return t.isAfter(RECOVERY_START) && t.isBefore(RECOVERY_END);
844
948
  });
845
949
  deployEvents.forEach(dep => {
846
- if (chance.bool({ likelihood: 50 })) {
950
+ // 100% clone rate with 3 copies per event to clearly
951
+ // exceed baseline deploy volume (d35-41)
952
+ for (let c = 0; c < 3; c++) {
847
953
  events.push({
848
954
  ...dep,
849
- time: dayjs(dep.time).add(chance.integer({ min: 1, max: 6 }), "hours").toISOString(),
955
+ time: dayjs(dep.time).add(chance.integer({ min: 1, max: 8 }), "hours").toISOString(),
850
956
  user_id: dep.user_id,
851
957
  deploy_status: chance.pickone(["success", "success", "rolled_back"]),
852
958
  environment: "production",