@ssheleg/sheleg-dev 0.6.0 → 0.8.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 (38) hide show
  1. package/CHANGELOG.md +318 -0
  2. package/README.md +115 -1
  3. package/SECURITY.md +199 -52
  4. package/bin/sheleg-dev.js +18 -0
  5. package/package.json +2 -2
  6. package/plugins/sheleg-dev/.claude-plugin/plugin.json +1 -1
  7. package/plugins/sheleg-dev/hooks/hooks.json +29 -0
  8. package/plugins/sheleg-dev/hooks/lib/moneygate.js +587 -0
  9. package/plugins/sheleg-dev/hooks/money-gate.js +68 -0
  10. package/plugins/sheleg-dev/skills/ad-tracking/SKILL.md +7 -3
  11. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/README.md +77 -0
  12. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/assert-dedup-contract.mjs +397 -0
  13. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/manifest.json +75 -0
  14. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-capi-from-thank-you-page.json +32 -0
  15. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-capi-server.json +26 -0
  16. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-pixel-browser.json +12 -0
  17. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/reference-emitter.mjs +129 -0
  18. package/plugins/sheleg-dev/skills/ad-tracking/references/meta-linkedin.md +17 -1
  19. package/plugins/sheleg-dev/skills/crypto-payments/SKILL.md +82 -77
  20. package/plugins/sheleg-dev/skills/crypto-payments/references/callback-route-hardening.md +55 -0
  21. package/plugins/sheleg-dev/skills/crypto-payments/references/heleket-provider.md +349 -10
  22. package/plugins/sheleg-dev/skills/crypto-payments/references/testing-and-local-dev.md +60 -0
  23. package/plugins/sheleg-dev/skills/stripe-billing/SKILL.md +1 -1
  24. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/README.md +111 -0
  25. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/assert-money-invariants.mjs +564 -0
  26. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/charge-refunded-partial.json +52 -0
  27. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/charge-refunded-remainder.json +63 -0
  28. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-async-payment-failed.json +43 -0
  29. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-completed-async-unpaid.json +43 -0
  30. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-completed-paid.json +42 -0
  31. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle-next-period.json +70 -0
  32. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle-redelivery.json +70 -0
  33. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle.json +61 -0
  34. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-update-proration.json +90 -0
  35. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/manifest.json +172 -0
  36. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/reference-handler.mjs +315 -0
  37. package/plugins/sheleg-dev/skills/stripe-billing/references/testing-and-local-dev.md +39 -2
  38. package/plugins/sheleg-dev/skills/stripe-billing/references/webhook-events.md +43 -1
