@ak--47/dungeon-master 1.5.4 → 1.6.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 (146) hide show
  1. package/.claude/skills/analyze-soup/SKILL.md +21 -12
  2. package/.claude/skills/create-dungeon/SKILL.md +65 -5
  3. package/.claude/skills/create-project/SKILL.md +1 -1
  4. package/.claude/skills/create-project/provision.mjs +57 -10
  5. package/.claude/skills/verify-dungeon/SKILL.md +56 -25
  6. package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
  7. package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
  8. package/.claude/skills/write-hooks/SKILL.md +156 -18
  9. package/CHANGELOG.md +183 -0
  10. package/HOOKS.md +566 -112
  11. package/README.md +28 -9
  12. package/docs/guides/1.3.0-upgrade-guide.md +262 -0
  13. package/docs/guides/1.3.1-upgrade-guide.md +328 -0
  14. package/docs/guides/1.4.0-upgrade-guide.md +130 -0
  15. package/docs/guides/1.4.1-upgrade-guide.md +125 -0
  16. package/docs/guides/1.4.3-upgrade-guide.md +200 -0
  17. package/docs/guides/1.4.4-upgrade-guide.md +95 -0
  18. package/docs/guides/1.5.0-upgrade-guide.md +426 -0
  19. package/docs/guides/1.5.1-upgrade-guide.md +178 -0
  20. package/docs/guides/1.5.3-upgrade-guide.md +117 -0
  21. package/docs/guides/1.6.0-upgrade-guide.md +204 -0
  22. package/dungeons/technical/stories-verify.js +152 -0
  23. package/dungeons/vertical/README.md +154 -0
  24. package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
  25. package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
  26. package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
  27. package/dungeons/vertical/community/community.js +1589 -0
  28. package/dungeons/vertical/community/community.sql +216 -0
  29. package/dungeons/vertical/community/community.verify.mjs +72 -0
  30. package/dungeons/vertical/crypto/crypto.js +1483 -0
  31. package/dungeons/vertical/crypto/crypto.sql +194 -0
  32. package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
  33. package/dungeons/vertical/dating/dating.js +1540 -0
  34. package/dungeons/vertical/dating/dating.sql +288 -0
  35. package/dungeons/vertical/dating/dating.verify.mjs +72 -0
  36. package/dungeons/vertical/devtools/devtools.js +1524 -0
  37. package/dungeons/vertical/devtools/devtools.sql +161 -0
  38. package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
  39. package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
  40. package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
  41. package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
  42. package/dungeons/vertical/education/education.js +1763 -0
  43. package/dungeons/vertical/education/education.sql +243 -0
  44. package/dungeons/vertical/education/education.verify.mjs +75 -0
  45. package/dungeons/vertical/fintech/fintech.js +1410 -0
  46. package/dungeons/vertical/fintech/fintech.sql +175 -0
  47. package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
  48. package/dungeons/vertical/fitness/fitness.js +1495 -0
  49. package/dungeons/vertical/fitness/fitness.sql +206 -0
  50. package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
  51. package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
  52. package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
  53. package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
  54. package/dungeons/vertical/gaming/gaming.js +1741 -0
  55. package/dungeons/vertical/gaming/gaming.sql +249 -0
  56. package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
  57. package/dungeons/vertical/healthcare/healthcare.js +1488 -0
  58. package/dungeons/vertical/healthcare/healthcare.sql +193 -0
  59. package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
  60. package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
  61. package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
  62. package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
  63. package/dungeons/vertical/logistics/logistics.js +1541 -0
  64. package/dungeons/vertical/logistics/logistics.sql +205 -0
  65. package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
  66. package/dungeons/vertical/marketplace/marketplace.js +1438 -0
  67. package/dungeons/vertical/marketplace/marketplace.sql +148 -0
  68. package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
  69. package/dungeons/vertical/{media.js → media/media.js} +557 -58
  70. package/dungeons/vertical/media/media.sql +173 -0
  71. package/dungeons/vertical/media/media.verify.mjs +74 -0
  72. package/dungeons/vertical/real-estate/real-estate.js +1629 -0
  73. package/dungeons/vertical/real-estate/real-estate.sql +167 -0
  74. package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
  75. package/dungeons/vertical/sass/sass.js +1733 -0
  76. package/dungeons/vertical/sass/sass.sql +173 -0
  77. package/dungeons/vertical/sass/sass.verify.mjs +74 -0
  78. package/dungeons/vertical/social/social.js +1456 -0
  79. package/dungeons/vertical/social/social.sql +160 -0
  80. package/dungeons/vertical/social/social.verify.mjs +76 -0
  81. package/dungeons/vertical/streaming/streaming.js +677 -0
  82. package/dungeons/vertical/streaming/streaming.sql +101 -0
  83. package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
  84. package/dungeons/vertical/support-desk/support-desk.js +741 -0
  85. package/dungeons/vertical/support-desk/support-desk.sql +137 -0
  86. package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
  87. package/dungeons/vertical/travel/travel.js +1603 -0
  88. package/dungeons/vertical/travel/travel.sql +236 -0
  89. package/dungeons/vertical/travel/travel.verify.mjs +72 -0
  90. package/index.js +2 -2
  91. package/lib/core/config-validator.js +37 -2
  92. package/lib/core/context.js +4 -2
  93. package/lib/core/dungeon-loader.js +20 -15
  94. package/lib/core/dungeon-to-json.js +22 -4
  95. package/lib/core/storage.js +3 -3
  96. package/lib/generators/funnels.js +36 -9
  97. package/lib/hook-helpers/cohort.js +53 -0
  98. package/lib/hook-helpers/index.js +8 -0
  99. package/lib/hook-helpers/mutate.js +1 -1
  100. package/lib/hook-helpers/shape.js +319 -0
  101. package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
  102. package/lib/hook-patterns/attributed-by-source.js +60 -58
  103. package/lib/hook-patterns/frequency-by-frequency.js +14 -4
  104. package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
  105. package/lib/hook-patterns/index.js +1 -1
  106. package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
  107. package/lib/orchestrators/user-loop.js +41 -0
  108. package/lib/templates/story-spec.schema.json +175 -0
  109. package/lib/utils/json-evaluator.js +1 -1
  110. package/lib/utils/utils.js +5 -4
  111. package/lib/verify/coerce.js +186 -0
  112. package/lib/verify/counting.js +270 -32
  113. package/lib/verify/emulate-breakdown.js +1285 -203
  114. package/lib/verify/first-time.js +89 -0
  115. package/lib/verify/flows.js +679 -0
  116. package/lib/verify/formula.js +259 -0
  117. package/lib/verify/funnel-engine.js +697 -113
  118. package/lib/verify/index.js +32 -4
  119. package/lib/verify/sessionize.js +275 -0
  120. package/lib/verify/story-runner.js +483 -0
  121. package/lib/verify/verify-dungeon.js +59 -37
  122. package/package.json +2 -1
  123. package/scripts/extract-dungeon-schema.mjs +2 -2
  124. package/scripts/run-many.mjs +2 -2
  125. package/scripts/verify-runner.mjs +2 -2
  126. package/scripts/verify-stories.mjs +244 -0
  127. package/types.d.ts +296 -30
  128. package/dungeons/vertical/ai-platform.js +0 -818
  129. package/dungeons/vertical/community.js +0 -966
  130. package/dungeons/vertical/crypto.js +0 -895
  131. package/dungeons/vertical/dating.js +0 -830
  132. package/dungeons/vertical/devtools.js +0 -1050
  133. package/dungeons/vertical/ecommerce.js +0 -811
  134. package/dungeons/vertical/education.js +0 -964
  135. package/dungeons/vertical/fintech.js +0 -946
  136. package/dungeons/vertical/fitness.js +0 -935
  137. package/dungeons/vertical/food-delivery.js +0 -934
  138. package/dungeons/vertical/gaming.js +0 -1120
  139. package/dungeons/vertical/healthcare.js +0 -945
  140. package/dungeons/vertical/insurance-application.js +0 -1039
  141. package/dungeons/vertical/logistics.js +0 -986
  142. package/dungeons/vertical/marketplace.js +0 -1019
  143. package/dungeons/vertical/real-estate.js +0 -872
  144. package/dungeons/vertical/sass.js +0 -995
  145. package/dungeons/vertical/social.js +0 -904
  146. package/dungeons/vertical/travel.js +0 -930
