@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,137 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- support-desk.js — v1.6.0 Hook Inspection Queries (DuckDB)
|
|
3
|
+
-- Score: see README — machine contract in support-desk.js `stories`
|
|
4
|
+
-- Run after: node scripts/verify-runner.mjs dungeons/vertical/support-desk/support-desk.js verify-support-desk
|
|
5
|
+
-- ============================================================
|
|
6
|
+
--
|
|
7
|
+
-- v1.6.0 derivation notes (2K reduced run vs organic counterfactual):
|
|
8
|
+
-- 1. This dungeon is the FLOWS + SESSIONS SHOWCASE: applySessionShape
|
|
9
|
+
-- retimes every user's stream into role-shaped sessions (agents ~5
|
|
10
|
+
-- long 45-min sessions/week, requesters ~1 short 10-min session/week
|
|
11
|
+
-- of ~28 events), then applyPathBias injects a one-touch resolution
|
|
12
|
+
-- (H1 band, 5-20s gaps — in-session) and a cross-session escalation
|
|
13
|
+
-- chain (H2 band, 1-4h gaps — resolved lands at session ordinal +3).
|
|
14
|
+
-- 2. hashFloat bands are not expressible in SQL, but `role` is pinned on
|
|
15
|
+
-- every event, so per-role sessionization (LAG > 30 min) is exact.
|
|
16
|
+
-- Session-count conversion windows and topPaths semantics live in the
|
|
17
|
+
-- stories export (emulator); the queries below eyeball the same
|
|
18
|
+
-- signals through time-gap and session aggregates.
|
|
19
|
+
-- 3. H2's signature in raw SQL: the session-ordinal span from a user's
|
|
20
|
+
-- first 'ticket created' to their first subsequent 'ticket resolved'
|
|
21
|
+
-- is 1 for the H1 band (one-touch) and 4 for the H2 band (the
|
|
22
|
+
-- injected chain's three cross-session steps) — the histogram in
|
|
23
|
+
-- query 2 shows both spikes; organic data ramps smoothly.
|
|
24
|
+
-- ============================================================
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
-- Hook 1: ONE-TOUCH RESOLUTION — ~45% of users get reply+resolved
|
|
28
|
+
-- injected 5-20s after their first ticket created.
|
|
29
|
+
-- Expect: a large spike of first-created → first-resolved gaps under
|
|
30
|
+
-- 30 seconds (2K hooked: ~0.45 of ticket creators; organic: none under
|
|
31
|
+
-- a minute — organic resolution flows through the F2 funnel over hours).
|
|
32
|
+
WITH firsts AS (
|
|
33
|
+
SELECT user_id,
|
|
34
|
+
MIN(CASE WHEN event = 'ticket created' THEN time::TIMESTAMP END) AS first_created,
|
|
35
|
+
MIN(CASE WHEN event = 'ticket resolved' THEN time::TIMESTAMP END) AS first_resolved
|
|
36
|
+
FROM read_json_auto('data/verify-support-desk-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
37
|
+
WHERE user_id IS NOT NULL
|
|
38
|
+
GROUP BY 1
|
|
39
|
+
)
|
|
40
|
+
SELECT
|
|
41
|
+
CASE
|
|
42
|
+
WHEN gap_s < 30 THEN 'a. <30s (H1 one-touch)'
|
|
43
|
+
WHEN gap_s < 1800 THEN 'b. 30s-30min'
|
|
44
|
+
WHEN gap_s < 14400 THEN 'c. 30min-4h (H2 chain)'
|
|
45
|
+
ELSE 'd. >4h (organic tail)'
|
|
46
|
+
END AS first_resolution_gap,
|
|
47
|
+
COUNT(*) AS users
|
|
48
|
+
FROM (
|
|
49
|
+
SELECT EXTRACT(EPOCH FROM first_resolved - first_created)::DOUBLE AS gap_s
|
|
50
|
+
FROM firsts
|
|
51
|
+
WHERE first_created IS NOT NULL AND first_resolved IS NOT NULL AND first_resolved > first_created
|
|
52
|
+
)
|
|
53
|
+
GROUP BY 1 ORDER BY 1;
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
-- Hook 2: CROSS-SESSION ESCALATION — the H2 band's organic resolutions
|
|
57
|
+
-- are dropped; the injected escalated→reply→resolved chain (1-4h gaps)
|
|
58
|
+
-- resolves at session ordinal +3.
|
|
59
|
+
-- Expect: bimodal session-ordinal span from first created to first
|
|
60
|
+
-- resolved — a spike at 1 (H1 one-touch, ~63% of resolvers) and a spike
|
|
61
|
+
-- at 4 (H2 chain, ~35%); organic data spreads across many ordinals.
|
|
62
|
+
WITH e AS (
|
|
63
|
+
SELECT user_id AS uid, event, time::TIMESTAMP AS t,
|
|
64
|
+
CASE WHEN LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) IS NULL
|
|
65
|
+
OR time::TIMESTAMP - LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) > INTERVAL 30 MINUTE
|
|
66
|
+
THEN 1 ELSE 0 END AS is_start
|
|
67
|
+
FROM read_json_auto('data/verify-support-desk-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
68
|
+
WHERE user_id IS NOT NULL
|
|
69
|
+
), s AS (
|
|
70
|
+
SELECT uid, event, t, SUM(is_start) OVER (PARTITION BY uid ORDER BY t) AS sid FROM e
|
|
71
|
+
), spans AS (
|
|
72
|
+
SELECT c.uid,
|
|
73
|
+
(SELECT MIN(r.sid) FROM s r WHERE r.uid = c.uid AND r.event = 'ticket resolved' AND r.t > c.t0)
|
|
74
|
+
- c.sid0 + 1 AS ordinal_span
|
|
75
|
+
FROM (
|
|
76
|
+
SELECT uid, MIN(t) AS t0, ARG_MIN(sid, t) AS sid0
|
|
77
|
+
FROM s WHERE event = 'ticket created' GROUP BY uid
|
|
78
|
+
) c
|
|
79
|
+
)
|
|
80
|
+
SELECT ordinal_span, COUNT(*) AS users
|
|
81
|
+
FROM spans WHERE ordinal_span IS NOT NULL
|
|
82
|
+
GROUP BY 1 ORDER BY 1 LIMIT 12;
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
-- Hook 3a: SESSION SHAPE — duration bimodality. applySessionShape gives
|
|
86
|
+
-- requesters 10-min sessions and agents 45-min sessions.
|
|
87
|
+
-- Expect: requester median ~10.0 min, agent median ~45.0 min (both
|
|
88
|
+
-- near-deterministic); organic sessions are ~1.2-event singletons.
|
|
89
|
+
WITH e AS (
|
|
90
|
+
SELECT user_id AS uid, role, time::TIMESTAMP AS t,
|
|
91
|
+
CASE WHEN LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) IS NULL
|
|
92
|
+
OR time::TIMESTAMP - LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) > INTERVAL 30 MINUTE
|
|
93
|
+
THEN 1 ELSE 0 END AS is_start
|
|
94
|
+
FROM read_json_auto('data/verify-support-desk-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
95
|
+
WHERE user_id IS NOT NULL
|
|
96
|
+
), s AS (
|
|
97
|
+
SELECT uid, role, t, SUM(is_start) OVER (PARTITION BY uid ORDER BY t) AS sid FROM e
|
|
98
|
+
), sess AS (
|
|
99
|
+
SELECT uid, ANY_VALUE(role) AS role, sid,
|
|
100
|
+
COUNT(*) AS events_in_session,
|
|
101
|
+
EXTRACT(EPOCH FROM MAX(t) - MIN(t))::DOUBLE / 60.0 AS dur_min
|
|
102
|
+
FROM s GROUP BY uid, sid
|
|
103
|
+
)
|
|
104
|
+
SELECT role,
|
|
105
|
+
COUNT(*) AS sessions,
|
|
106
|
+
ROUND(MEDIAN(dur_min), 1) AS median_min,
|
|
107
|
+
ROUND(QUANTILE_CONT(dur_min, 0.9), 1) AS p90_min,
|
|
108
|
+
ROUND(MEDIAN(events_in_session), 1) AS median_events
|
|
109
|
+
FROM sess GROUP BY 1 ORDER BY 1;
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
-- Hook 3b: SESSION CADENCE — agents ~5 sessions/week, requesters ~1.
|
|
113
|
+
-- Expect (2K hooked): agent median ~4.6/wk, requester ~1.0/wk (ratio
|
|
114
|
+
-- ~4.6x); organic ~23/wk for BOTH roles (ratio ~1.0x).
|
|
115
|
+
WITH e AS (
|
|
116
|
+
SELECT user_id AS uid, role, time::TIMESTAMP AS t,
|
|
117
|
+
CASE WHEN LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) IS NULL
|
|
118
|
+
OR time::TIMESTAMP - LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) > INTERVAL 30 MINUTE
|
|
119
|
+
THEN 1 ELSE 0 END AS is_start
|
|
120
|
+
FROM read_json_auto('data/verify-support-desk-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
121
|
+
WHERE user_id IS NOT NULL
|
|
122
|
+
), s AS (
|
|
123
|
+
SELECT uid, role, SUM(is_start) AS sessions FROM e GROUP BY uid, role
|
|
124
|
+
)
|
|
125
|
+
SELECT role,
|
|
126
|
+
COUNT(*) AS users,
|
|
127
|
+
ROUND(MEDIAN(sessions / 12.0), 2) AS median_sessions_per_week
|
|
128
|
+
FROM s GROUP BY 1 ORDER BY 1;
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
-- Identity invariants: every event carries user_id; avgDevicePerUser: 2.
|
|
132
|
+
-- Expect: uid_share 1.0, device_share ~0.999, devices/user ~2.05.
|
|
133
|
+
SELECT COUNT(*) AS n,
|
|
134
|
+
AVG((user_id IS NOT NULL)::INT) AS uid_share,
|
|
135
|
+
AVG((device_id IS NOT NULL)::INT) AS device_share,
|
|
136
|
+
COUNT(DISTINCT device_id)::DOUBLE / COUNT(DISTINCT user_id) AS dpu
|
|
137
|
+
FROM read_json_auto('data/verify-support-desk-EVENTS*.json', sample_size=-1, union_by_name=true);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* support-desk.verify.mjs — thin wrapper around the story runner.
|
|
4
|
+
*
|
|
5
|
+
* All verification logic lives in the `stories` export of ./support-desk.js;
|
|
6
|
+
* this script just streams the shards and delegates. It is equivalent to:
|
|
7
|
+
*
|
|
8
|
+
* node scripts/verify-stories.mjs dungeons/vertical/support-desk/support-desk.js --data-prefix verify-support-desk
|
|
9
|
+
*
|
|
10
|
+
* Generate first:
|
|
11
|
+
* node scripts/verify-runner.mjs dungeons/vertical/support-desk/support-desk.js verify-support-desk
|
|
12
|
+
* Run:
|
|
13
|
+
* node dungeons/vertical/support-desk/support-desk.verify.mjs [prefix] # default verify-support-desk
|
|
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 './support-desk.js';
|
|
22
|
+
|
|
23
|
+
const PREFIX = process.argv[2] || 'verify-support-desk';
|
|
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/support-desk/support-desk.js ${PREFIX}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log(`support-desk — 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
|
+
// Stories are emulator-backed where Mixpanel-equivalent semantics matter
|
|
55
|
+
// (topPaths for the engineered path shares, funnelFrequency with session
|
|
56
|
+
// conversion windows for the 1-vs-4 session contrast, sessionMetrics and
|
|
57
|
+
// uniques-sessions for the role cadence split). The per-role session-cadence
|
|
58
|
+
// cross-check and the identity invariants are DuckDB-backed: `role` is
|
|
59
|
+
// pinned on every event by the hook, so SQL can sessionize per user (LAG
|
|
60
|
+
// > 30 min) and split by role without needing hashFloat.
|
|
61
|
+
const results = await evaluateStories(stories, events, {
|
|
62
|
+
profiles,
|
|
63
|
+
funnels: config.funnels,
|
|
64
|
+
identityMap: buildIdentityMap(profiles),
|
|
65
|
+
runSql,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let worst = 'NAILED';
|
|
69
|
+
for (const r of results) {
|
|
70
|
+
console.log(`${r.verdict.padEnd(7)} ${r.id}`);
|
|
71
|
+
for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
|
|
72
|
+
if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
|
|
73
|
+
}
|
|
74
|
+
console.log(`\nworst verdict: ${worst}`);
|
|
75
|
+
process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);
|