@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,174 @@
1
+ -- ============================================================
2
+ -- insurance-application.js — v1.6 human-inspection queries (DuckDB)
3
+ --
4
+ -- Every query is keyed to a story id in insurance-application.js's
5
+ -- `stories` export; the machine-checked verdicts come from:
6
+ -- node scripts/verify-stories.mjs dungeons/vertical/insurance-application/insurance-application.js --data-prefix verify-insurance-application
7
+ -- Generate first:
8
+ -- node scripts/verify-runner.mjs dungeons/vertical/insurance-application/insurance-application.js verify-insurance-application
9
+ -- Run this file:
10
+ -- duckdb -c ".read dungeons/vertical/insurance-application/insurance-application.sql"
11
+ --
12
+ -- NOTE: this dungeon uses a LITERAL historical window (datasetStart
13
+ -- 2026-01-01 → datasetEnd 2026-05-01T23:59:59Z, no forward shift), so
14
+ -- day_idx = date_diff('day', DATE '2026-01-01', t::DATE) matches the
15
+ -- hook's day offsets exactly. Version boundaries are exact timestamps:
16
+ -- v2.11 @ 2026-01-31 00:00:00, v2.12 @ 2026-03-02 00:00:00,
17
+ -- v2.13 @ 2026-04-21 23:59:59 (end − 10d — NOT midnight).
18
+ -- ============================================================
19
+
20
+ -- ── identity-resolution prelude ─────────────────────────────
21
+ -- 'account created' is both isAuthEvent and isFirstEvent, so every user
22
+ -- auths on their very first event — user_id is present on ALL events and
23
+ -- there are zero device-only rows (asserted by story H1).
24
+ CREATE OR REPLACE VIEW us AS
25
+ SELECT distinct_id::VARCHAR AS duid, risk_profile
26
+ FROM read_json_auto('data/verify-insurance-application-USERS*.json', sample_size=-1, union_by_name=true);
27
+
28
+ CREATE OR REPLACE VIEW ev AS
29
+ SELECT e.user_id::VARCHAR AS uid, e.device_id::VARCHAR AS did, e.time::TIMESTAMP AS t,
30
+ date_diff('day', DATE '2026-01-01', e.time::TIMESTAMP::DATE) AS day_idx,
31
+ e.*
32
+ FROM read_json_auto('data/verify-insurance-application-EVENTS*.json', sample_size=-1, union_by_name=true) e;
33
+
34
+ -- Per-user aggregates. H8 deletes 75% of post-day-30 events for born
35
+ -- non-uploaders, so reads needing undistorted per-user tallies (H4, H10)
36
+ -- restrict to the H8-untouched population: (NOT born) OR uploader.
37
+ CREATE OR REPLACE VIEW pu AS
38
+ SELECT uid, min(t) AS first_t,
39
+ count(*) FILTER (WHERE event = 'account created') AS acct_created,
40
+ count(*) FILTER (WHERE event = 'application step completed') AS steps,
41
+ count(*) FILTER (WHERE event = 'application approved') AS approvals,
42
+ count(*) FILTER (WHERE event = 'application submitted') AS submits,
43
+ count(*) FILTER (WHERE event = 'claim filed') AS claims,
44
+ count(*) FILTER (WHERE event = 'payment made') AS payments
45
+ FROM ev GROUP BY 1;
46
+
47
+ CREATE OR REPLACE VIEW puu AS
48
+ SELECT p.*, p.acct_created > 0 AS born,
49
+ ((SELECT count(*) FROM ev e WHERE e.uid = p.uid AND e.event = 'document uploaded'
50
+ AND e.t <= p.first_t + INTERVAL '14 days') >= 3) AS uploader
51
+ FROM pu p;
52
+
53
+ CREATE OR REPLACE VIEW h4pop AS
54
+ SELECT * FROM puu WHERE (NOT born) OR uploader;
55
+
56
+ -- ── H1-version-bands: app_version is a pure function of timestamp ──
57
+ -- Expect: zero violations, four crisp bands.
58
+ SELECT app_version, count(*) AS events,
59
+ min(t) AS first_seen, max(t) AS last_seen
60
+ FROM ev GROUP BY 1 ORDER BY 1;
61
+
62
+ SELECT count(*) AS version_violations FROM ev
63
+ WHERE app_version != CASE
64
+ WHEN t < TIMESTAMP '2026-01-31 00:00:00' THEN '2.10'
65
+ WHEN t < TIMESTAMP '2026-03-02 00:00:00' THEN '2.11'
66
+ WHEN t < TIMESTAMP '2026-04-21 23:59:59' THEN '2.12'
67
+ ELSE '2.13' END;
68
+
69
+ -- ── H2-ticket-drop: volume collapse + bug categories vanish ──
70
+ -- Expect: v2.13 daily rate ~0.3x the final-v2.12 rate; bug-category share
71
+ -- ~0.67 pre-release, EXACTLY ZERO post (survivors are recategorized).
72
+ SELECT
73
+ count(*) FILTER (WHERE day_idx BETWEEN 101 AND 110) / 10.0 AS v212_last10_per_day,
74
+ count(*) FILTER (WHERE day_idx BETWEEN 111 AND 120) / 10.0 AS v213_per_day
75
+ FROM ev WHERE event = 'support ticket created';
76
+
77
+ SELECT
78
+ avg((issue_category IN ('form_crash','login_error','page_timeout','payment_failure'))::INT)
79
+ FILTER (WHERE t < TIMESTAMP '2026-04-21 23:59:59') AS bug_share_pre,
80
+ count(*) FILTER (WHERE issue_category IN ('form_crash','login_error','page_timeout','payment_failure')
81
+ AND t >= TIMESTAMP '2026-04-21 23:59:59') AS bug_tickets_post
82
+ FROM ev WHERE event = 'support ticket created';
83
+
84
+ -- ── H3-activation-gap: policy activated flatlines pre-v2.13 ──
85
+ -- Expect: ~28x per-day step-up on release (1/0.05 gate x organic drift);
86
+ -- approvals are untouched (H5 pins them near each user's start).
87
+ SELECT
88
+ count(*) FILTER (WHERE t < TIMESTAMP '2026-04-21 23:59:59') / 111.0 AS act_pre_per_day,
89
+ count(*) FILTER (WHERE t >= TIMESTAMP '2026-04-21 23:59:59') / 10.0 AS act_post_per_day
90
+ FROM ev WHERE event = 'policy activated';
91
+
92
+ -- ── H4-step-magic: sweet-spot premium boost + over-engagement drop ──
93
+ -- Expect: sweet/base avg approved_premium ~1.35x; over/sweet
94
+ -- approvals-per-submit ~0.5x (raw per-user is activity-confounded).
95
+ SELECT
96
+ (SELECT avg(e.approved_premium) FROM ev e JOIN h4pop p ON e.uid = p.uid
97
+ WHERE e.event = 'application approved' AND p.steps BETWEEN 8 AND 14) AS sweet_avg_premium,
98
+ (SELECT avg(e.approved_premium) FROM ev e JOIN h4pop p ON e.uid = p.uid
99
+ WHERE e.event = 'application approved' AND p.steps < 8) AS base_avg_premium,
100
+ (SELECT sum(approvals)::DOUBLE / nullif(sum(submits), 0) FROM h4pop WHERE steps >= 15) AS over_app_per_submit,
101
+ (SELECT sum(approvals)::DOUBLE / nullif(sum(submits), 0) FROM h4pop WHERE steps BETWEEN 8 AND 14) AS sweet_app_per_submit;
102
+
103
+ -- ── H5-ttc-account-type: started→approved gap pinned per account type ──
104
+ -- Expect: medians ~37h (business) / ~50h (individual) / ~65h (family);
105
+ -- support exactly [target, target+4h). Born users only (account_type
106
+ -- lives on the "account created" event).
107
+ WITH acct AS (SELECT uid, min(account_type) AS account_type FROM ev WHERE event = 'account created' GROUP BY 1),
108
+ fs AS (SELECT uid, min(t) AS first_started FROM ev WHERE event = 'application started' GROUP BY 1),
109
+ gaps AS (SELECT a.account_type, date_diff('second', f.first_started, e.t) / 3600.0 AS gap_h
110
+ FROM ev e JOIN fs f ON e.uid = f.uid JOIN acct a ON e.uid = a.uid
111
+ WHERE e.event = 'application approved')
112
+ SELECT account_type, count(*) AS n, median(gap_h) AS med_h, min(gap_h) AS min_h, max(gap_h) AS max_h
113
+ FROM gaps GROUP BY 1 ORDER BY med_h;
114
+
115
+ -- ── H6-claims-experiment: engine-native A/B on Claims Process ──
116
+ -- Expect: ~50/50 split, zero exposures before end−35d, Simplified ~1.3x
117
+ -- completion (49h window covers the 0.8x-compressed variant TTC).
118
+ SELECT "Variant name" AS variant, count(*) AS exposures, count(DISTINCT uid) AS users,
119
+ count(*) FILTER (WHERE t < TIMESTAMP '2026-03-27 23:59:59') AS pre_start_exposures
120
+ FROM ev WHERE event = '$experiment_started' GROUP BY 1;
121
+
122
+ WITH expusers AS (SELECT uid, min("Variant name") AS variant FROM ev WHERE event = '$experiment_started'
123
+ GROUP BY 1 HAVING count(DISTINCT "Variant name") = 1),
124
+ claimwin AS (SELECT c.uid,
125
+ (EXISTS (SELECT 1 FROM ev s WHERE s.uid = c.uid AND s.event = 'claim status checked'
126
+ AND s.t > c.t AND s.t <= c.t + INTERVAL 49 HOUR
127
+ AND EXISTS (SELECT 1 FROM ev k WHERE k.uid = c.uid AND k.event = 'support ticket created'
128
+ AND k.t > s.t AND k.t <= c.t + INTERVAL 49 HOUR))) AS completed
129
+ FROM ev c WHERE c.event = 'claim filed' AND c.t >= TIMESTAMP '2026-03-27 23:59:59')
130
+ SELECT x.variant, count(*) AS instances, avg(w.completed::INT) AS completion_49h
131
+ FROM claimwin w JOIN expusers x ON w.uid = x.uid GROUP BY 1;
132
+
133
+ -- ── H7-risk-approval: funnel-pre conversion multipliers ──
134
+ -- Expect approvals-per-submit low/med ~1.15x, high/med ~0.71x (partial-step
135
+ -- walk compresses the 1.8x/0.3x knobs: p = c + (1−c)/2 → 0.975/0.85/0.605).
136
+ SELECT u.risk_profile, count(*) AS users,
137
+ sum(p.approvals)::DOUBLE / nullif(sum(p.submits), 0) AS approvals_per_submit
138
+ FROM puu p JOIN us u ON p.uid = u.duid GROUP BY 1 ORDER BY 1;
139
+
140
+ -- ── H8-doc-retention: churn for born non-uploaders ──
141
+ -- Expect DiD (nonup post/pre ÷ up post/pre) ~0.25-0.30. Small cohorts even
142
+ -- at 15K (~240 non-uploaders / ~45 uploaders born before Mar 2).
143
+ WITH h8pop AS (SELECT p.*,
144
+ (SELECT count(*) FROM ev e WHERE e.uid = p.uid AND e.t <= p.first_t + INTERVAL '30 days') AS pre_n,
145
+ (SELECT count(*) FROM ev e WHERE e.uid = p.uid AND e.t > p.first_t + INTERVAL '30 days') AS post_n
146
+ FROM puu p WHERE p.born AND p.first_t < TIMESTAMP '2026-03-02')
147
+ SELECT
148
+ (SELECT sum(post_n)::DOUBLE / sum(pre_n) FROM h8pop WHERE NOT uploader) AS nonup_post_pre,
149
+ (SELECT sum(post_n)::DOUBLE / sum(pre_n) FROM h8pop WHERE uploader) AS up_post_pre,
150
+ (SELECT count(*) FROM h8pop WHERE NOT uploader) AS nonup_users,
151
+ (SELECT count(*) FROM h8pop WHERE uploader) AS up_users;
152
+
153
+ -- ── H9-renewal-spike: end-of-quarter clone burst, days 85-94 ──
154
+ -- Expect renewals ~3x and coverage reviews ~1.8-2x a LOCAL baseline
155
+ -- (days 75-84 ∪ 96-105; day 95 skipped — clone jitter bleeds into Apr 6).
156
+ SELECT event,
157
+ count(*) FILTER (WHERE t > TIMESTAMP '2026-03-27' AND t < TIMESTAMP '2026-04-06') / 10.0 AS spike_per_day,
158
+ count(*) FILTER (WHERE (day_idx BETWEEN 75 AND 84) OR (day_idx BETWEEN 96 AND 105)) / 20.0 AS local_base_per_day
159
+ FROM ev WHERE event IN ('renewal completed', 'coverage reviewed') GROUP BY 1;
160
+
161
+ -- ── H10-claim-premium: one-shot doubling after claim ──
162
+ -- Expect: zero payments > 600 among H8-untouched non-claimants (organic
163
+ -- premium caps at 600); claimant/non-claimant avg ~1.5x (only ~half of a
164
+ -- claimant's payments double — the 2.0x lives on the doubled payments).
165
+ SELECT
166
+ (SELECT count(*) FROM ev e JOIN puu p ON e.uid = p.uid
167
+ WHERE e.event = 'payment made' AND e.premium_amount > 600 AND p.claims = 0
168
+ AND ((NOT p.born) OR p.uploader)) AS gt600_untouched_nonclaimants,
169
+ (SELECT count(*) FROM ev e JOIN puu p ON e.uid = p.uid
170
+ WHERE e.event = 'payment made' AND e.premium_amount > 600 AND p.claims > 0) AS gt600_claimants,
171
+ (SELECT avg(e.premium_amount) FROM ev e JOIN puu p ON e.uid = p.uid
172
+ WHERE e.event = 'payment made' AND p.claims > 0) AS claimant_avg,
173
+ (SELECT avg(e.premium_amount) FROM ev e JOIN puu p ON e.uid = p.uid
174
+ WHERE e.event = 'payment made' AND p.claims = 0) AS nonclaimant_avg;
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * insurance-application.verify.mjs — thin wrapper around the story runner.
4
+ *
5
+ * All verification logic lives in the `stories` export of
6
+ * ./insurance-application.js; this script just streams the shards and
7
+ * delegates. It is equivalent to:
8
+ *
9
+ * node scripts/verify-stories.mjs dungeons/vertical/insurance-application/insurance-application.js --data-prefix verify-insurance-application
10
+ *
11
+ * Generate first:
12
+ * node scripts/verify-runner.mjs dungeons/vertical/insurance-application/insurance-application.js verify-insurance-application
13
+ * Run:
14
+ * node dungeons/vertical/insurance-application/insurance-application.verify.mjs [prefix] # default verify-insurance-application
15
+ */
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import readline from 'node:readline';
19
+ import { execFile } from 'node:child_process';
20
+ import { promisify } from 'node:util';
21
+ import { buildIdentityMap, evaluateStories, VERDICT_RANK } from '@ak--47/dungeon-master/verify';
22
+ import config, { stories } from './insurance-application.js';
23
+
24
+ const PREFIX = process.argv[2] || 'verify-insurance-application';
25
+ const prefixPath = path.join('data', PREFIX);
26
+
27
+ async function loadShards(suffix) {
28
+ // streaming load: full-fidelity EVENTS shards exceed the readFileSync cap
29
+ const dir = path.dirname(prefixPath), base = path.basename(prefixPath);
30
+ const out = [];
31
+ if (!fs.existsSync(dir)) return out;
32
+ for (const f of fs.readdirSync(dir).filter(f => f.startsWith(`${base}-${suffix}`) && f.endsWith('.json')).sort()) {
33
+ const rl = readline.createInterface({ input: fs.createReadStream(path.join(dir, f)), crlfDelay: Infinity });
34
+ for await (const line of rl) {
35
+ if (line.trim()) out.push(JSON.parse(line));
36
+ }
37
+ }
38
+ return out;
39
+ }
40
+
41
+ const events = await loadShards('EVENTS');
42
+ const profiles = await loadShards('USERS');
43
+ if (!events.length) {
44
+ console.error(`no shards at ${prefixPath}-EVENTS*.json — run: node scripts/verify-runner.mjs dungeons/vertical/insurance-application/insurance-application.js ${PREFIX}`);
45
+ process.exit(1);
46
+ }
47
+ console.log(`insurance-application — events=${events.length} users=${profiles.length} (${prefixPath})`);
48
+
49
+ const execFileP = promisify(execFile);
50
+ const runSql = async (sql) => {
51
+ const { stdout } = await execFileP('duckdb', ['-json', '-c', sql.replaceAll('{{PREFIX}}', prefixPath)], { maxBuffer: 64 * 1024 * 1024 });
52
+ return stdout.trim() ? JSON.parse(stdout) : [];
53
+ };
54
+
55
+ // All stories are DuckDB-backed. H5's TTC breakdown segments by account_type,
56
+ // which lives on the "account created" EVENT (not a profile prop), so
57
+ // emulateBreakdown's breakdownByUserProperty can't serve it — the SQL read
58
+ // derives the started→approved gap directly, which is exact for this hook
59
+ // because H5 pins approvals to firstStart + target + [0,4h) by construction.
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);