@ak--47/dungeon-master 1.5.3 → 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 +96 -0
- package/.claude/skills/create-project/provision.mjs +336 -0
- 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 +211 -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 +3 -2
- 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,173 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- sass.js — hook inspection queries (v1.6.0 stories rebuild)
|
|
3
|
+
--
|
|
4
|
+
-- The verification CONTRACT lives in the `stories` export of
|
|
5
|
+
-- sass.js (run via sass.verify.mjs). These queries are for
|
|
6
|
+
-- manual inspection of the same reads.
|
|
7
|
+
--
|
|
8
|
+
-- Derivation notes (measured at 2K reduced scale + organic
|
|
9
|
+
-- counterfactual run — same seed, identity hook):
|
|
10
|
+
-- - Doc-view buckets correlate with overall activity; deploys
|
|
11
|
+
-- must be read as a rate over other events (H5/H10). The
|
|
12
|
+
-- organic over/low activity curve is ~1.18x.
|
|
13
|
+
-- - The churn cutoff for behavioral reads is 2026-02-04
|
|
14
|
+
-- (day 34): H5 deploy clones spawn at lastEvent + 1-48h
|
|
15
|
+
-- AFTER the churn splice, reaching ~day 33 at most.
|
|
16
|
+
-- - H9's funnel TTC leg is NOT visible to cross-event SQL
|
|
17
|
+
-- (funnel-post scales gaps within funnel instances); use
|
|
18
|
+
-- the emulator via sass.verify.mjs. Property legs below
|
|
19
|
+
-- carry the full 0.67/1.5 factors.
|
|
20
|
+
-- - H11 must be read per-instance ($experiment_started
|
|
21
|
+
-- anchors), not per-user; engine column casing is
|
|
22
|
+
-- "Experiment name" / "Variant name".
|
|
23
|
+
--
|
|
24
|
+
-- Replace data/verify-sass with your run prefix.
|
|
25
|
+
-- ============================================================
|
|
26
|
+
|
|
27
|
+
-- H1a: plan_upgraded share of billing events, EOQ window (days 100-110) vs rest
|
|
28
|
+
-- Expected: upgrades/day ~3.95x; in-window share ~0.45 vs baseline ~0.11
|
|
29
|
+
SELECT (time::TIMESTAMP >= TIMESTAMP '2026-04-11 00:00:00' AND time::TIMESTAMP <= TIMESTAMP '2026-04-21 00:00:00') AS eoq,
|
|
30
|
+
COUNT(*) AS n,
|
|
31
|
+
COUNT(*) FILTER (WHERE event_type = 'plan_upgraded') AS upgrades,
|
|
32
|
+
ROUND(AVG((event_type = 'plan_upgraded')::INT), 4) AS upgrade_share
|
|
33
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
34
|
+
WHERE event = 'billing event' GROUP BY 1 ORDER BY 1;
|
|
35
|
+
|
|
36
|
+
-- H1b: team invites per day, EOQ window vs rest — expected ~1.55x
|
|
37
|
+
SELECT (time::TIMESTAMP >= TIMESTAMP '2026-04-11 00:00:00' AND time::TIMESTAMP <= TIMESTAMP '2026-04-21 00:00:00') AS eoq,
|
|
38
|
+
COUNT(*) AS n,
|
|
39
|
+
ROUND(COUNT(*)::DOUBLE / (CASE WHEN (time::TIMESTAMP >= TIMESTAMP '2026-04-11 00:00:00' AND time::TIMESTAMP <= TIMESTAMP '2026-04-21 00:00:00') THEN 10.0 ELSE 111.0 END), 2) AS per_day
|
|
40
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
41
|
+
WHERE event = 'team member invited' GROUP BY 1 ORDER BY 1;
|
|
42
|
+
|
|
43
|
+
-- H2: charCode-hash churn cohort vs behavioral silence (< 2026-02-04)
|
|
44
|
+
-- Expected: hash-true silent 1.00, hash-false ~0, cohort ~19% of event-users
|
|
45
|
+
WITH ue AS (
|
|
46
|
+
SELECT user_id::VARCHAR AS uid, MAX(time::TIMESTAMP) AS last_t
|
|
47
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
48
|
+
WHERE user_id IS NOT NULL GROUP BY 1
|
|
49
|
+
)
|
|
50
|
+
SELECT (list_sum([ascii(x) for x in string_split(uid, '')]) % 5 = 0) AS churn_hash,
|
|
51
|
+
COUNT(*) AS users,
|
|
52
|
+
ROUND(AVG((last_t < TIMESTAMP '2026-02-04 00:00:00')::INT), 4) AS silent_share
|
|
53
|
+
FROM ue GROUP BY 1 ORDER BY 1;
|
|
54
|
+
|
|
55
|
+
-- H2: zero-event profiles (churn-hashed users born after day 30) — expected ~2.4%
|
|
56
|
+
SELECT (SELECT COUNT(*) FROM read_json_auto('data/verify-sass-USERS*.json', sample_size=-1, union_by_name=true)) AS profiles,
|
|
57
|
+
(SELECT COUNT(DISTINCT user_id::VARCHAR) FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true) WHERE user_id IS NOT NULL) AS event_users;
|
|
58
|
+
|
|
59
|
+
-- H3: escalated (alert_id set) vs organic incidents vs remaining crit/emerg alerts
|
|
60
|
+
-- Expected: esc / (esc + remaining crit) = 0.30; esc share of incidents ~0.45
|
|
61
|
+
SELECT
|
|
62
|
+
COUNT(*) FILTER (WHERE event = 'incident created' AND alert_id IS NOT NULL) AS esc,
|
|
63
|
+
COUNT(*) FILTER (WHERE event = 'incident created' AND alert_id IS NULL) AS organic,
|
|
64
|
+
COUNT(*) FILTER (WHERE event = 'alert triggered' AND severity IN ('critical', 'emergency')) AS crit
|
|
65
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true);
|
|
66
|
+
|
|
67
|
+
-- H4: response/resolution by slack+pagerduty cohort — expected ratios ~0.39 / ~0.50
|
|
68
|
+
WITH integ AS (
|
|
69
|
+
SELECT user_id::VARCHAR AS uid,
|
|
70
|
+
BOOL_OR(event = 'integration configured' AND integration_type = 'slack') AS s,
|
|
71
|
+
BOOL_OR(event = 'integration configured' AND integration_type = 'pagerduty') AS p
|
|
72
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
73
|
+
WHERE user_id IS NOT NULL GROUP BY 1
|
|
74
|
+
)
|
|
75
|
+
SELECT (s AND p) AS both_integ, COUNT(DISTINCT e.user_id::VARCHAR) AS users,
|
|
76
|
+
ROUND(AVG(response_time_mins) FILTER (WHERE event = 'alert acknowledged'), 2) AS avg_resp,
|
|
77
|
+
ROUND(AVG(resolution_time_mins) FILTER (WHERE event = 'alert resolved'), 2) AS avg_reso
|
|
78
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true) e
|
|
79
|
+
JOIN integ i ON e.user_id::VARCHAR = i.uid GROUP BY 1 ORDER BY 1;
|
|
80
|
+
|
|
81
|
+
-- H5/H10: deploys-per-other-event by doc-view bucket, non-churned users only
|
|
82
|
+
-- Expected: sweet/low ~1.26; over/low ~0.89; over/sweet ~0.70
|
|
83
|
+
WITH pu AS (
|
|
84
|
+
SELECT user_id::VARCHAR AS uid,
|
|
85
|
+
COUNT(*) FILTER (WHERE event = 'documentation viewed') AS docs,
|
|
86
|
+
COUNT(*) FILTER (WHERE event = 'service deployed') AS deploys,
|
|
87
|
+
COUNT(*) FILTER (WHERE event NOT IN ('service deployed', 'documentation viewed')) AS other,
|
|
88
|
+
MAX(time::TIMESTAMP) AS last_t
|
|
89
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
90
|
+
WHERE user_id IS NOT NULL GROUP BY 1
|
|
91
|
+
)
|
|
92
|
+
SELECT CASE WHEN docs >= 8 THEN 'over' WHEN docs >= 4 THEN 'sweet' ELSE 'low' END AS bucket,
|
|
93
|
+
COUNT(*) AS users, ROUND(AVG(docs), 2) AS avg_docs,
|
|
94
|
+
ROUND(SUM(deploys)::DOUBLE / SUM(other), 5) AS deploys_per_other
|
|
95
|
+
FROM pu WHERE last_t >= TIMESTAMP '2026-02-04 00:00:00'
|
|
96
|
+
GROUP BY 1 ORDER BY 1;
|
|
97
|
+
|
|
98
|
+
-- H6: scale-direction by armed state (last >25% cost spike newer than last scale)
|
|
99
|
+
-- Expected: armed down-share ~0.90 vs unarmed ~0.27 (baseline array is 3 up / 1 down)
|
|
100
|
+
WITH seq AS (
|
|
101
|
+
SELECT user_id::VARCHAR AS uid, time::TIMESTAMP AS t, event, cost_change_percent, scale_direction
|
|
102
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
103
|
+
WHERE event IN ('cost report generated', 'infrastructure scaled') AND user_id IS NOT NULL
|
|
104
|
+
), marked AS (
|
|
105
|
+
SELECT *,
|
|
106
|
+
MAX(CASE WHEN event = 'cost report generated' AND cost_change_percent > 25 THEN t END)
|
|
107
|
+
OVER (PARTITION BY uid ORDER BY t ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS last_spike,
|
|
108
|
+
MAX(CASE WHEN event = 'infrastructure scaled' THEN t END)
|
|
109
|
+
OVER (PARTITION BY uid ORDER BY t ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS last_infra
|
|
110
|
+
FROM seq
|
|
111
|
+
)
|
|
112
|
+
SELECT (last_spike IS NOT NULL AND (last_infra IS NULL OR last_spike > last_infra)) AS armed,
|
|
113
|
+
COUNT(*) AS n, ROUND(AVG((scale_direction = 'down')::INT), 4) AS down_share
|
|
114
|
+
FROM marked WHERE event = 'infrastructure scaled' GROUP BY 1 ORDER BY 1;
|
|
115
|
+
|
|
116
|
+
-- H7: recovery-run duration vs other success runs (first runs excluded) — expected ~1.52x
|
|
117
|
+
WITH p AS (
|
|
118
|
+
SELECT user_id::VARCHAR AS uid, status, duration_sec,
|
|
119
|
+
LAG(status) OVER (PARTITION BY user_id::VARCHAR ORDER BY time) AS prev
|
|
120
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
121
|
+
WHERE event = 'deployment pipeline run' AND user_id IS NOT NULL
|
|
122
|
+
)
|
|
123
|
+
SELECT (prev = 'failed') AS recovery, COUNT(*) AS n, ROUND(AVG(duration_sec), 1) AS avg_dur
|
|
124
|
+
FROM p WHERE status = 'success' AND prev IS NOT NULL GROUP BY 1 ORDER BY 1;
|
|
125
|
+
|
|
126
|
+
-- H8: profile economics by company_size — expected ACV ~273K/31K/7.8K/1.8K, csm enterprise-only
|
|
127
|
+
SELECT company_size, COUNT(*) AS users, ROUND(AVG(annual_contract_value), 0) AS acv,
|
|
128
|
+
ROUND(AVG(seat_count), 1) AS seats, ROUND(AVG(customer_success_manager::INT), 3) AS csm
|
|
129
|
+
FROM read_json_auto('data/verify-sass-USERS*.json', sample_size=-1, union_by_name=true)
|
|
130
|
+
GROUP BY 1 ORDER BY acv DESC;
|
|
131
|
+
|
|
132
|
+
-- H9 (property legs): response/resolution by company_size
|
|
133
|
+
-- Expected vs smb/mid avg: enterprise ~0.65, startup ~1.50 (full factors)
|
|
134
|
+
SELECT u.company_size,
|
|
135
|
+
COUNT(*) FILTER (WHERE event = 'alert acknowledged') AS n_ack,
|
|
136
|
+
ROUND(AVG(response_time_mins) FILTER (WHERE event = 'alert acknowledged'), 2) AS resp,
|
|
137
|
+
ROUND(AVG(resolution_time_mins) FILTER (WHERE event = 'alert resolved'), 2) AS reso
|
|
138
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true) e
|
|
139
|
+
JOIN read_json_auto('data/verify-sass-USERS*.json', sample_size=-1, union_by_name=true) u
|
|
140
|
+
ON e.user_id::VARCHAR = u.distinct_id::VARCHAR
|
|
141
|
+
GROUP BY 1 ORDER BY resp;
|
|
142
|
+
|
|
143
|
+
-- H11: per-instance experiment read (greedy min-chain within 24h of each anchor)
|
|
144
|
+
-- Expected: canary/control conversion lift ~1.21, median TTC ratio ~0.81
|
|
145
|
+
WITH exp AS (
|
|
146
|
+
SELECT user_id::VARCHAR AS uid, time::TIMESTAMP AS t0, "Variant name" AS variant
|
|
147
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
148
|
+
WHERE event = '$experiment_started'
|
|
149
|
+
), ev2 AS (
|
|
150
|
+
SELECT user_id::VARCHAR AS uid, event, time::TIMESTAMP AS t
|
|
151
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
152
|
+
WHERE event IN ('deployment pipeline run', 'service deployed', 'dashboard viewed')
|
|
153
|
+
), c1 AS (
|
|
154
|
+
SELECT x.uid, x.variant, x.t0,
|
|
155
|
+
(SELECT MIN(t) FROM ev2 e WHERE e.uid = x.uid AND e.event = 'deployment pipeline run' AND e.t >= x.t0) AS tp
|
|
156
|
+
FROM exp x
|
|
157
|
+
), c2 AS (
|
|
158
|
+
SELECT c.*, (SELECT MIN(t) FROM ev2 e WHERE e.uid = c.uid AND e.event = 'service deployed' AND e.t >= c.tp) AS td
|
|
159
|
+
FROM c1 c
|
|
160
|
+
), c3 AS (
|
|
161
|
+
SELECT c.*, (SELECT MIN(t) FROM ev2 e WHERE e.uid = c.uid AND e.event = 'dashboard viewed' AND e.t >= c.td) AS tb
|
|
162
|
+
FROM c2 c
|
|
163
|
+
)
|
|
164
|
+
SELECT variant, COUNT(*) AS attempts, COUNT(DISTINCT uid) AS users,
|
|
165
|
+
ROUND(AVG((tb IS NOT NULL AND tb <= t0 + INTERVAL 24 HOUR)::INT), 4) AS conv_rate,
|
|
166
|
+
median(CASE WHEN tb IS NOT NULL AND tb <= t0 + INTERVAL 24 HOUR THEN date_diff('minute', tp, tb) END) AS med_ttc_min
|
|
167
|
+
FROM c3 GROUP BY 1 ORDER BY 1;
|
|
168
|
+
|
|
169
|
+
-- Identity invariants — expected uid_share 1.0, device_share >= 0.99, devices/user ~2
|
|
170
|
+
SELECT ROUND(AVG((user_id IS NOT NULL)::INT), 4) AS uid_share,
|
|
171
|
+
ROUND(AVG((device_id IS NOT NULL)::INT), 4) AS device_share,
|
|
172
|
+
ROUND(COUNT(DISTINCT device_id)::DOUBLE / COUNT(DISTINCT user_id), 2) AS devices_per_user
|
|
173
|
+
FROM read_json_auto('data/verify-sass-EVENTS*.json', sample_size=-1, union_by_name=true);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* sass.verify.mjs — thin wrapper around the story runner.
|
|
4
|
+
*
|
|
5
|
+
* All verification logic lives in the `stories` export of ./sass.js;
|
|
6
|
+
* this script just streams the shards and delegates. It is equivalent to:
|
|
7
|
+
*
|
|
8
|
+
* node scripts/verify-stories.mjs dungeons/vertical/sass/sass.js --data-prefix verify-sass
|
|
9
|
+
*
|
|
10
|
+
* Generate first:
|
|
11
|
+
* node scripts/verify-runner.mjs dungeons/vertical/sass/sass.js verify-sass
|
|
12
|
+
* Run:
|
|
13
|
+
* node dungeons/vertical/sass/sass.verify.mjs [prefix] # default verify-sass
|
|
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 './sass.js';
|
|
22
|
+
|
|
23
|
+
const PREFIX = process.argv[2] || 'verify-sass';
|
|
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/sass/sass.js ${PREFIX}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log(`sass — 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 (temporal windows, behavioral cohorts,
|
|
55
|
+
// rate-over-rate reads that cancel the doc-view activity confound, and
|
|
56
|
+
// per-instance experiment pairing). H9's funnel leg dispatches to
|
|
57
|
+
// emulateBreakdown's timeToConvert at a 24-HOUR window — funnel-post gap
|
|
58
|
+
// scaling is invisible to cross-event SQL, and longer windows only admit
|
|
59
|
+
// a slow organic tail that dilutes the segment medians.
|
|
60
|
+
const results = await evaluateStories(stories, events, {
|
|
61
|
+
profiles,
|
|
62
|
+
funnels: config.funnels,
|
|
63
|
+
identityMap: buildIdentityMap(profiles),
|
|
64
|
+
runSql,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
let worst = 'NAILED';
|
|
68
|
+
for (const r of results) {
|
|
69
|
+
console.log(`${r.verdict.padEnd(7)} ${r.id}`);
|
|
70
|
+
for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
|
|
71
|
+
if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
|
|
72
|
+
}
|
|
73
|
+
console.log(`\nworst verdict: ${worst}`);
|
|
74
|
+
process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);
|