@ak--47/dungeon-master 1.6.0 → 1.6.2
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/.claude/skills/powertools/SKILL.md +75 -0
- package/.claude/skills/powertools/pt.mjs +91 -0
- package/.claude/skills/powertools/snapshot-project.mjs +124 -0
- package/CHANGELOG.md +138 -0
- package/README.md +2 -0
- package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +4 -5
- package/dungeons/vertical/community/community.verify.mjs +4 -6
- package/dungeons/vertical/crypto/crypto.verify.mjs +4 -2
- package/dungeons/vertical/dating/dating.verify.mjs +4 -6
- package/dungeons/vertical/devtools/devtools.verify.mjs +4 -2
- package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +4 -4
- package/dungeons/vertical/education/education.verify.mjs +4 -9
- package/dungeons/vertical/fintech/fintech.verify.mjs +4 -4
- package/dungeons/vertical/fitness/fitness.verify.mjs +4 -5
- package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +4 -2
- package/dungeons/vertical/gaming/gaming.verify.mjs +4 -4
- package/dungeons/vertical/healthcare/healthcare.verify.mjs +4 -6
- package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +4 -2
- package/dungeons/vertical/logistics/logistics.verify.mjs +4 -7
- package/dungeons/vertical/marketplace/marketplace.verify.mjs +4 -2
- package/dungeons/vertical/media/media.verify.mjs +4 -2
- package/dungeons/vertical/real-estate/real-estate.verify.mjs +4 -9
- package/dungeons/vertical/sass/sass.verify.mjs +4 -2
- package/dungeons/vertical/social/social.verify.mjs +4 -2
- package/dungeons/vertical/streaming/streaming.verify.mjs +4 -2
- package/dungeons/vertical/support-desk/support-desk.verify.mjs +4 -2
- package/dungeons/vertical/travel/travel.verify.mjs +4 -6
- package/index.js +35 -2
- package/lib/core/config-validator.js +19 -1
- package/lib/orchestrators/mixpanel-sender.js +52 -2
- package/lib/utils/utils.js +146 -55
- package/lib/verify/index.js +6 -0
- package/lib/verify/verify-dungeon.js +39 -12
- package/package.json +4 -4
- package/scripts/verify-stories.mjs +2 -1
- package/types.d.ts +17 -0
package/lib/utils/utils.js
CHANGED
|
@@ -36,9 +36,35 @@ let globalUserChance;
|
|
|
36
36
|
let userChanceInitialized = false;
|
|
37
37
|
|
|
38
38
|
// Module-scoped memoization cache for weighted-array resolvers in `choose()`.
|
|
39
|
-
//
|
|
39
|
+
// Key is the function source string; cleared per run by resetValueCaches().
|
|
40
|
+
// Functions tagged `noCache = true` (e.g. pickAWinner closures, whose source
|
|
41
|
+
// strings are identical across instances) bypass this cache entirely.
|
|
40
42
|
const weightedArrayCache = new Map();
|
|
41
43
|
|
|
44
|
+
// v1.6.1: per-run winner memo for unweighted string arrays. Two layers:
|
|
45
|
+
// a WeakMap keyed by array IDENTITY holding the full {idx, weights} entry
|
|
46
|
+
// (property arrays are stable references across a run, so the steady state is
|
|
47
|
+
// a single WeakMap hit per event — no join, no weight rebuild), plus a Map
|
|
48
|
+
// keyed by array CONTENTS so distinct array instances with equal contents
|
|
49
|
+
// share one winner. Guarantees ONE stable winner per array per run so
|
|
50
|
+
// aggregate distributions stay visibly skewed instead of cancelling to
|
|
51
|
+
// uniform. Cleared by resetValueCaches() at run start / initChance so winners
|
|
52
|
+
// never leak across runs or seeds.
|
|
53
|
+
let winnerEntryCache = new WeakMap();
|
|
54
|
+
const winnerCache = new Map();
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Clear the per-run value-resolution caches (winner memo + weighted-array
|
|
58
|
+
* resolver cache). Called by initChance() and at the top of every runDungeon
|
|
59
|
+
* so in-process back-to-back runs (different seeds, different users) never
|
|
60
|
+
* inherit a prior run's winners.
|
|
61
|
+
*/
|
|
62
|
+
function resetValueCaches() {
|
|
63
|
+
winnerEntryCache = new WeakMap();
|
|
64
|
+
winnerCache.clear();
|
|
65
|
+
weightedArrayCache.clear();
|
|
66
|
+
}
|
|
67
|
+
|
|
42
68
|
// v1.5.1: dataset-window state moved to AsyncLocalStorage scope
|
|
43
69
|
// (`lib/utils/dataset-context.js`). Each `generate()` call wraps the pipeline
|
|
44
70
|
// in `runWithDataset(begin, now, fn)`, and factory thunks read via the
|
|
@@ -146,6 +172,7 @@ function initChance(seed) {
|
|
|
146
172
|
if (!seed && process.env.SEED) seed = process.env.SEED;
|
|
147
173
|
globalChance = new Chance(seed);
|
|
148
174
|
chanceInitialized = true;
|
|
175
|
+
resetValueCaches();
|
|
149
176
|
return globalChance;
|
|
150
177
|
}
|
|
151
178
|
|
|
@@ -387,10 +414,10 @@ function choose(value) {
|
|
|
387
414
|
// check to make sure that each element in the array only occurs once...
|
|
388
415
|
const uniqueItems = new Set(value);
|
|
389
416
|
if (uniqueItems.size === value.length) {
|
|
390
|
-
// Array has no duplicates
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
return
|
|
417
|
+
// Array has no duplicates → power-law draw with ONE stable
|
|
418
|
+
// seed-deterministic winner per array per run (v1.6.1)
|
|
419
|
+
const entry = getWinnerEntry(/** @type {string[]} */ (value));
|
|
420
|
+
return chance.weighted(value, entry.weights);
|
|
394
421
|
}
|
|
395
422
|
|
|
396
423
|
}
|
|
@@ -408,8 +435,17 @@ function choose(value) {
|
|
|
408
435
|
}
|
|
409
436
|
|
|
410
437
|
try {
|
|
411
|
-
// Keep resolving the value if it's a function (with caching)
|
|
438
|
+
// Keep resolving the value if it's a function (with caching).
|
|
439
|
+
// Functions tagged noCache (pickAWinner closures) skip the source-string
|
|
440
|
+
// cache: their toString() is identical across instances, so caching by
|
|
441
|
+
// source would hand one property's expansion to every other property.
|
|
412
442
|
while (typeof value === 'function') {
|
|
443
|
+
if (/** @type {any} */ (value).noCache === true) {
|
|
444
|
+
const result = value();
|
|
445
|
+
if (result instanceof ListValue) return result;
|
|
446
|
+
value = result;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
413
449
|
const funcString = value.toString();
|
|
414
450
|
|
|
415
451
|
if (weightedArrayCache.has(funcString)) {
|
|
@@ -452,11 +488,6 @@ function choose(value) {
|
|
|
452
488
|
}
|
|
453
489
|
}
|
|
454
490
|
|
|
455
|
-
// ["","",""] should pick-a-winner
|
|
456
|
-
if (Array.isArray(value) && typeof value[0] === "string") {
|
|
457
|
-
value = pickAWinner(value)();
|
|
458
|
-
}
|
|
459
|
-
|
|
460
491
|
// [0,1,2] should pick one
|
|
461
492
|
if (Array.isArray(value) && typeof value[0] === "number") {
|
|
462
493
|
return chance.pickone(value);
|
|
@@ -988,63 +1019,122 @@ function weighChoices(items) {
|
|
|
988
1019
|
};
|
|
989
1020
|
}
|
|
990
1021
|
|
|
1022
|
+
/**
|
|
1023
|
+
* Resolve the stable per-run winner index for an array of items. Memoized in
|
|
1024
|
+
* `winnerCache` keyed by array contents (raw joined string — no hashing, so
|
|
1025
|
+
* no collision risk), so every event drawing from the same array favors the
|
|
1026
|
+
* SAME winner for the whole run. Winner is rolled from the seeded chance →
|
|
1027
|
+
* same seed = same winner; resetValueCaches() clears the memo between runs.
|
|
1028
|
+
*
|
|
1029
|
+
* @param {Array} items - The list of items to pick a winner from.
|
|
1030
|
+
* @returns {number} - The stable winner index for this run.
|
|
1031
|
+
*/
|
|
1032
|
+
function getStableWinnerIndex(items) {
|
|
1033
|
+
const key = items.join('\u0000');
|
|
1034
|
+
if (winnerCache.has(key)) return winnerCache.get(key);
|
|
1035
|
+
const chance = getChance();
|
|
1036
|
+
const winner = chance.integer({ min: 0, max: items.length - 1 });
|
|
1037
|
+
winnerCache.set(key, winner);
|
|
1038
|
+
return winner;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Full winner entry ({idx, weights}) for an array, memoized by array identity
|
|
1043
|
+
* in a WeakMap. Steady state for the choose() hot path is one WeakMap hit per
|
|
1044
|
+
* event — no join, no weight rebuild. Distinct array instances with equal
|
|
1045
|
+
* contents converge on the same winner via getStableWinnerIndex.
|
|
1046
|
+
*
|
|
1047
|
+
* @param {string[]} items
|
|
1048
|
+
* @returns {{idx: number, weights: number[]}}
|
|
1049
|
+
*/
|
|
1050
|
+
function getWinnerEntry(items) {
|
|
1051
|
+
let entry = winnerEntryCache.get(items);
|
|
1052
|
+
if (entry) return entry;
|
|
1053
|
+
const idx = getStableWinnerIndex(items);
|
|
1054
|
+
entry = { idx, weights: winnerWeights(items.length, idx) };
|
|
1055
|
+
winnerEntryCache.set(items, entry);
|
|
1056
|
+
return entry;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Build a power-law weight vector aligned to item indices: winner ~45%,
|
|
1061
|
+
* second ~25%, third ~15%, remainder split over the tail with geometric decay.
|
|
1062
|
+
* Rank order follows the rotational convention: second = (winner+1) % n,
|
|
1063
|
+
* third = (winner+2) % n. This is THE place to tune the curve.
|
|
1064
|
+
*
|
|
1065
|
+
* @param {number} n - Number of items.
|
|
1066
|
+
* @param {number} winnerIndex - Index of the winning item.
|
|
1067
|
+
* @returns {number[]} - Weights (unnormalized; chance.weighted normalizes).
|
|
1068
|
+
*/
|
|
1069
|
+
function winnerWeights(n, winnerIndex) {
|
|
1070
|
+
if (n <= 0) return [];
|
|
1071
|
+
if (n === 1) return [1];
|
|
1072
|
+
const HEAD = [45, 25, 15];
|
|
1073
|
+
const TAIL_BUDGET = 15;
|
|
1074
|
+
const TAIL_DECAY = 0.7;
|
|
1075
|
+
const tailCount = Math.max(0, n - HEAD.length);
|
|
1076
|
+
let tailWeights = [];
|
|
1077
|
+
if (tailCount > 0) {
|
|
1078
|
+
const raw = [];
|
|
1079
|
+
let w = 1;
|
|
1080
|
+
for (let k = 0; k < tailCount; k++) { raw.push(w); w *= TAIL_DECAY; }
|
|
1081
|
+
const sum = raw.reduce((a, b) => a + b, 0);
|
|
1082
|
+
tailWeights = raw.map(r => (r / sum) * TAIL_BUDGET);
|
|
1083
|
+
}
|
|
1084
|
+
const weights = new Array(n).fill(0);
|
|
1085
|
+
for (let rank = 0; rank < n; rank++) {
|
|
1086
|
+
const idx = (winnerIndex + rank) % n;
|
|
1087
|
+
weights[idx] = rank < HEAD.length ? HEAD[rank] : tailWeights[rank - HEAD.length];
|
|
1088
|
+
}
|
|
1089
|
+
return weights;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
991
1092
|
/**
|
|
992
1093
|
* Creates a function that generates a weighted list of items
|
|
993
1094
|
* with a higher likelihood of picking a specified index and clear second and third place indices.
|
|
994
|
-
*
|
|
1095
|
+
*
|
|
1096
|
+
* v1.6.1: the returned closure yields a DETERMINISTIC weighted expansion
|
|
1097
|
+
* (winner ~45%, second ~25%, third ~15%, geometric tail) built once — no
|
|
1098
|
+
* per-call sampling variance. When no index is passed, the winner is the
|
|
1099
|
+
* stable per-run memoized winner for the array (see getStableWinnerIndex),
|
|
1100
|
+
* so direct dungeon-file usage is stable and seed-deterministic too.
|
|
1101
|
+
*
|
|
995
1102
|
* @param {Array} items - The list of items to pick from.
|
|
996
1103
|
* @param {number} [mostChosenIndex] - The index of the item to be most favored.
|
|
997
1104
|
* @returns {function} - A function that returns a weighted list of items.
|
|
998
1105
|
*/
|
|
999
1106
|
function pickAWinner(items, mostChosenIndex) {
|
|
1000
|
-
const chance = getChance();
|
|
1001
|
-
|
|
1002
|
-
// Ensure mostChosenIndex is within the bounds of the items array
|
|
1003
1107
|
if (!items) return () => { return ""; };
|
|
1004
1108
|
if (!items.length) return () => { return ""; };
|
|
1005
|
-
if (!mostChosenIndex)
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1109
|
+
if (!Number.isFinite(mostChosenIndex)) {
|
|
1110
|
+
// undefined/null/NaN → stable per-run memoized winner
|
|
1111
|
+
mostChosenIndex = getStableWinnerIndex(items);
|
|
1112
|
+
} else {
|
|
1113
|
+
mostChosenIndex = Math.floor(mostChosenIndex);
|
|
1114
|
+
if (mostChosenIndex >= items.length) mostChosenIndex = items.length - 1;
|
|
1115
|
+
if (mostChosenIndex < 0) mostChosenIndex = 0;
|
|
1116
|
+
}
|
|
1011
1117
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
} else {
|
|
1024
|
-
const addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;
|
|
1025
|
-
let newIndex = mostChosenIndex + addOrSubtract;
|
|
1118
|
+
const weights = winnerWeights(items.length, mostChosenIndex);
|
|
1119
|
+
const total = weights.reduce((a, b) => a + b, 0);
|
|
1120
|
+
// scale slots with n so large arrays keep the ~45% winner share instead of
|
|
1121
|
+
// having Math.max(1, ...) floors dilute it
|
|
1122
|
+
const SLOTS = Math.max(20, items.length * 4);
|
|
1123
|
+
const expansion = [];
|
|
1124
|
+
items.forEach((item, i) => {
|
|
1125
|
+
// every item keeps at least one slot so no value becomes unreachable
|
|
1126
|
+
const count = Math.max(1, Math.round((weights[i] / total) * SLOTS));
|
|
1127
|
+
for (let j = 0; j < count; j++) expansion.push(item);
|
|
1128
|
+
});
|
|
1026
1129
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
if (newIndex >= items.length) newIndex = items.length - 1;
|
|
1030
|
-
weighted.push(items[newIndex]);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
// 25% chance to favor the second most chosen index
|
|
1034
|
-
else if (chance.bool({ likelihood: 25 })) {
|
|
1035
|
-
weighted.push(items[secondMostChosenIndex]);
|
|
1036
|
-
}
|
|
1037
|
-
// 15% chance to favor the third most chosen index
|
|
1038
|
-
else if (chance.bool({ likelihood: 15 })) {
|
|
1039
|
-
weighted.push(items[thirdMostChosenIndex]);
|
|
1040
|
-
}
|
|
1041
|
-
// Otherwise, pick a random item from the list
|
|
1042
|
-
else {
|
|
1043
|
-
weighted.push(chance.pickone(items));
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
return weighted;
|
|
1130
|
+
const resolver = function () {
|
|
1131
|
+
return expansion;
|
|
1047
1132
|
};
|
|
1133
|
+
// all pickAWinner closures share one source string; without this tag,
|
|
1134
|
+
// choose()'s weightedArrayCache would cache the first closure's expansion
|
|
1135
|
+
// under that shared key and serve it for every other pickAWinner property
|
|
1136
|
+
resolver.noCache = true;
|
|
1137
|
+
return resolver;
|
|
1048
1138
|
}
|
|
1049
1139
|
|
|
1050
1140
|
function quickHash(str, seed = 0) {
|
|
@@ -1774,6 +1864,7 @@ export {
|
|
|
1774
1864
|
getUniqueKeys,
|
|
1775
1865
|
person,
|
|
1776
1866
|
pickAWinner,
|
|
1867
|
+
resetValueCaches,
|
|
1777
1868
|
quickHash,
|
|
1778
1869
|
weighArray,
|
|
1779
1870
|
validateEventConfig,
|
package/lib/verify/index.js
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
export { emulateBreakdown } from './emulate-breakdown.js';
|
|
13
13
|
export { verifyDungeon } from './verify-dungeon.js';
|
|
14
|
+
// v1.6.2: exposed on the verify surface so a standalone verify script — one that
|
|
15
|
+
// reads shards off disk instead of running the dungeon — can resolve funnel
|
|
16
|
+
// defaults (`conversionWindowDays`, `order`) before handing funnels to
|
|
17
|
+
// `evaluateStories` / `applyFunnelDefaults`. Use the RETURN value; it does not
|
|
18
|
+
// enrich the config you pass in.
|
|
19
|
+
export { validateDungeonConfig } from '../core/config-validator.js';
|
|
14
20
|
export { deriveExpectedSchema, validateSchema } from './schema-validator.js';
|
|
15
21
|
export {
|
|
16
22
|
evaluateFunnel,
|
|
@@ -59,9 +59,10 @@ function findMatchingFunnel(funnels, steps) {
|
|
|
59
59
|
* args object — the input is not mutated. Extracted from `verifyDungeon` in
|
|
60
60
|
* v1.6 so the story runner (P3.3) reuses the exact same threading.
|
|
61
61
|
* @param {Object} breakdownArgs - Args destined for `emulateBreakdown`.
|
|
62
|
-
* @param {Array<Object>} funnels - VALIDATED dungeon funnels
|
|
63
|
-
*
|
|
64
|
-
* `conversionWindowDays` / `order
|
|
62
|
+
* @param {Array<Object>} funnels - VALIDATED dungeon funnels — i.e. the funnels off
|
|
63
|
+
* `validateDungeonConfig`'s RETURN value (post-1.6.2 it no longer enriches the
|
|
64
|
+
* caller's object), which carry resolved `conversionWindowDays` / `order`. A run
|
|
65
|
+
* result exposes them as `result.validatedConfig.funnels`.
|
|
65
66
|
* @param {Array<Object>} [profiles] - User profiles, threaded into `timeToConvert`.
|
|
66
67
|
* @returns {Object}
|
|
67
68
|
*/
|
|
@@ -113,20 +114,44 @@ export function applyFunnelDefaults(breakdownArgs, funnels, profiles) {
|
|
|
113
114
|
/**
|
|
114
115
|
* @param {Object} config - Dungeon config (or path; passed straight to DUNGEON_MASTER).
|
|
115
116
|
* @param {VerifyCheck[]} checks
|
|
116
|
-
* @
|
|
117
|
+
* @param {Object} [overrides] - v1.6.2: merged into the dungeon before it runs, same as
|
|
118
|
+
* `DUNGEON_MASTER`'s second argument. Lets CI verify a production-scale dungeon at a
|
|
119
|
+
* small `numUsers` / `numEvents` without editing it.
|
|
120
|
+
* @returns {Promise<{ pass: boolean, results: Array<{ name: string, pass: boolean, detail?: string, rows?: Array<Object> }>, schemaReport: Object, validatedConfig: Object }>}
|
|
117
121
|
*/
|
|
118
|
-
export async function verifyDungeon(config, checks) {
|
|
122
|
+
export async function verifyDungeon(config, checks, overrides) {
|
|
119
123
|
if (!checks || !checks.length) throw new Error('verifyDungeon: at least one check required');
|
|
120
|
-
let result = await DUNGEON_MASTER(config);
|
|
121
|
-
if (Array.isArray(result))
|
|
124
|
+
let result = await DUNGEON_MASTER(config, overrides);
|
|
125
|
+
if (Array.isArray(result)) {
|
|
126
|
+
// v1.6.2: an array input runs N dungeons but checks are written against ONE
|
|
127
|
+
// schema, and we used to silently verify `result[0]` and throw the rest away —
|
|
128
|
+
// a green report for dungeons that were never looked at. Call verifyDungeon
|
|
129
|
+
// per dungeon instead.
|
|
130
|
+
if (result.length !== 1) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`verifyDungeon: got ${result.length} dungeons, but checks apply to one. ` +
|
|
133
|
+
`Call verifyDungeon once per dungeon.`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
result = result[0];
|
|
137
|
+
}
|
|
122
138
|
const events = Array.isArray(result.eventData) ? result.eventData : Array.from(result.eventData);
|
|
123
139
|
const profiles = Array.isArray(result.userProfilesData) ? result.userProfilesData : Array.from(result.userProfilesData);
|
|
124
|
-
|
|
140
|
+
// v1.6.2: schema-check against the config the run actually used. `config` may be a
|
|
141
|
+
// PATH STRING (no fields at all → every property reads as unexpected), and even for
|
|
142
|
+
// an object input a v1.5.1 dungeon keeps `hasAndroidDevices` / `hasBrowser` under
|
|
143
|
+
// `switches`, which `deriveExpectedSchema` only reads once flattened.
|
|
144
|
+
const schemaReport = validateSchema(events, result.validatedConfig || config);
|
|
125
145
|
const ctx = { events, profiles, schemaReport };
|
|
126
146
|
const results = [];
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
147
|
+
// v1.6.2: read the funnels off the run's `validatedConfig` — `conversionWindowDays`
|
|
148
|
+
// and the resolved `order` live there. Before 1.6.2 we read them back off the
|
|
149
|
+
// caller's own `config`, relying on `validateDungeonConfig` enriching in place;
|
|
150
|
+
// that also meant a path/JSON input (no `.funnels` on the string) silently got an
|
|
151
|
+
// empty funnel list and thus an unbounded conversion window. Both are fixed here.
|
|
152
|
+
const validatedFunnels = Array.isArray(result?.validatedConfig?.funnels)
|
|
153
|
+
? result.validatedConfig.funnels
|
|
154
|
+
: (config && Array.isArray(config.funnels)) ? config.funnels : [];
|
|
130
155
|
for (const check of checks) {
|
|
131
156
|
try {
|
|
132
157
|
const breakdownArgs = applyFunnelDefaults(check.breakdown, validatedFunnels, profiles);
|
|
@@ -138,5 +163,7 @@ export async function verifyDungeon(config, checks) {
|
|
|
138
163
|
}
|
|
139
164
|
}
|
|
140
165
|
const pass = results.every(r => r.pass) && schemaReport.pass;
|
|
141
|
-
|
|
166
|
+
// v1.6.2: hand back the enriched config the run used, so callers can read
|
|
167
|
+
// resolved funnel/event values without re-validating.
|
|
168
|
+
return { pass, results, schemaReport, validatedConfig: result.validatedConfig };
|
|
142
169
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ak--47/dungeon-master",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "generate fancy datasets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"HOOKS.md"
|
|
34
34
|
],
|
|
35
35
|
"engines": {
|
|
36
|
-
"node": ">=
|
|
36
|
+
"node": ">=20.20.0"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"dotenv": "^16.4.5",
|
|
88
88
|
"hyparquet-writer": "^0.6.1",
|
|
89
89
|
"mixpanel": "^0.18.0",
|
|
90
|
-
"mixpanel-import": "^3.
|
|
90
|
+
"mixpanel-import": "^3.5.1",
|
|
91
91
|
"p-limit": "^3.1.0",
|
|
92
92
|
"pino": "^9.0.0",
|
|
93
93
|
"pino-pretty": "^11.0.0",
|
|
@@ -108,4 +108,4 @@
|
|
|
108
108
|
"tmp/"
|
|
109
109
|
]
|
|
110
110
|
}
|
|
111
|
-
}
|
|
111
|
+
}
|
|
@@ -176,7 +176,8 @@ if (inMemory) {
|
|
|
176
176
|
|
|
177
177
|
// Funnel auto-threading reads VALIDATED funnel fields (conversionWindowDays,
|
|
178
178
|
// order). The dungeon was not run in this process, so validate the config
|
|
179
|
-
// here —
|
|
179
|
+
// here and use the RETURN value — as of v1.6.2 validateDungeonConfig does not
|
|
180
|
+
// enrich the object you hand it.
|
|
180
181
|
const validated = validateDungeonConfig({ ...config, token: '' });
|
|
181
182
|
const identityMap = buildIdentityMap(profiles);
|
|
182
183
|
|
package/types.d.ts
CHANGED
|
@@ -1324,6 +1324,21 @@ export type Result = {
|
|
|
1324
1324
|
* so `userProfilesData.length - profilesPushed` = dropped profile count.
|
|
1325
1325
|
*/
|
|
1326
1326
|
profilesPushed?: number;
|
|
1327
|
+
/**
|
|
1328
|
+
* v1.6.2: the enriched config this run actually used. `validateDungeonConfig`
|
|
1329
|
+
* no longer writes back to the object you passed in, so resolved values —
|
|
1330
|
+
* `funnels[].conversionWindowDays`, `events[].isStrictEvent`, the resolved
|
|
1331
|
+
* dataset window — must be read here rather than off your own config.
|
|
1332
|
+
*
|
|
1333
|
+
* READ-ONLY. Do NOT feed this back into `DUNGEON_MASTER` — validation is not
|
|
1334
|
+
* idempotent (each pass appends to the funnel set, eventually producing an
|
|
1335
|
+
* empty `sequence`), and the object carries engine scratch fields. Re-run the
|
|
1336
|
+
* ORIGINAL config instead.
|
|
1337
|
+
*
|
|
1338
|
+
* Credentials (`token`, `serviceAccount`, `serviceSecret`, `projectId`,
|
|
1339
|
+
* `credentials`) are stripped — a Result is a thing hosts log.
|
|
1340
|
+
*/
|
|
1341
|
+
validatedConfig?: Dungeon;
|
|
1327
1342
|
/** Progress callback summary. Only present when `onProgress` was provided. */
|
|
1328
1343
|
progress?: ProgressSummary;
|
|
1329
1344
|
};
|
|
@@ -2358,6 +2373,8 @@ declare module '@ak--47/dungeon-master/utils' {
|
|
|
2358
2373
|
export function objectList(template: Record<string, ValueValid>, options?: { min?: number; max?: number }): () => Array<Record<string, unknown>>;
|
|
2359
2374
|
export function weighNumRange(min: number, max: number, skew?: number, size?: number): number[];
|
|
2360
2375
|
export function pickAWinner(items: string[], mostChosenIndex?: number): () => string[];
|
|
2376
|
+
/** Clears the per-run stable-winner + weighted-array caches (also called by initChance and at every run start). */
|
|
2377
|
+
export function resetValueCaches(): void;
|
|
2361
2378
|
export function initChance(seed?: string): unknown;
|
|
2362
2379
|
export function initUserChance(seed?: string): unknown;
|
|
2363
2380
|
export function getUserChance(): unknown;
|