@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.
- package/.claude/skills/analyze-soup/SKILL.md +21 -12
- package/.claude/skills/create-dungeon/SKILL.md +65 -5
- package/.claude/skills/create-project/SKILL.md +1 -1
- package/.claude/skills/create-project/provision.mjs +57 -10
- package/.claude/skills/verify-dungeon/SKILL.md +56 -25
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
- package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
- package/.claude/skills/write-hooks/SKILL.md +156 -18
- package/CHANGELOG.md +183 -0
- package/HOOKS.md +566 -112
- package/README.md +28 -9
- package/docs/guides/1.3.0-upgrade-guide.md +262 -0
- package/docs/guides/1.3.1-upgrade-guide.md +328 -0
- package/docs/guides/1.4.0-upgrade-guide.md +130 -0
- package/docs/guides/1.4.1-upgrade-guide.md +125 -0
- package/docs/guides/1.4.3-upgrade-guide.md +200 -0
- package/docs/guides/1.4.4-upgrade-guide.md +95 -0
- package/docs/guides/1.5.0-upgrade-guide.md +426 -0
- package/docs/guides/1.5.1-upgrade-guide.md +178 -0
- package/docs/guides/1.5.3-upgrade-guide.md +117 -0
- package/docs/guides/1.6.0-upgrade-guide.md +204 -0
- package/dungeons/technical/stories-verify.js +152 -0
- package/dungeons/vertical/README.md +154 -0
- package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
- package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
- package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
- package/dungeons/vertical/community/community.js +1589 -0
- package/dungeons/vertical/community/community.sql +216 -0
- package/dungeons/vertical/community/community.verify.mjs +72 -0
- package/dungeons/vertical/crypto/crypto.js +1483 -0
- package/dungeons/vertical/crypto/crypto.sql +194 -0
- package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
- package/dungeons/vertical/dating/dating.js +1540 -0
- package/dungeons/vertical/dating/dating.sql +288 -0
- package/dungeons/vertical/dating/dating.verify.mjs +72 -0
- package/dungeons/vertical/devtools/devtools.js +1524 -0
- package/dungeons/vertical/devtools/devtools.sql +161 -0
- package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
- package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
- package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
- package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
- package/dungeons/vertical/education/education.js +1763 -0
- package/dungeons/vertical/education/education.sql +243 -0
- package/dungeons/vertical/education/education.verify.mjs +75 -0
- package/dungeons/vertical/fintech/fintech.js +1410 -0
- package/dungeons/vertical/fintech/fintech.sql +175 -0
- package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
- package/dungeons/vertical/fitness/fitness.js +1495 -0
- package/dungeons/vertical/fitness/fitness.sql +206 -0
- package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
- package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
- package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
- package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
- package/dungeons/vertical/gaming/gaming.js +1741 -0
- package/dungeons/vertical/gaming/gaming.sql +249 -0
- package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
- package/dungeons/vertical/healthcare/healthcare.js +1488 -0
- package/dungeons/vertical/healthcare/healthcare.sql +193 -0
- package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
- package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
- package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
- package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
- package/dungeons/vertical/logistics/logistics.js +1541 -0
- package/dungeons/vertical/logistics/logistics.sql +205 -0
- package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
- package/dungeons/vertical/marketplace/marketplace.js +1438 -0
- package/dungeons/vertical/marketplace/marketplace.sql +148 -0
- package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
- package/dungeons/vertical/{media.js → media/media.js} +557 -58
- package/dungeons/vertical/media/media.sql +173 -0
- package/dungeons/vertical/media/media.verify.mjs +74 -0
- package/dungeons/vertical/real-estate/real-estate.js +1629 -0
- package/dungeons/vertical/real-estate/real-estate.sql +167 -0
- package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
- package/dungeons/vertical/sass/sass.js +1733 -0
- package/dungeons/vertical/sass/sass.sql +173 -0
- package/dungeons/vertical/sass/sass.verify.mjs +74 -0
- package/dungeons/vertical/social/social.js +1456 -0
- package/dungeons/vertical/social/social.sql +160 -0
- package/dungeons/vertical/social/social.verify.mjs +76 -0
- package/dungeons/vertical/streaming/streaming.js +677 -0
- package/dungeons/vertical/streaming/streaming.sql +101 -0
- package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
- package/dungeons/vertical/support-desk/support-desk.js +741 -0
- package/dungeons/vertical/support-desk/support-desk.sql +137 -0
- package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
- package/dungeons/vertical/travel/travel.js +1603 -0
- package/dungeons/vertical/travel/travel.sql +236 -0
- package/dungeons/vertical/travel/travel.verify.mjs +72 -0
- package/index.js +2 -2
- package/lib/core/config-validator.js +37 -2
- package/lib/core/context.js +4 -2
- package/lib/core/dungeon-loader.js +20 -15
- package/lib/core/dungeon-to-json.js +22 -4
- package/lib/core/storage.js +3 -3
- package/lib/generators/funnels.js +36 -9
- package/lib/hook-helpers/cohort.js +53 -0
- package/lib/hook-helpers/index.js +8 -0
- package/lib/hook-helpers/mutate.js +1 -1
- package/lib/hook-helpers/shape.js +319 -0
- package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
- package/lib/hook-patterns/attributed-by-source.js +60 -58
- package/lib/hook-patterns/frequency-by-frequency.js +14 -4
- package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
- package/lib/hook-patterns/index.js +1 -1
- package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
- package/lib/orchestrators/user-loop.js +41 -0
- package/lib/templates/story-spec.schema.json +175 -0
- package/lib/utils/json-evaluator.js +1 -1
- package/lib/utils/utils.js +5 -4
- package/lib/verify/coerce.js +186 -0
- package/lib/verify/counting.js +270 -32
- package/lib/verify/emulate-breakdown.js +1285 -203
- package/lib/verify/first-time.js +89 -0
- package/lib/verify/flows.js +679 -0
- package/lib/verify/formula.js +259 -0
- package/lib/verify/funnel-engine.js +697 -113
- package/lib/verify/index.js +32 -4
- package/lib/verify/sessionize.js +275 -0
- package/lib/verify/story-runner.js +483 -0
- package/lib/verify/verify-dungeon.js +59 -37
- package/package.json +2 -1
- package/scripts/extract-dungeon-schema.mjs +2 -2
- package/scripts/run-many.mjs +2 -2
- package/scripts/verify-runner.mjs +2 -2
- package/scripts/verify-stories.mjs +244 -0
- package/types.d.ts +296 -30
- package/dungeons/vertical/ai-platform.js +0 -818
- package/dungeons/vertical/community.js +0 -966
- package/dungeons/vertical/crypto.js +0 -895
- package/dungeons/vertical/dating.js +0 -830
- package/dungeons/vertical/devtools.js +0 -1050
- package/dungeons/vertical/ecommerce.js +0 -811
- package/dungeons/vertical/education.js +0 -964
- package/dungeons/vertical/fintech.js +0 -946
- package/dungeons/vertical/fitness.js +0 -935
- package/dungeons/vertical/food-delivery.js +0 -934
- package/dungeons/vertical/gaming.js +0 -1120
- package/dungeons/vertical/healthcare.js +0 -945
- package/dungeons/vertical/insurance-application.js +0 -1039
- package/dungeons/vertical/logistics.js +0 -986
- package/dungeons/vertical/marketplace.js +0 -1019
- package/dungeons/vertical/real-estate.js +0 -872
- package/dungeons/vertical/sass.js +0 -995
- package/dungeons/vertical/social.js +0 -904
- 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);
|