@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.
- package/CHANGELOG.md +42 -0
- package/README.md +51 -13
- package/dungeons/technical/ad-spend.js +2 -2
- package/dungeons/technical/anonymous-users.js +2 -2
- package/dungeons/technical/array-of-object-lookup.js +2 -4
- package/dungeons/technical/experiments.js +2 -2
- package/dungeons/technical/foobar.js +2 -2
- package/dungeons/technical/group-analytics.js +2 -2
- package/dungeons/technical/mirror-strategies.js +2 -2
- package/dungeons/technical/nested-objects.js +2 -2
- package/dungeons/technical/retention-cadence.js +2 -3
- package/dungeons/technical/sanity.js +2 -2
- package/dungeons/technical/scale-test.js +2 -2
- package/dungeons/technical/scd.js +2 -2
- package/dungeons/technical/simple.js +5 -6
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +3 -3
- package/dungeons/vertical/ai-platform.js +858 -0
- package/dungeons/vertical/community.js +84 -40
- package/dungeons/vertical/crypto.js +830 -0
- package/dungeons/vertical/dating.js +744 -0
- package/dungeons/vertical/devtools.js +175 -69
- package/dungeons/vertical/ecommerce.js +242 -94
- package/dungeons/vertical/education.js +330 -313
- package/dungeons/vertical/fintech.js +442 -313
- package/dungeons/vertical/fitness.js +143 -61
- package/dungeons/vertical/food-delivery.js +327 -353
- package/dungeons/vertical/gaming.js +912 -382
- package/dungeons/vertical/healthcare.js +142 -63
- package/dungeons/vertical/insurance-application.js +170 -76
- package/dungeons/vertical/logistics.js +115 -20
- package/dungeons/vertical/marketplace.js +152 -58
- package/dungeons/vertical/media.js +248 -384
- package/dungeons/vertical/real-estate.js +781 -0
- package/dungeons/vertical/sass.js +255 -266
- package/dungeons/vertical/social.js +264 -206
- package/dungeons/vertical/travel.js +117 -41
- package/index.js +17 -17
- package/lib/core/config-validator.js +159 -31
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +11 -14
- package/lib/generators/funnels.js +12 -4
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/orchestrators/user-loop.js +81 -47
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/lib/utils/utils.js +71 -39
- package/package.json +8 -2
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +251 -51
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -438
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/rpg-schema.json +0 -2491
- package/dungeons/vertical/rpg.js +0 -976
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- 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
|
|
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 (
|
|
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
|
|
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 (
|
|
90
|
+
* 2. NIGHT DEPLOY RISK (everything hook)
|
|
88
91
|
* ---------------------------------------------------------------
|
|
89
92
|
*
|
|
90
|
-
* PATTERN: Deployments between 10PM-6AM get
|
|
91
|
-
* "failed" 40% of the time.
|
|
92
|
-
*
|
|
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:
|
|
102
|
-
* - Expected:
|
|
104
|
+
* - Breakdown: hour of day
|
|
105
|
+
* - Expected: 22:00-05:59 hours show ~ 40% failure vs ~ 15% baseline
|
|
103
106
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
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
|
|
152
|
+
* 5. OPEN SOURCE POWER USAGE (everything hook)
|
|
157
153
|
* ---------------------------------------------------------------
|
|
158
154
|
*
|
|
159
|
-
* PATTERN: OSS users with >15
|
|
160
|
-
*
|
|
161
|
-
*
|
|
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:
|
|
166
|
-
* - Report type: Insights
|
|
167
|
-
* -
|
|
168
|
-
* -
|
|
169
|
-
* -
|
|
170
|
-
* -
|
|
171
|
-
* - Expected:
|
|
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:
|
|
174
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
259
|
-
|
|
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
|
-
//
|
|
752
|
-
//
|
|
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
|
|
828
|
-
// OSS users with >15 events get
|
|
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
|
|
831
|
-
|
|
832
|
-
|
|
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
|
|
838
|
-
|
|
839
|
-
|
|
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(
|
|
947
|
+
return t.isAfter(RECOVERY_START) && t.isBefore(RECOVERY_END);
|
|
844
948
|
});
|
|
845
949
|
deployEvents.forEach(dep => {
|
|
846
|
-
|
|
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:
|
|
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",
|