@@ -0,0 +1,194 @@
1
+ -- ============================================================
2
+ -- crypto.js — v1.6.0 Hook Inspection Queries (DuckDB)
3
+ -- Score: NAILED (11/11 stories; machine contract in crypto.js `stories`)
4
+ -- Run after: node scripts/verify-runner.mjs dungeons/vertical/crypto/crypto.js verify-crypto
5
+ -- ============================================================
6
+ --
7
+ -- v1.6.0 derivation notes (2K reduced run vs organic counterfactual):
8
+ -- 1. Whale/bot cohorts are ~13-14% of users, not the 2%/4% the v1.5
9
+ -- docstring claimed — user_ids are hex uuids, so charCodeAt % 50/25
10
+ -- matches hex digits '2' and 'd' (2 of 16), not 1-in-50/25.
11
+ -- 2. All boundary days are UTC calendar days from 2026-01-01 (hooks run
12
+ -- dayjs in UTC mode; the dataset spans the 2026-03-08 US DST shift).
13
+ -- 3. H6 has a first-24h grace window: churn never erases the signup, so
14
+ -- late-born users keep their onboarding funnel + auth event.
15
+ -- 4. H8's SCAM-holder cohort is the MAJORITY of eventful users (~59%):
16
+ -- 2%/swap over hundreds of swaps catches almost everyone active.
17
+ -- 5. H9 is emulator-only (funnel median TTC): funnel-post gap scaling is
18
+ -- invisible to cross-event MIN→MIN SQL. Scoped to the onboarding
19
+ -- funnel; see crypto.verify.mjs.
20
+ -- ============================================================
21
+
22
+
23
+ -- Hook 1: WHALE WALLETS — ~13% of wallets trade at 50x
24
+ -- Expect: whale/non avg trade amount ~48x; top-5% volume share ~0.75.
25
+ WITH pu AS (
26
+ SELECT user_id::VARCHAR AS uid,
27
+ (ascii(substr(user_id::VARCHAR, 1, 1)) % 50 = 0) AS whale,
28
+ AVG(trade_amount_usd) AS avg_amt, SUM(trade_amount_usd) AS vol
29
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
30
+ WHERE event = 'swap' AND user_id IS NOT NULL
31
+ GROUP BY 1, 2
32
+ )
33
+ SELECT CASE WHEN whale THEN 'whale' ELSE 'non' END AS cohort,
34
+ COUNT(*) AS users, ROUND(AVG(avg_amt), 0) AS avg_trade, ROUND(SUM(vol), 0) AS total_vol
35
+ FROM pu GROUP BY 1 ORDER BY 1;
36
+
37
+
38
+ -- Hook 2: GAS SPIKE — days 35-37 (2026-02-05..07 UTC), ~10x gas + fail share ~0.52
39
+ SELECT
40
+ DATE_TRUNC('day', time::TIMESTAMP) AS day,
41
+ COUNT(*) AS swaps,
42
+ ROUND(AVG(gas_fee_usd), 2) AS avg_gas,
43
+ ROUND(100.0 * COUNT(*) FILTER (WHERE swap_status = 'failed') / COUNT(*), 1) AS pct_failed
44
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
45
+ WHERE event = 'swap'
46
+ AND time::TIMESTAMP BETWEEN TIMESTAMP '2026-02-01' AND TIMESTAMP '2026-02-12'
47
+ GROUP BY 1 ORDER BY 1;
48
+
49
+
50
+ -- Hook 3: TOKEN LAUNCH SURGE — MOON pairs: exactly 0 before day 50
51
+ -- (2026-02-20 UTC), ~58% of swaps after.
52
+ SELECT CASE WHEN time::TIMESTAMP > TIMESTAMP '2026-02-20 00:00:00' THEN 'post_d50' ELSE 'pre_d50' END AS win,
53
+ COUNT(*) AS swaps,
54
+ ROUND(AVG((token_pair LIKE '%MOON%')::INT), 4) AS moon_share
55
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
56
+ WHERE event = 'swap' GROUP BY 1 ORDER BY 1;
57
+
58
+
59
+ -- Hook 4: AIRDROP HUNTER CHURN — bot claimers (~14%, hash) post/pre ~1.5
60
+ -- vs non-bot claimers ~25 (contrast ~0.06).
61
+ WITH claims AS (
62
+ SELECT user_id::VARCHAR AS uid, MIN(time::TIMESTAMP) AS t0
63
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
64
+ WHERE event = 'claim airdrop' AND user_id IS NOT NULL GROUP BY 1
65
+ ), pu AS (
66
+ SELECT e.user_id::VARCHAR AS uid,
67
+ (ascii(substr(e.user_id::VARCHAR, 2, 1)) % 25 = 0) AS bot,
68
+ COUNT(*) FILTER (WHERE e.time::TIMESTAMP <= c.t0) AS pre_ev,
69
+ COUNT(*) FILTER (WHERE e.time::TIMESTAMP > c.t0) AS post_ev
70
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true) e
71
+ JOIN claims c ON e.user_id::VARCHAR = c.uid GROUP BY 1, 2
72
+ )
73
+ SELECT CASE WHEN bot THEN 'bot' ELSE 'non' END AS cohort, COUNT(*) AS users,
74
+ ROUND(AVG(post_ev::DOUBLE / GREATEST(pre_ev, 1)), 3) AS post_pre_ratio
75
+ FROM pu GROUP BY 1 ORDER BY 1;
76
+
77
+
78
+ -- Hook 5: KYC COMPLETION — post-KYC deposits ~3.4x, swaps/user ~7.6x
79
+ -- (organic activity confound alone is 2.0x).
80
+ WITH kyc AS (
81
+ SELECT user_id::VARCHAR AS uid, MIN(time::TIMESTAMP) AS kt
82
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
83
+ WHERE event = 'kyc completed' AND user_id IS NOT NULL GROUP BY 1
84
+ ), pu AS (
85
+ SELECT e.user_id::VARCHAR AS uid, (k.uid IS NOT NULL) AS has_kyc,
86
+ AVG(e.deposit_amount_usd) FILTER (WHERE e.event = 'deposit' AND (k.uid IS NULL OR e.time::TIMESTAMP > k.kt)) AS dep,
87
+ COUNT(*) FILTER (WHERE e.event = 'swap') AS swaps
88
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true) e
89
+ LEFT JOIN kyc k ON e.user_id::VARCHAR = k.uid
90
+ WHERE e.user_id IS NOT NULL GROUP BY 1, 2
91
+ )
92
+ SELECT CASE WHEN has_kyc THEN 'kyc' ELSE 'non' END AS cohort, COUNT(*) AS users,
93
+ ROUND(AVG(dep), 0) AS avg_deposit, ROUND(AVG(swaps), 1) AS swaps_per_user
94
+ FROM pu GROUP BY 1 ORDER BY 1;
95
+
96
+
97
+ -- Hook 6: STAKE-TO-RETAIN — first-14d stakers keep ~5.3x post-d60
98
+ -- (2026-03-02 UTC) volume. Binary retention barely moves; volume is the read.
99
+ WITH pu AS (
100
+ SELECT user_id::VARCHAR AS uid,
101
+ BOOL_OR(event = 'stake' AND time::TIMESTAMP < TIMESTAMP '2026-01-15 00:00:00') AS early_staker,
102
+ COUNT(*) FILTER (WHERE time::TIMESTAMP > TIMESTAMP '2026-03-02 00:00:00') AS post60
103
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
104
+ WHERE user_id IS NOT NULL GROUP BY 1
105
+ )
106
+ SELECT CASE WHEN early_staker THEN 'staker' ELSE 'non' END AS cohort,
107
+ COUNT(*) AS users, ROUND(AVG(post60), 1) AS post60_per_user,
108
+ ROUND(AVG((post60 > 0)::INT), 3) AS active_post60_share
109
+ FROM pu GROUP BY 1 ORDER BY 1;
110
+
111
+
112
+ -- Hook 7: PRO TIER FEES — Pro 0.05 vs Standard 0.30 exact; swaps/user ~5.2x.
113
+ SELECT trading_tier, COUNT(DISTINCT user_id::VARCHAR) AS users,
114
+ ROUND(AVG(maker_fee_pct), 4) AS avg_fee,
115
+ ROUND(COUNT(*)::DOUBLE / COUNT(DISTINCT user_id::VARCHAR), 1) AS swaps_per_user
116
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
117
+ WHERE event = 'swap' AND user_id IS NOT NULL GROUP BY 1 ORDER BY 1;
118
+
119
+
120
+ -- Hook 8: RUG-PULL AFTERMATH — SCAM holders (~59% of eventful users)
121
+ -- post/pre-d70 (2026-03-12 UTC) ~0.21 vs ~1.25 non-holders.
122
+ WITH pu AS (
123
+ SELECT user_id::VARCHAR AS uid,
124
+ BOOL_OR(event = 'swap' AND token_pair LIKE '%SCAM%') AS scam,
125
+ COUNT(*) FILTER (WHERE time::TIMESTAMP <= TIMESTAMP '2026-03-12 00:00:00') AS pre_ev,
126
+ COUNT(*) FILTER (WHERE time::TIMESTAMP > TIMESTAMP '2026-03-12 00:00:00') AS post_ev
127
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
128
+ WHERE user_id IS NOT NULL GROUP BY 1
129
+ )
130
+ SELECT CASE WHEN scam THEN 'scam' ELSE 'non' END AS cohort, COUNT(*) AS users,
131
+ ROUND(AVG(post_ev::DOUBLE / pre_ev), 3) AS post_pre_ratio
132
+ FROM pu WHERE pre_ev >= 5 GROUP BY 1 ORDER BY 1;
133
+
134
+
135
+ -- Hook 9: ONBOARDING TTC BY TIER — emulator-only read (see crypto.verify.mjs).
136
+ -- This query only shows the funnel-step populations by tier; the median
137
+ -- TTC delta (Pro/Std ~0.74 @6h window) is NOT visible in cross-event SQL —
138
+ -- funnel-post scales gaps within the funnel instance, and greedy MIN→MIN
139
+ -- queries pick unrelated instances.
140
+ SELECT trading_tier, event, COUNT(*) AS n, COUNT(DISTINCT user_id::VARCHAR) AS users
141
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
142
+ WHERE event IN ('wallet connected', 'kyc started', 'deposit') AND user_id IS NOT NULL
143
+ GROUP BY 1, 2 ORDER BY 1, 2;
144
+
145
+
146
+ -- Hook 10: SWAP-COUNT MAGIC NUMBER — sweet (8-20) stake amounts ~1.32x low;
147
+ -- over (21+) portfolio total_value_usd ~0.50x sweet. Views-per-user is NOT
148
+ -- the read (activity confound nets the 75% drop to parity).
149
+ WITH pu AS (
150
+ SELECT user_id::VARCHAR AS uid,
151
+ COUNT(*) FILTER (WHERE event = 'swap') AS swaps,
152
+ AVG(amount_usd) FILTER (WHERE event = 'stake') AS stake_amt,
153
+ AVG(total_value_usd) FILTER (WHERE event = 'portfolio viewed') AS pv_val
154
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
155
+ WHERE user_id IS NOT NULL GROUP BY 1
156
+ )
157
+ SELECT CASE WHEN swaps >= 21 THEN 'over' WHEN swaps >= 8 THEN 'sweet' ELSE 'low' END AS bucket,
158
+ COUNT(*) AS users, ROUND(AVG(stake_amt), 0) AS avg_stake_amt,
159
+ ROUND(AVG(pv_val), 0) AS avg_portfolio_value
160
+ FROM pu GROUP BY 1 ORDER BY 1;
161
+
162
+
163
+ -- Hook 11: EARLY STAKER RETENTION — born-in (has 'wallet connected') users
164
+ -- with 2+ stakes in first 10 lifetime days: post-d40 ratio ~1.31 vs the
165
+ -- INVERSE organic baseline 0.52.
166
+ WITH born AS (
167
+ SELECT DISTINCT user_id::VARCHAR AS uid
168
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
169
+ WHERE event = 'wallet connected' AND user_id IS NOT NULL
170
+ ), firsts AS (
171
+ SELECT user_id::VARCHAR AS uid, MIN(time::TIMESTAMP) AS t0
172
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true)
173
+ WHERE user_id IS NOT NULL GROUP BY 1
174
+ ), pu AS (
175
+ SELECT e.user_id::VARCHAR AS uid,
176
+ COUNT(*) FILTER (WHERE e.event = 'stake' AND e.time::TIMESTAMP <= f.t0 + INTERVAL 10 DAY) AS early_stakes,
177
+ COUNT(*) FILTER (WHERE e.time::TIMESTAMP > f.t0 + INTERVAL 40 DAY) AS post40
178
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true) e
179
+ JOIN firsts f ON e.user_id::VARCHAR = f.uid
180
+ JOIN born b ON e.user_id::VARCHAR = b.uid
181
+ GROUP BY 1
182
+ )
183
+ SELECT CASE WHEN early_stakes >= 2 THEN 'early' ELSE 'non' END AS cohort,
184
+ COUNT(*) AS users, ROUND(AVG(post40), 2) AS post40_per_user
185
+ FROM pu GROUP BY 1 ORDER BY 1;
186
+
187
+
188
+ -- Identity invariants: every event carries user_id; device coverage ~1.0;
189
+ -- devices/user ~2.06 (avgDevicePerUser: 2).
190
+ SELECT COUNT(*) AS n,
191
+ ROUND(AVG((user_id IS NOT NULL)::INT), 4) AS uid_share,
192
+ ROUND(AVG((device_id IS NOT NULL)::INT), 4) AS device_share,
193
+ ROUND(COUNT(DISTINCT device_id)::DOUBLE / COUNT(DISTINCT user_id), 3) AS devices_per_user
194
+ FROM read_json_auto('data/verify-crypto-EVENTS*.json', sample_size=-1, union_by_name=true);
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * crypto.verify.mjs — thin wrapper around the story runner.
4
+ *
5
+ * All verification logic lives in the `stories` export of ./crypto.js;
6
+ * this script just streams the shards and delegates. It is equivalent to:
7
+ *
8
+ * node scripts/verify-stories.mjs dungeons/vertical/crypto/crypto.js --data-prefix verify-crypto
9
+ *
10
+ * Generate first:
11
+ * node scripts/verify-runner.mjs dungeons/vertical/crypto/crypto.js verify-crypto
12
+ * Run:
13
+ * node dungeons/vertical/crypto/crypto.verify.mjs [prefix] # default verify-crypto
14
+ */
15
+ import fs from 'node:fs';
16
+ import path from 'node:path';
17
+ import readline from 'node:readline';
18
+ import { execFile } from 'node:child_process';
19
+ import { promisify } from 'node:util';
20
+ import { buildIdentityMap, evaluateStories, VERDICT_RANK } from '@ak--47/dungeon-master/verify';
21
+ import config, { stories } from './crypto.js';
22
+
23
+ const PREFIX = process.argv[2] || 'verify-crypto';
24
+ const prefixPath = path.join('data', PREFIX);
25
+
26
+ async function loadShards(suffix) {
27
+ // streaming load: full-fidelity EVENTS shards exceed the readFileSync cap
28
+ const dir = path.dirname(prefixPath), base = path.basename(prefixPath);
29
+ const out = [];
30
+ if (!fs.existsSync(dir)) return out;
31
+ for (const f of fs.readdirSync(dir).filter(f => f.startsWith(`${base}-${suffix}`) && f.endsWith('.json')).sort()) {
32
+ const rl = readline.createInterface({ input: fs.createReadStream(path.join(dir, f)), crlfDelay: Infinity });
33
+ for await (const line of rl) {
34
+ if (line.trim()) out.push(JSON.parse(line));
35
+ }
36
+ }
37
+ return out;
38
+ }
39
+
40
+ const events = await loadShards('EVENTS');
41
+ const profiles = await loadShards('USERS');
42
+ if (!events.length) {
43
+ console.error(`no shards at ${prefixPath}-EVENTS*.json — run: node scripts/verify-runner.mjs dungeons/vertical/crypto/crypto.js ${PREFIX}`);
44
+ process.exit(1);
45
+ }
46
+ console.log(`crypto — events=${events.length} users=${profiles.length} (${prefixPath})`);
47
+
48
+ const execFileP = promisify(execFile);
49
+ const runSql = async (sql) => {
50
+ const { stdout } = await execFileP('duckdb', ['-json', '-c', sql.replaceAll('{{PREFIX}}', prefixPath)], { maxBuffer: 64 * 1024 * 1024 });
51
+ return stdout.trim() ? JSON.parse(stdout) : [];
52
+ };
53
+
54
+ // Ten stories are DuckDB-backed (hash cohorts, temporal windows, per-user
55
+ // post/pre retention ratios, swap-count buckets, identity invariants). H9
56
+ // dispatches to emulateBreakdown's timeToConvert at a 6-HOUR window —
57
+ // funnel-post gap scaling is invisible to cross-event SQL, and the
58
+ // onboarding funnel's unique first step (`wallet connected`, isFirstEvent
59
+ // + isAuthEvent) anchors the greedy evaluator to the exact instance the
60
+ // hook touched, so the read is stable across 1h-24h windows. The trading
61
+ // and DeFi funnels are NOT scaled: they share the swap step, which H3/H5/
62
+ // H7 clone at arbitrary offsets — scaling them lets greedy evaluation
63
+ // assemble chains across unscaled clones and collapse the read.
64
+ const results = await evaluateStories(stories, events, {
65
+ profiles,
66
+ funnels: config.funnels,
67
+ identityMap: buildIdentityMap(profiles),
68
+ runSql,
69
+ });
70
+
71
+ let worst = 'NAILED';
72
+ for (const r of results) {
73
+ console.log(`${r.verdict.padEnd(7)} ${r.id}`);
74
+ for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
75
+ if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
76
+ }
77
+ console.log(`\nworst verdict: ${worst}`);
78
+ process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);