@@ -0,0 +1,564 @@
1
+ #!/usr/bin/env node
2
+ // The assertions a correct Stripe webhook handler must pass, and the proof that each one
3
+ // can fail.
4
+ //
5
+ // node assert-money-invariants.mjs # run them against the reference handler
6
+ // node assert-money-invariants.mjs --self-test # break the handler one rule at a time
7
+ //
8
+ // `--self-test` is the half that makes this evidence rather than decoration. For every
9
+ // invariant it names the rule whose removal MUST turn that invariant red, deletes exactly
10
+ // that rule, and fails if the assertion still passes. A check nobody has watched failing
11
+ // is indistinguishable from a check that cannot fail.
12
+ //
13
+ // It also prints the whole invariant x mutant matrix, because two rules that both explain
14
+ // a passing fixture mean neither is tested. Where `breaks` holds one rule, that fixture
15
+ // isolates it. Where it holds more, the overlap is written down instead of assumed away
16
+ // (`README.md` -> *What masks what*).
17
+ //
18
+ // To point this at YOUR handler: replace `createStore`/`createHandler` below with your
19
+ // own, keep the same three entry points (`deliver(event)`, `reconcile(...)`, and a store
20
+ // you can read counts off), and delete the `--self-test` block only after you have your
21
+ // own way of watching each assertion fail.
22
+ //
23
+ // No network, no filesystem writes, no clock. The only reads are the JSON fixtures beside
24
+ // this file.
25
+
26
+ import { readFileSync } from 'node:fs';
27
+ import { fileURLToPath } from 'node:url';
28
+ import path from 'node:path';
29
+ import assert from 'node:assert/strict';
30
+ import {
31
+ createStore, createHandler, subscriptionIdOf, RULES, CREDITS_PER_PERIOD,
32
+ } from './reference-handler.mjs';
33
+
34
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
35
+ const fixture = (name) => JSON.parse(readFileSync(path.join(HERE, name), 'utf8'));
36
+
37
+ const CYCLE_JAN = 'invoice-paid-subscription-cycle.json';
38
+ const CYCLE_JAN_REDELIVERY = 'invoice-paid-subscription-cycle-redelivery.json';
39
+ const CYCLE_FEB = 'invoice-paid-subscription-cycle-next-period.json';
40
+ const PRORATION = 'invoice-paid-subscription-update-proration.json';
41
+ const REFUND_PARTIAL = 'charge-refunded-partial.json';
42
+ const REFUND_REMAINDER = 'charge-refunded-remainder.json';
43
+ const SESSION_UNPAID = 'checkout-session-completed-async-unpaid.json';
44
+ const SESSION_FAILED = 'checkout-session-async-payment-failed.json';
45
+ const SESSION_PAID = 'checkout-session-completed-paid.json';
46
+
47
+ /** A fresh store and handler per invariant: no assertion inherits another's state. */
48
+ function harness(without = []) {
49
+ const store = createStore();
50
+ const handler = createHandler(store, { without });
51
+ return { store, handler };
52
+ }
53
+
54
+ function seedOneOffPurchase(store) {
55
+ // The reader's own database row for a $90 credit pack, bought before the refunds.
56
+ store.purchases.set('pi_PLACEHOLDER_creditpack', { amount: 9000, refundedTotal: 0 });
57
+ }
58
+
59
+ function periodOfCycle(event) {
60
+ const line = event.data.object.lines.data[0];
61
+ return { start: line.period.start, end: line.period.end };
62
+ }
63
+
64
+ // ---------------------------------------------------------------------- invariants
65
+ //
66
+ // `breaks` is a MEASURED list, not an intention: `--self-test` fails if the set of
67
+ // mutants that turn an invariant red is anything other than this.
68
+
69
+ export const INVARIANTS = [
70
+ {
71
+ id: 'renewal-grants-exactly-once',
72
+ fixtures: [CYCLE_JAN],
73
+ states: 'a paid renewal becomes exactly one grant, and the mirror carries its period',
74
+ breaks: ['grant-on-renewal'],
75
+ async run({ store, handler, assert }) {
76
+ const event = fixture(CYCLE_JAN);
77
+ const response = await handler.deliver(event);
78
+ // `unmutated`: no rule in RULES makes this route answer anything but 200 with that
79
+ // body, so neither assertion has ever been watched failing. They pin the envelope a
80
+ // reader copies; they are not evidence, and the verdict line counts them apart.
81
+ assert.unmutated.equal(response.status, 200);
82
+ assert.unmutated.deepEqual(response.body, { received: true });
83
+ assert.equal(store.grants.length, 1, 'a paid renewal granted nothing');
84
+ assert.equal(store.credits.get('usr_PLACEHOLDER_alice'), CREDITS_PER_PERIOD);
85
+ // `|| {}` on purpose: with no mirror row at all these two assertions must FAIL
86
+ // rather than throw a TypeError on the line above them. A throw aborts the run, and
87
+ // an assertion the runner never reached is one no mutant has been watched breaking.
88
+ const mirror = store.subscriptions.get('sub_PLACEHOLDER_alice') || {};
89
+ assert.equal(mirror.periodStart, 1767225600);
90
+ assert.equal(mirror.periodEnd, 1769904000, 'the period is not the line period');
91
+ },
92
+ },
93
+ {
94
+ id: 'proration-invoice-grants-nothing',
95
+ fixtures: [CYCLE_JAN, PRORATION],
96
+ states: 'billing_reason subscription_update is a $40 proration, not a month of product',
97
+ breaks: ['billing-reason', 'grant-on-renewal'],
98
+ async run({ store, handler, assert }) {
99
+ await handler.deliver(fixture(CYCLE_JAN));
100
+ const response = await handler.deliver(fixture(PRORATION));
101
+ // unmutated: nothing in RULES makes the endpoint answer 500 for a proration.
102
+ assert.unmutated.equal(response.status, 200, 'a proration invoice must not fail the endpoint');
103
+ assert.equal(store.grants.length, 1,
104
+ 'the proration granted a second period — a user who adds and removes a seat '
105
+ + 'four times has been given four months of product');
106
+ assert.ok(
107
+ store.log.some((l) => l.decision === 'skipped: subscription_update'),
108
+ 'nothing recorded WHY the proration was skipped',
109
+ );
110
+ },
111
+ },
112
+ {
113
+ id: 'sequential-redelivery-grants-once',
114
+ fixtures: [CYCLE_JAN, CYCLE_JAN_REDELIVERY],
115
+ states: 'the same evt_ delivered twice grants once AND runs its side effects once',
116
+ breaks: ['claim', 'grant-on-renewal'],
117
+ async run({ store, handler, assert }) {
118
+ const first = await handler.deliver(fixture(CYCLE_JAN));
119
+ const second = await handler.deliver(fixture(CYCLE_JAN_REDELIVERY));
120
+ assert.unmutated.deepEqual(first.body, { received: true }); // the envelope, again
121
+ assert.deepEqual(second.body, { received: true, duplicate: true },
122
+ 'the second delivery was not recognised as a duplicate');
123
+ assert.equal(store.grants.length, 1, 'the redelivery granted a second time');
124
+ assert.equal(store.notifications.length, 1, '"your renewal" was sent twice');
125
+ assert.equal(store.conversions.length, 1,
126
+ 'the server-side conversion fired twice — the revenue is counted twice');
127
+ },
128
+ },
129
+ {
130
+ id: 'concurrent-redelivery-grants-once',
131
+ fixtures: [CYCLE_JAN, CYCLE_JAN_REDELIVERY],
132
+ states: 'two deliveries in flight at once grant once — the claim is an INSERT, not a SELECT',
133
+ breaks: ['claim', 'grant-on-renewal'],
134
+ async run({ store, handler, assert }) {
135
+ // 40 ms apart in production; here, both before either has finished its first read.
136
+ // This is the fixture that separates the event claim from the grant marker: the
137
+ // marker reads before it writes, and a read is a round trip.
138
+ const [a, b] = await Promise.all([
139
+ handler.deliver(fixture(CYCLE_JAN)),
140
+ handler.deliver(fixture(CYCLE_JAN_REDELIVERY)),
141
+ ]);
142
+ const bodies = [a.body, b.body];
143
+ assert.equal(store.grants.length, 1, 'both deliveries granted');
144
+ assert.equal(store.credits.get('usr_PLACEHOLDER_alice'), CREDITS_PER_PERIOD,
145
+ 'the user was credited twice for one renewal');
146
+ assert.equal(store.conversions.length, 1, 'the conversion fired twice');
147
+ assert.equal(bodies.filter((x) => x.duplicate === true).length, 1,
148
+ 'exactly one of the two deliveries must answer duplicate');
149
+ },
150
+ },
151
+ {
152
+ id: 'reconciliation-does-not-regrant',
153
+ fixtures: [CYCLE_JAN],
154
+ states: 'the nightly repair reuses the webhook marker — a nightly job is not a nightly gift',
155
+ breaks: ['grant-marker', 'grant-on-renewal'],
156
+ async run({ store, handler, assert }) {
157
+ const event = fixture(CYCLE_JAN);
158
+ await handler.deliver(event);
159
+ const subId = subscriptionIdOf(event.data.object);
160
+ const outcome = await handler.reconcile(subId, periodOfCycle(event), {
161
+ userId: 'usr_PLACEHOLDER_alice',
162
+ });
163
+ assert.equal(outcome.granted, false, 'reconciliation granted a period the webhook granted');
164
+ assert.equal(store.grants.length, 1);
165
+ assert.equal(store.credits.get('usr_PLACEHOLDER_alice'), CREDITS_PER_PERIOD);
166
+ },
167
+ },
168
+ {
169
+ id: 'out-of-order-pair-does-not-rewind-state',
170
+ fixtures: [CYCLE_FEB, CYCLE_JAN],
171
+ states: 'February delivered before January leaves February in the mirror, and grants both',
172
+ breaks: ['ordering', 'grant-on-renewal'],
173
+ async run({ store, handler, assert }) {
174
+ await handler.deliver(fixture(CYCLE_FEB)); // arrives first
175
+ await handler.deliver(fixture(CYCLE_JAN)); // the earlier period, delivered late
176
+ assert.equal(store.grants.length, 2, 'both periods were paid, so both are owed');
177
+ const mirror = store.subscriptions.get('sub_PLACEHOLDER_alice') || {};
178
+ assert.equal(mirror.periodStart, 1769904000,
179
+ 'the mirror was rewound to the older period — state derived from arrival order');
180
+ assert.equal(mirror.periodEnd, 1772323200,
181
+ 'the renewal date now points at a period that already ended');
182
+ },
183
+ },
184
+ {
185
+ id: 'refund-total-is-cumulative',
186
+ fixtures: [REFUND_PARTIAL, REFUND_REMAINDER],
187
+ states: 'amount_refunded is the total so far: two partials claw back $90, not $130',
188
+ breaks: ['cumulative-refund'],
189
+ async run({ store, handler, assert }) {
190
+ seedOneOffPurchase(store);
191
+ await handler.deliver(fixture(REFUND_PARTIAL));
192
+ await handler.deliver(fixture(REFUND_REMAINDER));
193
+ const total = store.clawbacks.reduce((sum, c) => sum + c.amount, 0);
194
+ assert.equal(total, 9000,
195
+ `clawed back ${total} minor units against a 9000 charge — amount_refunded was read `
196
+ + 'as an increment');
197
+ assert.deepEqual(store.clawbacks.map((c) => c.amount), [4000, 5000]);
198
+ assert.equal(store.purchases.get('pi_PLACEHOLDER_creditpack').refundedTotal, 9000);
199
+ },
200
+ },
201
+ {
202
+ id: 'sequential-redelivery-grants-once-by-count-alone',
203
+ fixtures: [CYCLE_JAN, CYCLE_JAN_REDELIVERY],
204
+ states: 'the same pair judged ONLY by the grant count — masked, and here to prove it',
205
+ // Deliberately weaker than `sequential-redelivery-grants-once`, and kept because the
206
+ // measurement is the finding: no SINGLE removed rule turns this red. Delete the event
207
+ // claim and the per-period marker still refuses the second grant; delete the marker
208
+ // and the claim still refuses the second delivery. A suite that judged a redelivery by
209
+ // its grant count alone would ship with no idempotency and stay green.
210
+ breaks: [['claim', 'grant-marker'], 'grant-on-renewal'],
211
+ async run({ store, handler, assert }) {
212
+ await handler.deliver(fixture(CYCLE_JAN));
213
+ await handler.deliver(fixture(CYCLE_JAN_REDELIVERY));
214
+ assert.equal(store.grants.length, 1, 'the redelivery granted a second time');
215
+ },
216
+ },
217
+ {
218
+ id: 'duplicate-refund-claws-back-once',
219
+ fixtures: [REFUND_PARTIAL],
220
+ states: 'the same charge.refunded twice claws back $40 once — also masked',
221
+ // The second masked pair, and the reason the claim's own discriminating fixture is the
222
+ // CONCURRENT delivery rather than any duplicate: the compare-and-swap's
223
+ // `increment <= 0` catches a sequential replay by itself, and the claim catches it
224
+ // without the swap. Neither is tested by this fixture alone.
225
+ breaks: [['claim', 'cumulative-refund']],
226
+ async run({ store, handler, assert }) {
227
+ seedOneOffPurchase(store);
228
+ await handler.deliver(fixture(REFUND_PARTIAL));
229
+ await handler.deliver(fixture(REFUND_PARTIAL));
230
+ const total = store.clawbacks.reduce((sum, c) => sum + c.amount, 0);
231
+ assert.equal(total, 4000, `clawed back ${total} for one $40 refund`);
232
+ },
233
+ },
234
+ {
235
+ id: 'unpaid-session-grants-nothing',
236
+ fixtures: [SESSION_UNPAID, SESSION_FAILED],
237
+ states: 'the redirect proves a browser: an unpaid async session grants and converts nothing',
238
+ breaks: ['async-gate'],
239
+ async run({ store, handler, assert }) {
240
+ const completed = await handler.deliver(fixture(SESSION_UNPAID));
241
+ // unmutated: the envelope. Removing `async-gate` grants the session and still answers 200.
242
+ assert.unmutated.equal(completed.status, 200, 'an unpaid session must still answer 200');
243
+ assert.equal(store.grants.length, 0,
244
+ 'a session whose payment_status is unpaid was granted — the money has not moved');
245
+ // unmutated, and the reason is a gap worth seeing: this handler emits conversions on
246
+ // `invoice.paid` only, so no rule here can make a checkout session report one. The
247
+ // assertion states the rule for a reader whose handler DOES emit from a session.
248
+ assert.unmutated.equal(store.conversions.length, 0,
249
+ 'a purchase conversion was reported for a charge that has not cleared');
250
+ await handler.deliver(fixture(SESSION_FAILED));
251
+ assert.equal(store.grants.length, 0, 'the failed async payment still granted');
252
+ assert.unmutated.equal(store.conversions.length, 0); // same gap as above
253
+ assert.equal(store.credits.get('usr_PLACEHOLDER_bob'), undefined);
254
+ },
255
+ },
256
+ {
257
+ id: 'paid-session-grants-once',
258
+ fixtures: [SESSION_PAID],
259
+ states: 'the positive control: a card session that DID clear grants exactly once',
260
+ // Without it, a handler that refuses every checkout session satisfies the assertion
261
+ // above and this pack would have taught the opposite defect.
262
+ breaks: ['grant-on-checkout'],
263
+ async run({ store, handler, assert }) {
264
+ const response = await handler.deliver(fixture(SESSION_PAID));
265
+ assert.unmutated.deepEqual(response.body, { received: true }); // the envelope
266
+ assert.equal(store.grants.length, 1, 'a cleared card session granted nothing');
267
+ assert.equal(store.credits.get('usr_PLACEHOLDER_carol'), CREDITS_PER_PERIOD);
268
+ assert.ok(store.subscriptions.has('sub_PLACEHOLDER_carol'));
269
+ },
270
+ },
271
+ {
272
+ id: 'conversion-id-survives-the-session',
273
+ fixtures: [CYCLE_JAN],
274
+ states: 'the conversion id comes from subscription metadata, so browser and server share it',
275
+ breaks: ['conversion-id-from-metadata', 'grant-on-renewal'],
276
+ async run({ store, handler, assert }) {
277
+ await handler.deliver(fixture(CYCLE_JAN));
278
+ assert.equal(store.conversions.length, 1);
279
+ const [conversion = {}] = store.conversions;
280
+ assert.equal(conversion.eventId, 'evtid_PLACEHOLDER_purchase_jan',
281
+ 'the server generated its own id — nothing will ever deduplicate against the pixel');
282
+ assert.equal(conversion.eventName, 'Purchase', 'the event name is not byte-equal');
283
+ assert.equal(conversion.source, 'webhook');
284
+ assert.equal(conversion.value, 29);
285
+ assert.equal(conversion.currency, 'USD');
286
+ },
287
+ },
288
+ ];
289
+
290
+ // ------------------------------------------------- one assertion, one measurement
291
+ //
292
+ // Why the assertions are not called straight through `node:assert`. Until 2026-08-20 the
293
+ // runner returned on the FIRST throw and the matrix compared one row per invariant, so an
294
+ // invariant was measured as a single unit: any assertion that was not the sole
295
+ // discriminator could be replaced by `assert.ok(true)` and nothing went red. Measured that
296
+ // day — three neutered assertions (the `clawbacks.map` deepEqual, the `mirror.periodEnd`
297
+ // equality, and the `response.body` deepEqual) left `--self-test` at exit 0 still printing
298
+ // *"12 assertions, each watched failing"*, and `npm test` at 0 with them.
299
+ //
300
+ // So the `assert` an invariant receives is a RECORDER: same API, but a failure is
301
+ // remembered instead of thrown and the assertions after it still run. Each call site is one
302
+ // assertion with its own identity — its line in this file — and `--self-test` asks the
303
+ // question per assertion rather than per invariant.
304
+ //
305
+ // Two kinds, and the difference is declared at the call site rather than in a table:
306
+ //
307
+ // assert.equal(...) a DISCRIMINATING assertion. At least one mutant must turn it
308
+ // red, or it is not evidence of anything.
309
+ // assert.unmutated.equal() an assertion NO mutant in this pack varies. It pins the
310
+ // envelope or a field the rules do not touch, so it has never
311
+ // been watched failing and the verdict line counts it apart
312
+ // rather than as evidence. Measured, not asserted: the self-test
313
+ // fails if a mutant does break one, because that is a
314
+ // discriminator hiding in the wrong list.
315
+ //
316
+ // A reader pointing this at their own handler keeps writing `assert.equal`; the recorder is
317
+ // API-compatible with `node:assert/strict` for the methods below.
318
+
319
+ const SELF = import.meta.url;
320
+ const METHODS = ['equal', 'notEqual', 'deepEqual', 'notDeepEqual', 'ok', 'match', 'doesNotMatch'];
321
+
322
+ /**
323
+ * The line in THIS file that called the recorder.
324
+ *
325
+ * `captureStackTrace(holder, boundary)` drops `boundary` and everything above it, so the
326
+ * top frame is the assertion's own line — no frame counting, which is what makes the
327
+ * identity survive a refactor of the recorder itself.
328
+ */
329
+ function siteOf(boundary) {
330
+ const holder = {};
331
+ Error.captureStackTrace(holder, boundary);
332
+ const frame = (holder.stack || '').split('\n').slice(1).find((f) => f.includes(SELF));
333
+ const m = frame && frame.match(/:(\d+):\d+\)?\s*$/);
334
+ return m ? Number(m[1]) : 0;
335
+ }
336
+
337
+ function recorder() {
338
+ const seen = new Map(); // line -> { site, unmutated, ok, why }
339
+
340
+ function record(site, unmutated, name, args) {
341
+ let ok = true;
342
+ let why = null;
343
+ try {
344
+ assert[name](...args);
345
+ } catch (error) {
346
+ ok = false;
347
+ why = error.message.split('\n')[0];
348
+ }
349
+ const prev = seen.get(site);
350
+ if (prev) {
351
+ prev.ok = prev.ok && ok;
352
+ prev.why = prev.why || why;
353
+ } else {
354
+ seen.set(site, { site, unmutated, ok, why });
355
+ }
356
+ }
357
+
358
+ const bind = (unmutated) => {
359
+ const api = {};
360
+ for (const name of METHODS) {
361
+ const call = (...args) => record(siteOf(call), unmutated, name, args);
362
+ api[name] = call;
363
+ }
364
+ return api;
365
+ };
366
+
367
+ const api = bind(false);
368
+ api.unmutated = bind(true);
369
+ return { api, seen };
370
+ }
371
+
372
+ // ------------------------------------------------------------------------- runners
373
+
374
+ async function runOne(invariant, without) {
375
+ const { api, seen } = recorder();
376
+ const { store, handler } = harness(without);
377
+ let threw = null;
378
+ try {
379
+ await invariant.run({ store, handler, assert: api });
380
+ } catch (error) {
381
+ // A real error — a `TypeError` on a row the mutant never wrote, say. It aborts the
382
+ // remaining assertions, and it is still the invariant going red.
383
+ threw = error.message.split('\n')[0];
384
+ }
385
+ const sites = [...seen.values()];
386
+ return {
387
+ threw,
388
+ sites,
389
+ failed: sites.filter((s) => !s.ok),
390
+ broke: Boolean(threw) || sites.some((s) => !s.ok),
391
+ };
392
+ }
393
+
394
+ async function runAll() {
395
+ let failed = 0;
396
+ let asserted = 0;
397
+ for (const invariant of INVARIANTS) {
398
+ const result = await runOne(invariant, []);
399
+ asserted += result.sites.length;
400
+ if (result.broke) {
401
+ failed += 1;
402
+ console.error(`FAIL ${invariant.id}`);
403
+ for (const s of result.failed) console.error(` :${s.site} ${s.why}`);
404
+ if (result.threw) console.error(` threw: ${result.threw}`);
405
+ } else {
406
+ console.log(`pass ${invariant.id} (${result.sites.length} assertions) — ${invariant.states}`);
407
+ }
408
+ }
409
+ if (failed) {
410
+ console.error(`\n${failed} of ${INVARIANTS.length} money invariants failed`);
411
+ return 1;
412
+ }
413
+ console.log(`\nOK: ${INVARIANTS.length} money invariants hold against the handler `
414
+ + `(${asserted} assertions)`);
415
+ return 0;
416
+ }
417
+
418
+ /**
419
+ * The broadest mutant: a handler that grants nothing at all. It breaks every assertion
420
+ * about a grant, so it is excluded when asking which fixture ISOLATES a rule — otherwise
421
+ * it would appear beside every answer and hide the ones that matter.
422
+ */
423
+ const BROAD = ['grant-on-renewal'];
424
+
425
+ const key = (mutant) => [...mutant].sort().join('+');
426
+
427
+ async function selfTest() {
428
+ const problems = [];
429
+
430
+ // Every single rule, plus each multi-rule mutant some invariant declares. A combination
431
+ // exists only where the measurement forced it: where two rules cover each other, no
432
+ // single removal turns the assertion red, and pretending otherwise is the failure mode
433
+ // this block was written to make visible.
434
+ const combos = [];
435
+ for (const invariant of INVARIANTS) {
436
+ for (const declared of invariant.breaks) {
437
+ if (!Array.isArray(declared)) continue;
438
+ if (!combos.some((c) => key(c) === key(declared))) combos.push(declared);
439
+ }
440
+ }
441
+ const mutants = [...RULES.map((rule) => [rule]), ...combos];
442
+
443
+ const matrix = [];
444
+ const coverage = []; // one row per ASSERTION: which mutants turn that call site red
445
+ for (const invariant of INVARIANTS) {
446
+ // The clean run is the census: it names every assertion this invariant executes, and
447
+ // whether each is declared discriminating or shape. An invariant that records nothing
448
+ // is one whose `run` never took the recorder — the failure mode of the injection.
449
+ const clean = await runOne(invariant, []);
450
+ if (clean.broke) {
451
+ problems.push(`${invariant.id}: fails against the unmutated handler `
452
+ + `(${(clean.failed[0] && `:${clean.failed[0].site} ${clean.failed[0].why}`) || clean.threw})`);
453
+ }
454
+ if (!clean.sites.length) {
455
+ problems.push(`${invariant.id}: recorded no assertion — its run() does not take the `
456
+ + '`assert` the runner passes it, so nothing about it is measured');
457
+ }
458
+ const broken = new Map(clean.sites.map((s) => [s.site, []]));
459
+
460
+ const singles = [];
461
+ for (const rule of RULES) {
462
+ const r = await runOne(invariant, [rule]);
463
+ if (r.broke) singles.push(rule);
464
+ for (const s of r.failed) if (broken.has(s.site)) broken.get(s.site).push(rule);
465
+ }
466
+ // Minimal mutants only: a combination containing a rule that already breaks the
467
+ // assertion on its own says nothing new.
468
+ const minimalCombos = [];
469
+ for (const combo of combos) {
470
+ if (combo.some((rule) => singles.includes(rule))) continue;
471
+ const r = await runOne(invariant, combo);
472
+ if (r.broke) minimalCombos.push(combo);
473
+ for (const s of r.failed) if (broken.has(s.site)) broken.get(s.site).push(key(combo));
474
+ }
475
+ const broke = [...singles.map((r) => [r]), ...minimalCombos];
476
+ matrix.push({ id: invariant.id, broke, singles });
477
+
478
+ for (const site of clean.sites) {
479
+ coverage.push({
480
+ invariant: invariant.id,
481
+ site: site.site,
482
+ unmutated: site.unmutated,
483
+ by: broken.get(site.site) || [],
484
+ });
485
+ }
486
+
487
+ const expected = invariant.breaks
488
+ .map((b) => key(Array.isArray(b) ? b : [b])).sort().join(', ');
489
+ const measured = broke.map(key).sort().join(', ');
490
+ if (expected !== measured) {
491
+ problems.push(
492
+ `${invariant.id}: declares breaks [${expected || '-'}], measured [${measured || '-'}]`,
493
+ );
494
+ }
495
+ if (broke.length === 0) {
496
+ problems.push(`${invariant.id}: no mutant breaks it — the assertion cannot fail`);
497
+ }
498
+ }
499
+
500
+ const width = Math.max(...matrix.map((row) => row.id.length));
501
+ console.log(`\ninvariant x mutant — ${mutants.length} mutants, `
502
+ + 'and the minimal removal that turns each assertion red\n');
503
+ for (const row of matrix) {
504
+ console.log(` ${row.id.padEnd(width)} ${row.broke.map(key).join(', ') || '(nothing)'}`);
505
+ }
506
+
507
+ // The per-ASSERTION half, and the one the per-invariant table above cannot give: an
508
+ // invariant goes red as a unit, so a dead assertion inside a live one is invisible there.
509
+ console.log('\nassertion x mutant — every call site, and what turns IT red\n');
510
+ for (const row of coverage) {
511
+ const label = `${row.invariant}:${row.site}`.padEnd(width + 6);
512
+ if (row.unmutated) {
513
+ console.log(` ${label} unmutated — no rule varies what it checks`);
514
+ if (row.by.length) {
515
+ problems.push(`${row.invariant} :${row.site} is declared assert.unmutated and `
516
+ + `[${row.by.join(', ')}] breaks it — it discriminates, so drop the .unmutated`);
517
+ }
518
+ } else {
519
+ console.log(` ${label} ${row.by.join(', ') || '(nothing)'}`);
520
+ if (!row.by.length) {
521
+ problems.push(`${row.invariant} :${row.site} is broken by no mutant — the assertion `
522
+ + 'cannot fail. Either it is not testing anything, or no rule varies what it '
523
+ + 'checks and it belongs on assert.unmutated, which the verdict counts apart');
524
+ }
525
+ }
526
+ }
527
+
528
+ console.log('\nisolation — the fixture that separates each rule from every other\n');
529
+ for (const rule of RULES) {
530
+ const isolating = matrix.filter((row) => {
531
+ const exact = row.broke.filter((m) => m.length === 1 && m[0] === rule);
532
+ if (exact.length !== 1) return false;
533
+ // The broad baseline is allowed to co-occur: it breaks every grant assertion by
534
+ // construction, so it hides nothing. Any OTHER co-occurring rule does.
535
+ return row.broke
536
+ .filter((m) => !(m.length === 1 && m[0] === rule))
537
+ .every((m) => m.every((r) => BROAD.includes(r)));
538
+ });
539
+ if (isolating.length) {
540
+ console.log(` ${rule.padEnd(28)} ${isolating.map((r) => r.id).join(', ')}`);
541
+ } else {
542
+ problems.push(`no fixture isolates ${rule} — every assertion it breaks is also `
543
+ + 'broken by another rule, so neither is proven');
544
+ }
545
+ }
546
+
547
+ if (problems.length) {
548
+ console.error(`\nFAIL: ${problems.length} problem(s) with the assertion pack itself`);
549
+ for (const p of problems) console.error(` - ${p}`);
550
+ return 1;
551
+ }
552
+ const pinned = coverage.filter((r) => r.unmutated).length;
553
+ console.log(`\nOK: ${INVARIANTS.length} invariants over ${coverage.length} assertions — `
554
+ + `${coverage.length - pinned} watched failing ONE CALL SITE AT A TIME against `
555
+ + `${mutants.length} mutants; ${pinned} declared unmutated and measured unbreakable; `
556
+ + `${RULES.length} rules, each isolated by a fixture`);
557
+ return 0;
558
+ }
559
+
560
+ const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
561
+ if (isMain) {
562
+ const mode = process.argv.includes('--self-test') ? selfTest : runAll;
563
+ process.exit(await mode());
564
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "id": "evt_PLACEHOLDER_refund_first",
3
+ "object": "event",
4
+ "api_version": "2026-07-29.dahlia",
5
+ "created": 1770076800,
6
+ "livemode": false,
7
+ "pending_webhooks": 1,
8
+ "request": {
9
+ "id": "req_PLACEHOLDER",
10
+ "idempotency_key": null
11
+ },
12
+ "type": "charge.refunded",
13
+ "data": {
14
+ "object": {
15
+ "id": "ch_PLACEHOLDER_creditpack",
16
+ "object": "charge",
17
+ "amount": 9000,
18
+ "amount_captured": 9000,
19
+ "amount_refunded": 4000,
20
+ "captured": true,
21
+ "paid": true,
22
+ "refunded": false,
23
+ "status": "succeeded",
24
+ "currency": "usd",
25
+ "customer": "cus_PLACEHOLDER_alice",
26
+ "invoice": null,
27
+ "payment_intent": "pi_PLACEHOLDER_creditpack",
28
+ "metadata": {
29
+ "userId": "usr_PLACEHOLDER_alice",
30
+ "productId": "prod_PLACEHOLDER_credits_90"
31
+ },
32
+ "refunds": {
33
+ "object": "list",
34
+ "has_more": false,
35
+ "total_count": 1,
36
+ "data": [
37
+ {
38
+ "id": "re_PLACEHOLDER_first",
39
+ "object": "refund",
40
+ "amount": 4000,
41
+ "charge": "ch_PLACEHOLDER_creditpack",
42
+ "created": 1770076800,
43
+ "currency": "usd",
44
+ "payment_intent": "pi_PLACEHOLDER_creditpack",
45
+ "reason": "requested_by_customer",
46
+ "status": "succeeded"
47
+ }
48
+ ]
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "id": "evt_PLACEHOLDER_refund_second",
3
+ "object": "event",
4
+ "api_version": "2026-07-29.dahlia",
5
+ "created": 1770163200,
6
+ "livemode": false,
7
+ "pending_webhooks": 1,
8
+ "request": {
9
+ "id": "req_PLACEHOLDER",
10
+ "idempotency_key": null
11
+ },
12
+ "type": "charge.refunded",
13
+ "data": {
14
+ "object": {
15
+ "id": "ch_PLACEHOLDER_creditpack",
16
+ "object": "charge",
17
+ "amount": 9000,
18
+ "amount_captured": 9000,
19
+ "amount_refunded": 9000,
20
+ "captured": true,
21
+ "paid": true,
22
+ "refunded": true,
23
+ "status": "succeeded",
24
+ "currency": "usd",
25
+ "customer": "cus_PLACEHOLDER_alice",
26
+ "invoice": null,
27
+ "payment_intent": "pi_PLACEHOLDER_creditpack",
28
+ "metadata": {
29
+ "userId": "usr_PLACEHOLDER_alice",
30
+ "productId": "prod_PLACEHOLDER_credits_90"
31
+ },
32
+ "refunds": {
33
+ "object": "list",
34
+ "has_more": false,
35
+ "total_count": 2,
36
+ "data": [
37
+ {
38
+ "id": "re_PLACEHOLDER_second",
39
+ "object": "refund",
40
+ "amount": 5000,
41
+ "charge": "ch_PLACEHOLDER_creditpack",
42
+ "created": 1770163200,
43
+ "currency": "usd",
44
+ "payment_intent": "pi_PLACEHOLDER_creditpack",
45
+ "reason": "requested_by_customer",
46
+ "status": "succeeded"
47
+ },
48
+ {
49
+ "id": "re_PLACEHOLDER_first",
50
+ "object": "refund",
51
+ "amount": 4000,
52
+ "charge": "ch_PLACEHOLDER_creditpack",
53
+ "created": 1770076800,
54
+ "currency": "usd",
55
+ "payment_intent": "pi_PLACEHOLDER_creditpack",
56
+ "reason": "requested_by_customer",
57
+ "status": "succeeded"
58
+ }
59
+ ]
60
+ }
61
+ }
62
+ }
63
+ }