@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.
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 +96 -0
  4. package/.claude/skills/create-project/provision.mjs +336 -0
  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 +211 -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 +3 -2
  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,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);