@ak--47/dungeon-master 1.8.0 → 1.8.2
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 +9 -0
- package/.claude/skills/create-dungeon/SKILL.md +35 -34
- package/.claude/skills/create-project/SKILL.md +6 -0
- package/.claude/skills/headless-build/SKILL.md +21 -11
- package/.claude/skills/powertools/SKILL.md +6 -2
- package/.claude/skills/release-check/SKILL.md +27 -2
- package/.claude/skills/verify-dungeon/SKILL.md +32 -13
- package/.claude/skills/verify-dungeon/references/alignment-contract.md +110 -0
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +29 -16
- package/.claude/skills/verify-dungeon/references/report-format.md +23 -9
- package/.claude/skills/verify-dungeon/references/sql-recipes.md +135 -225
- package/.claude/skills/warehouse-metrics/SKILL.md +6 -0
- package/.claude/skills/write-hooks/SKILL.md +61 -48
- package/CHANGELOG.md +82 -0
- package/HOOKS.md +105 -47
- package/README.md +41 -1
- package/docs/guides/1.8.1-upgrade-guide.md +153 -0
- package/docs/guides/1.8.2-upgrade-guide.md +110 -0
- package/lib/generators/events.js +6 -0
- package/lib/generators/funnels.js +16 -0
- package/lib/hook-helpers/shape.js +73 -17
- package/lib/hook-patterns/attributed-by-source.js +4 -3
- package/lib/hook-patterns/funnel-frequency-breakdown.js +4 -7
- package/lib/orchestrators/user-loop.js +82 -15
- package/lib/verify/counting.js +7 -10
- package/lib/verify/emulate-breakdown.js +48 -29
- package/lib/verify/funnel-engine.js +93 -40
- package/lib/verify/identity.js +32 -9
- package/lib/verify/story-runner.js +93 -30
- package/lib/verify/verify-dungeon.js +4 -1
- package/package.json +1 -1
- package/scripts/verify-stories.mjs +3 -3
- package/types.d.ts +9 -5
|
@@ -12,26 +12,28 @@ Automatic warehouse audits run even without stories. Its history buckets can
|
|
|
12
12
|
precede the user-event window; sparse point-in-time tables intentionally omit
|
|
13
13
|
unchanged rows. Do not apply cadence or user-population counting rules to them.
|
|
14
14
|
|
|
15
|
-
# Counting
|
|
15
|
+
# Counting semantics and local proof limits
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Read the [1.8.1 verification contract](alignment-contract.md) first. The verifier
|
|
18
|
+
implements selected source-derived counting rules. Record explicit report options
|
|
19
|
+
and remaining gaps; a passing local assertion does not establish universal parity.
|
|
18
20
|
|
|
19
21
|
## Core rules
|
|
20
22
|
|
|
21
23
|
| Concept | Mixpanel rule | Wrong SQL → Right SQL |
|
|
22
24
|
|---------|--------------|----------------------|
|
|
23
|
-
| Frequency / cohort by event count |
|
|
24
|
-
| Funnels |
|
|
25
|
+
| Frequency / cohort by event count | Report-dependent: raw totals, calendar days, or rolling periods | Set the counting algorithm explicitly; distinct UTC days do not prove rolling-frequency parity |
|
|
26
|
+
| Funnels | Ordered histories, restart/shared-edge behavior, inclusive 2000ms completion grace | Use `emulateBreakdown` with explicit window, order, and reentry; local totals default to `reentry: false` |
|
|
25
27
|
| AVG / SUM / MIN / MAX | Skip null and non-numeric from BOTH num and denom (`normal_query.cpp:1718-1733`) | Always wrap in `TRY_CAST(prop AS DOUBLE)` |
|
|
26
28
|
| Attribution | Cap at 10 touchpoints in lookback (`attributed_value_reader.cpp:16` `TOUCHPOINTS_LIMIT`) | Use `emulateBreakdown` with `attributedBy` |
|
|
27
29
|
| Conversion window | Strict `<` boundary (`conversion_window.cpp:48` `t1 < t2 + 1000*len`) | Read `Funnel.conversionWindowDays` and respect it |
|
|
28
|
-
| Sessions | 3-trigger split: timeout `>`, max duration `>`, day-idx change (`session_query.cpp:906-911`) |
|
|
30
|
+
| Sessions | 3-trigger split: timeout `>`, max duration `>`, day-idx change (`session_query.cpp:906-911`) | Derive from the full resolved user stream before HPC partitioning; local UTC / 30-minute / 24-hour defaults |
|
|
29
31
|
| Retention | Birth-anchored, ms-strict gate (default `birth_can_retain=false` → `<`), bucketed by `floor((ret−birth)/unit)` (`retention_query.cpp:1097-1109,1228-1231`) | Use `emulateBreakdown` with `retention` |
|
|
30
32
|
|
|
31
33
|
**Known divergences from Mixpanel C++** (1.6.0):
|
|
32
34
|
- `countDistinctPeriods` default = `algorithm: 'calendar'` (UTC bucket).
|
|
33
35
|
Mixpanel C++ (`addiction_query.cpp:359`) uses ROLLING window. Pass
|
|
34
|
-
`algorithm: 'rolling'`
|
|
36
|
+
`algorithm: 'rolling'` when that is the intended report algorithm; validate the remaining report options separately.
|
|
35
37
|
- Touchpoint sampling: generator stamps uniform-random across user
|
|
36
38
|
lifetime; verifier reads last-N before conversion (matches C++).
|
|
37
39
|
For users with ≤10 attribution events lifetime, no divergence.
|
|
@@ -42,7 +44,10 @@ Full rules: see [HOOKS.md Section 2](../../../../HOOKS.md#2-how-mixpanel-counts-
|
|
|
42
44
|
|
|
43
45
|
## When to use the emulator vs DuckDB
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
Prefer `emulateBreakdown` from `@ak--47/dungeon-master/verify` for supported report
|
|
48
|
+
types. Inspect its options against the independent report specification. If a
|
|
49
|
+
required semantic is unsupported, report the gap; do not silently substitute SQL
|
|
50
|
+
or a different metric and call the original report verified.
|
|
46
51
|
|
|
47
52
|
Use DuckDB ONLY for:
|
|
48
53
|
- Schema integrity checks (column coverage, flag detection)
|
|
@@ -110,7 +115,9 @@ For CI-style assertions, use `verifyDungeon` with a checks array; see `tests/e2e
|
|
|
110
115
|
|
|
111
116
|
- **`Funnel.conversionWindowDays` auto-applied.** When a check's `breakdown` matches a funnel by sequence, `verifyDungeon` reads `conversionWindowDays` from the funnel config and passes it to the emulator. You do NOT need to thread `conversionWindowMs` by hand for funnels declared in the dungeon.
|
|
112
117
|
- **`Funnel.order` auto-dispatched.** For `sequential` / `interrupt` funnels, the emulator runs the greedy single-pass engine. For other order modes (`first-fixed`, `last-fixed`, `random`, etc.), it dispatches to `evaluateAnyOrderCompletion` (set-membership check). For `random` mode, results are `verificationKind: "informational"` — Mixpanel funnel shape doesn't apply; do not assert PASS/FAIL.
|
|
113
|
-
- **
|
|
118
|
+
- **SQL still needs explicit ordering.** The engine auto-sorts output by default,
|
|
119
|
+
but SQL row order is unspecified. Use `ORDER BY time` in `LAG`/`LEAD` windows
|
|
120
|
+
and define tie handling when the report depends on it.
|
|
114
121
|
- **Auto-promote `isStrictEvent` is silent healing — not a regression.** If a stale dungeon's funnel-step events also live in `events[]`, the validator stamps `isStrictEvent: true` and warns. Verification of those dungeons may show CHANGED standalone-event counts vs older runs — that is correct behavior, not a bug to chase.
|
|
115
122
|
- **Touchpoint cap = 10 enforced at generation.** `switches.hasCampaigns: true` users get up to `maxTouchpointsPerUser` (default 10) UTM-stamped events, sampled across lifetime. Attribution checks via `attributedBy` should see realistic first/last-touch shapes, not all-stamps-at-birth. The verifier's `attributedBy` reads last-N before conversion (matches Mixpanel); the generator samples uniform across lifetime. For users with >10 attribution events lifetime, expect minor divergence on multi-conversion users.
|
|
116
123
|
|
|
@@ -126,7 +133,7 @@ For CI-style assertions, use `verifyDungeon` with a checks array; see `tests/e2e
|
|
|
126
133
|
| Cohort B shows MORE absolute post-d30 events than cohort A even though hook reduces B | Cohort B has structurally higher event volume (e.g., low-balance users check balance constantly) | Compare per-user `post / pre` ratio, not raw counts |
|
|
127
134
|
| Time-window hook (rainy week, etc.) inverts when measured against full-dataset avg | Born-in-dataset ramp inflates late-window baseline | Compare against neighboring days only, not full-dataset average |
|
|
128
135
|
| Weekend-surge hook still <1.0x weekday | Default soup `dayOfWeekWeights` dampens weekends to ~0.55x weekday | Verify against soup baseline (`wknd/wkday > 0.55 × 1.2`), not >1.0 |
|
|
129
|
-
| Funnel-post TTC scaling
|
|
136
|
+
| Funnel-post TTC scaling does not move the report | Completed histories can combine instances or restart; the scaled run may not be the reported history | Inspect completed histories under explicit options and compare paired baseline/treatment; do not accept by code inspection |
|
|
130
137
|
| Hook references `profile.X` that's not a defined userProp | Validator doesn't catch undeclared profile reads — `X` resolves to `undefined` | Verify by data SPREAD (max/min, cv) instead of segment correlation |
|
|
131
138
|
| Two `everything` hooks where one injects events the other mutates produce wrong ratios | Hook ordering matters — injection hook ran AFTER cohort-shaping hook | Run cohort-degrading hooks AFTER all injection hooks in same `everything` block |
|
|
132
139
|
| `readFileSync` ENOMEM on shards >500MB | Node string cap at ~512MB | Stream-load with `readline.createInterface` over `data/PREFIX-EVENTS*.json` glob |
|
|
@@ -140,7 +147,7 @@ When writing per-dungeon verify scripts, follow the template in HOOKS.md §9.9.
|
|
|
140
147
|
|
|
141
148
|
If a dungeon's frequency / funnel / TTC pattern shows WEAK or NONE in verification, check these BEFORE concluding the hook is broken:
|
|
142
149
|
|
|
143
|
-
1. **Did you
|
|
150
|
+
1. **Did you choose the report's counting unit?** Raw totals, distinct UTC days, and rolling periods answer different questions; set the algorithm explicitly.
|
|
144
151
|
2. **Did you hand-roll funnel SQL?** Self-joins find the optimal match; Mixpanel uses greedy. Always use `emulateBreakdown` for funnels.
|
|
145
152
|
3. **Are you including step events at the conversion-window boundary?** Mixpanel uses strict `<`. An event exactly at the boundary is excluded.
|
|
146
153
|
4. **Did the hook scale event count without spreading across days?** `scaleEventCount(events, 'X', 3)` clones at sub-second offsets — same day. Frequency reports show ZERO movement. Use `injectOnNewDays`.
|
|
@@ -175,12 +182,13 @@ When the dungeon's `Funnel` config sets these fields, `verifyDungeon` auto-appli
|
|
|
175
182
|
| `exclusionEvents: string[]` | Wraps as `exclusionSteps: [{ event }]` and terminates the funnel attempt |
|
|
176
183
|
| `stepFilters: { N: { prop, op, value } }` | Mutates `breakdownArgs.steps[N]` to attach the `where` clause |
|
|
177
184
|
|
|
178
|
-
## Identity-model dungeons
|
|
185
|
+
## Identity-model dungeons: profiles and emitted links
|
|
179
186
|
|
|
180
187
|
When `identity.avgDevicePerUser > 0` (or the deprecated `hasAnonIds: true`),
|
|
181
|
-
|
|
182
|
-
`
|
|
183
|
-
|
|
188
|
+
pass `profiles` for profile-property segmentation. For identity proof, build an
|
|
189
|
+
explicit `identityMap` from valid ordinary both-ID events in the emitted stream.
|
|
190
|
+
Profile device pools alone do not establish a link. Later ordinary Login events
|
|
191
|
+
can link earlier device-only rows retrospectively; report conflicts and ID limits.
|
|
184
192
|
|
|
185
193
|
**v1.5.1 anonymous non-converter semantic:** born-in-dataset users who
|
|
186
194
|
never reach an `isAuthEvent` get `_drop: true` on their profile.
|
|
@@ -197,11 +205,16 @@ emulateBreakdown(events, {
|
|
|
197
205
|
type: 'funnelFrequency',
|
|
198
206
|
steps: ['visit_landing', 'sign_up', 'first_action'],
|
|
199
207
|
breakdownByFrequencyOf: 'visit_landing',
|
|
200
|
-
profiles,
|
|
208
|
+
profiles,
|
|
209
|
+
identityMap, // prepared from emitted both-ID evidence for this run
|
|
201
210
|
});
|
|
202
211
|
```
|
|
203
212
|
|
|
204
|
-
|
|
213
|
+
In 1.8.2, automatic mapping uses emitted both-ID records before report filtering.
|
|
214
|
+
The public `buildIdentityMap(profiles)` helper still reads profile pools when a
|
|
215
|
+
caller explicitly requests that override. It cannot prove that a link survived
|
|
216
|
+
generation or ingest. Imported historical identity can become query-visible later;
|
|
217
|
+
verify readiness before accepting a local/live comparison.
|
|
205
218
|
|
|
206
219
|
## Time-series verification (timeBucket)
|
|
207
220
|
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Templates and conventions for writing `hook-results.md` and per-dungeon verification SQL. For user dungeons these live in the dungeon's folder (`dungeons/user/<name>/`); otherwise in `./research/`. See [SKILL.md "Artifact location"](../SKILL.md).
|
|
4
4
|
|
|
5
|
+
Apply the [1.8.1 verification contract](alignment-contract.md). Keep the runner's
|
|
6
|
+
computed verdict, semantic correctness, and evidence sufficiency in separate
|
|
7
|
+
columns. Record the independent report definition, explicit options, baseline and
|
|
8
|
+
neutral-control measurements, eligible users/converters, and source-derived scope.
|
|
9
|
+
`INSUFFICIENT_EVIDENCE` is an unresolved acceptance status, not a new runner tier
|
|
10
|
+
and not a pass or measured effect failure. Preserve actual deployment reports;
|
|
11
|
+
offline and dry-run results must not replace live outcomes.
|
|
12
|
+
|
|
5
13
|
## Verdict criteria (5-tier)
|
|
6
14
|
|
|
7
15
|
Every report records the exact data prefix and retained artifact paths. Separate
|
|
@@ -25,11 +33,13 @@ Verdicts are **mechanical** — computed by `scripts/verify-stories.mjs` from ea
|
|
|
25
33
|
|
|
26
34
|
- **NAILED** — observed within ±10% of `target`.
|
|
27
35
|
- **STRONG** — passes `floor` (or `target` when no floor is declared).
|
|
28
|
-
- **WEAK**
|
|
36
|
+
- **WEAK** - fails `floor` but effect direction is correct, or a supported selected-row population field is below `minCohort`. Inspect actual independent users and converters separately; bespoke denominators may not be guarded.
|
|
29
37
|
- **NONE** — no measurable effect, or the selection matched no rows.
|
|
30
38
|
- **INVERSE** — effect direction is opposite the assertion.
|
|
31
39
|
|
|
32
|
-
Story verdict = worst assertion verdict. NAILED and STRONG
|
|
40
|
+
Story verdict = worst assertion verdict. NAILED and STRONG pass the mechanical
|
|
41
|
+
gate; WEAK, NONE, and INVERSE fail it. Acceptance also requires correct report
|
|
42
|
+
semantics, neutral controls, and sufficient evidence, even for a passing target.
|
|
33
43
|
|
|
34
44
|
Hand-assigned verdicts appear only in the legacy no-stories fallback and MUST follow the same definitions: derive a target from the hook's knob constants, compute the band the observed value lands in, and state the derivation in the detail block — never assign a tier by feel.
|
|
35
45
|
|
|
@@ -46,7 +56,10 @@ The summary table should also be sorted this way (INVERSE → NONE → WEAK →
|
|
|
46
56
|
|
|
47
57
|
## Single-dungeon report structure
|
|
48
58
|
|
|
49
|
-
For story-backed dungeons,
|
|
59
|
+
For story-backed dungeons, render the runner's JSON without changing computed
|
|
60
|
+
verdicts. Add semantic and evidence status for every story. Include detailed
|
|
61
|
+
blocks for misses, bespoke SQL, legacy hooks, and semantic or evidence gaps,
|
|
62
|
+
including those found in mechanically passing stories.
|
|
50
63
|
|
|
51
64
|
```markdown
|
|
52
65
|
# Dungeon Verification Report
|
|
@@ -62,15 +75,15 @@ For story-backed dungeons, `hook-results.md` **renders the runner's JSON**: run
|
|
|
62
75
|
| purchase | (none) | — | SCHEMA-PASS |
|
|
63
76
|
| page view | (none) | — | SCHEMA-PASS |
|
|
64
77
|
|
|
65
|
-
<
|
|
78
|
+
<fail every undeclared column, even at 100% coverage; list remediation here>
|
|
66
79
|
|
|
67
80
|
## Hook Summary
|
|
68
81
|
|
|
69
|
-
| # | Hook Name |
|
|
70
|
-
|
|
71
|
-
| 3 | ... |
|
|
72
|
-
| 2 | ... |
|
|
73
|
-
| 1 | ... |
|
|
82
|
+
| # | Hook Name | Expected | Observed | Runner Verdict | Semantics | Evidence |
|
|
83
|
+
|---|-----------|----------|----------|----------------|-----------|----------|
|
|
84
|
+
| 3 | ... | ... | ... | INVERSE | MATCH | SUFFICIENT |
|
|
85
|
+
| 2 | ... | ... | ... | WEAK | MATCH | INSUFFICIENT_EVIDENCE |
|
|
86
|
+
| 1 | ... | ... | ... | NAILED | MATCH | SUFFICIENT |
|
|
74
87
|
|
|
75
88
|
## Detailed Results
|
|
76
89
|
|
|
@@ -141,6 +154,7 @@ When verifying multiple dungeons, use this consolidated structure. Each dungeon
|
|
|
141
154
|
**Key rules for multi-dungeon reports:**
|
|
142
155
|
- The overall summary table at the top shows pass/weak/fail counts per dungeon, sorted with most failures first
|
|
143
156
|
- Each dungeon section is self-contained with its own summary, details, and recommendations
|
|
157
|
+
- Include the same semantic and evidence columns used in the single-dungeon template; report insufficient-evidence counts separately from runner tiers.
|
|
144
158
|
- Dungeon sections are ordered by failure count descending (most problems first)
|
|
145
159
|
- Use the dungeon filename (without path) as the section header for clarity
|
|
146
160
|
|