@hyodotdev/openiap-commerce-protocol 0.0.0-bootstrap.0 → 0.1.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 (42) hide show
  1. package/CONVENTION.md +168 -0
  2. package/DESIGN.md +1056 -0
  3. package/README.md +227 -5
  4. package/SPEC.md +1471 -0
  5. package/conformance/index.d.ts +303 -0
  6. package/conformance/index.mjs +2126 -0
  7. package/conformance/mock-provider.mjs +491 -0
  8. package/examples/entitlement-granted-no-subscription.json +12 -0
  9. package/examples/entitlement-revoked.json +21 -0
  10. package/examples/provider-capabilities.json +209 -0
  11. package/examples/store-event-mapping.json +287 -0
  12. package/examples/subscription-canceled.json +22 -0
  13. package/examples/subscription-product-changed.json +30 -0
  14. package/examples/subscription-renewed.json +29 -0
  15. package/examples/verify-purchase-request.json +6 -0
  16. package/examples/verify-purchase-result.json +7 -0
  17. package/generated/bindings/graphql-operations.json +87 -0
  18. package/generated/bindings/http-binding.json +143 -0
  19. package/generated/bindings/introspection-signature.json +320 -0
  20. package/generated/bindings/operations-sdl.json +4 -0
  21. package/generated/bindings/operations.graphql +366 -0
  22. package/generated/commerce-protocol.graphql +1219 -0
  23. package/generated/openapi/commerce-protocol.openapi.json +1413 -0
  24. package/generated/schemas/commerce-event.schema.json +499 -0
  25. package/generated/schemas/commerce-protocol.bundle.schema.json +1576 -0
  26. package/generated/schemas/operations.schema.json +578 -0
  27. package/generated/schemas/primitives.schema.json +101 -0
  28. package/generated/schemas/provider-capabilities.schema.json +205 -0
  29. package/generated/schemas/store-event-mapping.schema.json +211 -0
  30. package/generated/vectors/lifecycle.json +908 -0
  31. package/generated/vectors/operations.json +1122 -0
  32. package/package.json +62 -12
  33. package/schema/01-primitives.graphql +102 -0
  34. package/schema/02-commerce-event.graphql +195 -0
  35. package/schema/03-provider-capabilities.graphql +139 -0
  36. package/schema/04-store-event-mapping.graphql +98 -0
  37. package/schema/05-operations.graphql +461 -0
  38. package/schema/06-compiler-vocabulary.graphql +139 -0
  39. package/schema/07-protocol-metadata.graphql +76 -0
  40. package/src/index.d.ts +63 -0
  41. package/src/index.mjs +121 -0
  42. package/vectors/signatures.json +139 -0
@@ -0,0 +1,908 @@
1
+ {
2
+ "$comment": "Deterministic lifecycle vectors for the OpenIAP Commerce Protocol. Any implementation, in any language, must reproduce every expected value. These are derived from the rules in SPEC.md — sections 2.3, 2.4, and 9.1 — and not from any particular backend, so passing them demonstrates conformance to the specification rather than agreement with one implementation.",
3
+ "entitlement": {
4
+ "rule": "SPEC.md 2.3. Evaluate at processedAt. Entitled when the state grants access AND processedAt < expiresAt. An omitted expiresAt means no expiry is known. The boundary is exclusive: processedAt == expiresAt is NOT entitled.",
5
+ "cases": [
6
+ {
7
+ "name": "Active / no expiry",
8
+ "state": "Active",
9
+ "occurredAt": 1756300799000,
10
+ "processedAt": 1756300800000,
11
+ "entitled": true
12
+ },
13
+ {
14
+ "name": "Active / expires in the future",
15
+ "state": "Active",
16
+ "expiresAt": 1756387200000,
17
+ "occurredAt": 1756300799000,
18
+ "processedAt": 1756300800000,
19
+ "entitled": true
20
+ },
21
+ {
22
+ "name": "Active / expires exactly at processing",
23
+ "state": "Active",
24
+ "expiresAt": 1756300800000,
25
+ "occurredAt": 1756300799000,
26
+ "processedAt": 1756300800000,
27
+ "entitled": false
28
+ },
29
+ {
30
+ "name": "Active / expired before processing",
31
+ "state": "Active",
32
+ "expiresAt": 1756300799999,
33
+ "occurredAt": 1756300799000,
34
+ "processedAt": 1756300800000,
35
+ "entitled": false
36
+ },
37
+ {
38
+ "name": "InGracePeriod / no expiry",
39
+ "state": "InGracePeriod",
40
+ "occurredAt": 1756300799000,
41
+ "processedAt": 1756300800000,
42
+ "entitled": true
43
+ },
44
+ {
45
+ "name": "InGracePeriod / expires in the future",
46
+ "state": "InGracePeriod",
47
+ "expiresAt": 1756387200000,
48
+ "occurredAt": 1756300799000,
49
+ "processedAt": 1756300800000,
50
+ "entitled": true
51
+ },
52
+ {
53
+ "name": "InGracePeriod / expires exactly at processing",
54
+ "state": "InGracePeriod",
55
+ "expiresAt": 1756300800000,
56
+ "occurredAt": 1756300799000,
57
+ "processedAt": 1756300800000,
58
+ "entitled": false
59
+ },
60
+ {
61
+ "name": "InGracePeriod / expired before processing",
62
+ "state": "InGracePeriod",
63
+ "expiresAt": 1756300799999,
64
+ "occurredAt": 1756300799000,
65
+ "processedAt": 1756300800000,
66
+ "entitled": false
67
+ },
68
+ {
69
+ "name": "InBillingRetry / no expiry",
70
+ "state": "InBillingRetry",
71
+ "occurredAt": 1756300799000,
72
+ "processedAt": 1756300800000,
73
+ "entitled": false
74
+ },
75
+ {
76
+ "name": "InBillingRetry / expires in the future",
77
+ "state": "InBillingRetry",
78
+ "expiresAt": 1756387200000,
79
+ "occurredAt": 1756300799000,
80
+ "processedAt": 1756300800000,
81
+ "entitled": false
82
+ },
83
+ {
84
+ "name": "InBillingRetry / expires exactly at processing",
85
+ "state": "InBillingRetry",
86
+ "expiresAt": 1756300800000,
87
+ "occurredAt": 1756300799000,
88
+ "processedAt": 1756300800000,
89
+ "entitled": false
90
+ },
91
+ {
92
+ "name": "InBillingRetry / expired before processing",
93
+ "state": "InBillingRetry",
94
+ "expiresAt": 1756300799999,
95
+ "occurredAt": 1756300799000,
96
+ "processedAt": 1756300800000,
97
+ "entitled": false
98
+ },
99
+ {
100
+ "name": "Paused / no expiry",
101
+ "state": "Paused",
102
+ "occurredAt": 1756300799000,
103
+ "processedAt": 1756300800000,
104
+ "entitled": false
105
+ },
106
+ {
107
+ "name": "Paused / expires in the future",
108
+ "state": "Paused",
109
+ "expiresAt": 1756387200000,
110
+ "occurredAt": 1756300799000,
111
+ "processedAt": 1756300800000,
112
+ "entitled": false
113
+ },
114
+ {
115
+ "name": "Paused / expires exactly at processing",
116
+ "state": "Paused",
117
+ "expiresAt": 1756300800000,
118
+ "occurredAt": 1756300799000,
119
+ "processedAt": 1756300800000,
120
+ "entitled": false
121
+ },
122
+ {
123
+ "name": "Paused / expired before processing",
124
+ "state": "Paused",
125
+ "expiresAt": 1756300799999,
126
+ "occurredAt": 1756300799000,
127
+ "processedAt": 1756300800000,
128
+ "entitled": false
129
+ },
130
+ {
131
+ "name": "Expired / no expiry",
132
+ "state": "Expired",
133
+ "occurredAt": 1756300799000,
134
+ "processedAt": 1756300800000,
135
+ "entitled": false
136
+ },
137
+ {
138
+ "name": "Expired / expires in the future",
139
+ "state": "Expired",
140
+ "expiresAt": 1756387200000,
141
+ "occurredAt": 1756300799000,
142
+ "processedAt": 1756300800000,
143
+ "entitled": false
144
+ },
145
+ {
146
+ "name": "Expired / expires exactly at processing",
147
+ "state": "Expired",
148
+ "expiresAt": 1756300800000,
149
+ "occurredAt": 1756300799000,
150
+ "processedAt": 1756300800000,
151
+ "entitled": false
152
+ },
153
+ {
154
+ "name": "Expired / expired before processing",
155
+ "state": "Expired",
156
+ "expiresAt": 1756300799999,
157
+ "occurredAt": 1756300799000,
158
+ "processedAt": 1756300800000,
159
+ "entitled": false
160
+ },
161
+ {
162
+ "name": "Revoked / no expiry",
163
+ "state": "Revoked",
164
+ "occurredAt": 1756300799000,
165
+ "processedAt": 1756300800000,
166
+ "entitled": false
167
+ },
168
+ {
169
+ "name": "Revoked / expires in the future",
170
+ "state": "Revoked",
171
+ "expiresAt": 1756387200000,
172
+ "occurredAt": 1756300799000,
173
+ "processedAt": 1756300800000,
174
+ "entitled": false
175
+ },
176
+ {
177
+ "name": "Revoked / expires exactly at processing",
178
+ "state": "Revoked",
179
+ "expiresAt": 1756300800000,
180
+ "occurredAt": 1756300799000,
181
+ "processedAt": 1756300800000,
182
+ "entitled": false
183
+ },
184
+ {
185
+ "name": "Revoked / expired before processing",
186
+ "state": "Revoked",
187
+ "expiresAt": 1756300799999,
188
+ "occurredAt": 1756300799000,
189
+ "processedAt": 1756300800000,
190
+ "entitled": false
191
+ },
192
+ {
193
+ "name": "Refunded / no expiry",
194
+ "state": "Refunded",
195
+ "occurredAt": 1756300799000,
196
+ "processedAt": 1756300800000,
197
+ "entitled": false
198
+ },
199
+ {
200
+ "name": "Refunded / expires in the future",
201
+ "state": "Refunded",
202
+ "expiresAt": 1756387200000,
203
+ "occurredAt": 1756300799000,
204
+ "processedAt": 1756300800000,
205
+ "entitled": false
206
+ },
207
+ {
208
+ "name": "Refunded / expires exactly at processing",
209
+ "state": "Refunded",
210
+ "expiresAt": 1756300800000,
211
+ "occurredAt": 1756300799000,
212
+ "processedAt": 1756300800000,
213
+ "entitled": false
214
+ },
215
+ {
216
+ "name": "Refunded / expired before processing",
217
+ "state": "Refunded",
218
+ "expiresAt": 1756300799999,
219
+ "occurredAt": 1756300799000,
220
+ "processedAt": 1756300800000,
221
+ "entitled": false
222
+ },
223
+ {
224
+ "name": "Unknown / no expiry",
225
+ "state": "Unknown",
226
+ "occurredAt": 1756300799000,
227
+ "processedAt": 1756300800000,
228
+ "entitled": false
229
+ },
230
+ {
231
+ "name": "Unknown / expires in the future",
232
+ "state": "Unknown",
233
+ "expiresAt": 1756387200000,
234
+ "occurredAt": 1756300799000,
235
+ "processedAt": 1756300800000,
236
+ "entitled": false
237
+ },
238
+ {
239
+ "name": "Unknown / expires exactly at processing",
240
+ "state": "Unknown",
241
+ "expiresAt": 1756300800000,
242
+ "occurredAt": 1756300799000,
243
+ "processedAt": 1756300800000,
244
+ "entitled": false
245
+ },
246
+ {
247
+ "name": "Unknown / expired before processing",
248
+ "state": "Unknown",
249
+ "expiresAt": 1756300799999,
250
+ "occurredAt": 1756300799000,
251
+ "processedAt": 1756300800000,
252
+ "entitled": false
253
+ }
254
+ ]
255
+ },
256
+ "emission": {
257
+ "rule": "SPEC.md 9.1. `lifecycleEvent` is the subscription event the emitter determined occurred, or null when nothing lifecycle-relevant changed. An entitlement event is emitted only when the gate actually flips, and follows the lifecycle event when both occur. How an emitter decides which lifecycle event occurred is implementation business; what it must then emit is not.",
258
+ "precondition": "Every case assumes the purchase has a bound userId and productId. SPEC.md 2.4 defers an entitlement delta while that actionable target is unavailable.",
259
+ "cases": [
260
+ {
261
+ "name": "subscription.started (not entitled -> not entitled)",
262
+ "lifecycleEvent": "subscription.started",
263
+ "entitledBefore": false,
264
+ "entitledAfter": false,
265
+ "emit": [
266
+ "subscription.started"
267
+ ]
268
+ },
269
+ {
270
+ "name": "subscription.started (not entitled -> entitled)",
271
+ "lifecycleEvent": "subscription.started",
272
+ "entitledBefore": false,
273
+ "entitledAfter": true,
274
+ "emit": [
275
+ "subscription.started",
276
+ "entitlement.granted"
277
+ ]
278
+ },
279
+ {
280
+ "name": "subscription.started (entitled -> entitled)",
281
+ "lifecycleEvent": "subscription.started",
282
+ "entitledBefore": true,
283
+ "entitledAfter": true,
284
+ "emit": [
285
+ "subscription.started"
286
+ ]
287
+ },
288
+ {
289
+ "name": "subscription.started (entitled -> not entitled)",
290
+ "lifecycleEvent": "subscription.started",
291
+ "entitledBefore": true,
292
+ "entitledAfter": false,
293
+ "emit": [
294
+ "subscription.started",
295
+ "entitlement.revoked"
296
+ ]
297
+ },
298
+ {
299
+ "name": "subscription.renewed (not entitled -> not entitled)",
300
+ "lifecycleEvent": "subscription.renewed",
301
+ "entitledBefore": false,
302
+ "entitledAfter": false,
303
+ "emit": [
304
+ "subscription.renewed"
305
+ ]
306
+ },
307
+ {
308
+ "name": "subscription.renewed (not entitled -> entitled)",
309
+ "lifecycleEvent": "subscription.renewed",
310
+ "entitledBefore": false,
311
+ "entitledAfter": true,
312
+ "emit": [
313
+ "subscription.renewed",
314
+ "entitlement.granted"
315
+ ]
316
+ },
317
+ {
318
+ "name": "subscription.renewed (entitled -> entitled)",
319
+ "lifecycleEvent": "subscription.renewed",
320
+ "entitledBefore": true,
321
+ "entitledAfter": true,
322
+ "emit": [
323
+ "subscription.renewed"
324
+ ]
325
+ },
326
+ {
327
+ "name": "subscription.renewed (entitled -> not entitled)",
328
+ "lifecycleEvent": "subscription.renewed",
329
+ "entitledBefore": true,
330
+ "entitledAfter": false,
331
+ "emit": [
332
+ "subscription.renewed",
333
+ "entitlement.revoked"
334
+ ]
335
+ },
336
+ {
337
+ "name": "subscription.recovered (not entitled -> not entitled)",
338
+ "lifecycleEvent": "subscription.recovered",
339
+ "entitledBefore": false,
340
+ "entitledAfter": false,
341
+ "emit": [
342
+ "subscription.recovered"
343
+ ]
344
+ },
345
+ {
346
+ "name": "subscription.recovered (not entitled -> entitled)",
347
+ "lifecycleEvent": "subscription.recovered",
348
+ "entitledBefore": false,
349
+ "entitledAfter": true,
350
+ "emit": [
351
+ "subscription.recovered",
352
+ "entitlement.granted"
353
+ ]
354
+ },
355
+ {
356
+ "name": "subscription.recovered (entitled -> entitled)",
357
+ "lifecycleEvent": "subscription.recovered",
358
+ "entitledBefore": true,
359
+ "entitledAfter": true,
360
+ "emit": [
361
+ "subscription.recovered"
362
+ ]
363
+ },
364
+ {
365
+ "name": "subscription.recovered (entitled -> not entitled)",
366
+ "lifecycleEvent": "subscription.recovered",
367
+ "entitledBefore": true,
368
+ "entitledAfter": false,
369
+ "emit": [
370
+ "subscription.recovered",
371
+ "entitlement.revoked"
372
+ ]
373
+ },
374
+ {
375
+ "name": "subscription.entered_grace_period (not entitled -> not entitled)",
376
+ "lifecycleEvent": "subscription.entered_grace_period",
377
+ "entitledBefore": false,
378
+ "entitledAfter": false,
379
+ "emit": [
380
+ "subscription.entered_grace_period"
381
+ ]
382
+ },
383
+ {
384
+ "name": "subscription.entered_grace_period (not entitled -> entitled)",
385
+ "lifecycleEvent": "subscription.entered_grace_period",
386
+ "entitledBefore": false,
387
+ "entitledAfter": true,
388
+ "emit": [
389
+ "subscription.entered_grace_period",
390
+ "entitlement.granted"
391
+ ]
392
+ },
393
+ {
394
+ "name": "subscription.entered_grace_period (entitled -> entitled)",
395
+ "lifecycleEvent": "subscription.entered_grace_period",
396
+ "entitledBefore": true,
397
+ "entitledAfter": true,
398
+ "emit": [
399
+ "subscription.entered_grace_period"
400
+ ]
401
+ },
402
+ {
403
+ "name": "subscription.entered_grace_period (entitled -> not entitled)",
404
+ "lifecycleEvent": "subscription.entered_grace_period",
405
+ "entitledBefore": true,
406
+ "entitledAfter": false,
407
+ "emit": [
408
+ "subscription.entered_grace_period",
409
+ "entitlement.revoked"
410
+ ]
411
+ },
412
+ {
413
+ "name": "subscription.entered_billing_retry (not entitled -> not entitled)",
414
+ "lifecycleEvent": "subscription.entered_billing_retry",
415
+ "entitledBefore": false,
416
+ "entitledAfter": false,
417
+ "emit": [
418
+ "subscription.entered_billing_retry"
419
+ ]
420
+ },
421
+ {
422
+ "name": "subscription.entered_billing_retry (not entitled -> entitled)",
423
+ "lifecycleEvent": "subscription.entered_billing_retry",
424
+ "entitledBefore": false,
425
+ "entitledAfter": true,
426
+ "emit": [
427
+ "subscription.entered_billing_retry",
428
+ "entitlement.granted"
429
+ ]
430
+ },
431
+ {
432
+ "name": "subscription.entered_billing_retry (entitled -> entitled)",
433
+ "lifecycleEvent": "subscription.entered_billing_retry",
434
+ "entitledBefore": true,
435
+ "entitledAfter": true,
436
+ "emit": [
437
+ "subscription.entered_billing_retry"
438
+ ]
439
+ },
440
+ {
441
+ "name": "subscription.entered_billing_retry (entitled -> not entitled)",
442
+ "lifecycleEvent": "subscription.entered_billing_retry",
443
+ "entitledBefore": true,
444
+ "entitledAfter": false,
445
+ "emit": [
446
+ "subscription.entered_billing_retry",
447
+ "entitlement.revoked"
448
+ ]
449
+ },
450
+ {
451
+ "name": "subscription.expired (not entitled -> not entitled)",
452
+ "lifecycleEvent": "subscription.expired",
453
+ "entitledBefore": false,
454
+ "entitledAfter": false,
455
+ "emit": [
456
+ "subscription.expired"
457
+ ]
458
+ },
459
+ {
460
+ "name": "subscription.expired (not entitled -> entitled)",
461
+ "lifecycleEvent": "subscription.expired",
462
+ "entitledBefore": false,
463
+ "entitledAfter": true,
464
+ "emit": [
465
+ "subscription.expired",
466
+ "entitlement.granted"
467
+ ]
468
+ },
469
+ {
470
+ "name": "subscription.expired (entitled -> entitled)",
471
+ "lifecycleEvent": "subscription.expired",
472
+ "entitledBefore": true,
473
+ "entitledAfter": true,
474
+ "emit": [
475
+ "subscription.expired"
476
+ ]
477
+ },
478
+ {
479
+ "name": "subscription.expired (entitled -> not entitled)",
480
+ "lifecycleEvent": "subscription.expired",
481
+ "entitledBefore": true,
482
+ "entitledAfter": false,
483
+ "emit": [
484
+ "subscription.expired",
485
+ "entitlement.revoked"
486
+ ]
487
+ },
488
+ {
489
+ "name": "subscription.canceled (not entitled -> not entitled)",
490
+ "lifecycleEvent": "subscription.canceled",
491
+ "entitledBefore": false,
492
+ "entitledAfter": false,
493
+ "emit": [
494
+ "subscription.canceled"
495
+ ]
496
+ },
497
+ {
498
+ "name": "subscription.canceled (not entitled -> entitled)",
499
+ "lifecycleEvent": "subscription.canceled",
500
+ "entitledBefore": false,
501
+ "entitledAfter": true,
502
+ "emit": [
503
+ "subscription.canceled",
504
+ "entitlement.granted"
505
+ ]
506
+ },
507
+ {
508
+ "name": "subscription.canceled (entitled -> entitled)",
509
+ "lifecycleEvent": "subscription.canceled",
510
+ "entitledBefore": true,
511
+ "entitledAfter": true,
512
+ "emit": [
513
+ "subscription.canceled"
514
+ ]
515
+ },
516
+ {
517
+ "name": "subscription.canceled (entitled -> not entitled)",
518
+ "lifecycleEvent": "subscription.canceled",
519
+ "entitledBefore": true,
520
+ "entitledAfter": false,
521
+ "emit": [
522
+ "subscription.canceled",
523
+ "entitlement.revoked"
524
+ ]
525
+ },
526
+ {
527
+ "name": "subscription.uncanceled (not entitled -> not entitled)",
528
+ "lifecycleEvent": "subscription.uncanceled",
529
+ "entitledBefore": false,
530
+ "entitledAfter": false,
531
+ "emit": [
532
+ "subscription.uncanceled"
533
+ ]
534
+ },
535
+ {
536
+ "name": "subscription.uncanceled (not entitled -> entitled)",
537
+ "lifecycleEvent": "subscription.uncanceled",
538
+ "entitledBefore": false,
539
+ "entitledAfter": true,
540
+ "emit": [
541
+ "subscription.uncanceled",
542
+ "entitlement.granted"
543
+ ]
544
+ },
545
+ {
546
+ "name": "subscription.uncanceled (entitled -> entitled)",
547
+ "lifecycleEvent": "subscription.uncanceled",
548
+ "entitledBefore": true,
549
+ "entitledAfter": true,
550
+ "emit": [
551
+ "subscription.uncanceled"
552
+ ]
553
+ },
554
+ {
555
+ "name": "subscription.uncanceled (entitled -> not entitled)",
556
+ "lifecycleEvent": "subscription.uncanceled",
557
+ "entitledBefore": true,
558
+ "entitledAfter": false,
559
+ "emit": [
560
+ "subscription.uncanceled",
561
+ "entitlement.revoked"
562
+ ]
563
+ },
564
+ {
565
+ "name": "subscription.revoked (not entitled -> not entitled)",
566
+ "lifecycleEvent": "subscription.revoked",
567
+ "entitledBefore": false,
568
+ "entitledAfter": false,
569
+ "emit": [
570
+ "subscription.revoked"
571
+ ]
572
+ },
573
+ {
574
+ "name": "subscription.revoked (not entitled -> entitled)",
575
+ "lifecycleEvent": "subscription.revoked",
576
+ "entitledBefore": false,
577
+ "entitledAfter": true,
578
+ "emit": [
579
+ "subscription.revoked",
580
+ "entitlement.granted"
581
+ ]
582
+ },
583
+ {
584
+ "name": "subscription.revoked (entitled -> entitled)",
585
+ "lifecycleEvent": "subscription.revoked",
586
+ "entitledBefore": true,
587
+ "entitledAfter": true,
588
+ "emit": [
589
+ "subscription.revoked"
590
+ ]
591
+ },
592
+ {
593
+ "name": "subscription.revoked (entitled -> not entitled)",
594
+ "lifecycleEvent": "subscription.revoked",
595
+ "entitledBefore": true,
596
+ "entitledAfter": false,
597
+ "emit": [
598
+ "subscription.revoked",
599
+ "entitlement.revoked"
600
+ ]
601
+ },
602
+ {
603
+ "name": "subscription.refunded (not entitled -> not entitled)",
604
+ "lifecycleEvent": "subscription.refunded",
605
+ "entitledBefore": false,
606
+ "entitledAfter": false,
607
+ "emit": [
608
+ "subscription.refunded"
609
+ ]
610
+ },
611
+ {
612
+ "name": "subscription.refunded (not entitled -> entitled)",
613
+ "lifecycleEvent": "subscription.refunded",
614
+ "entitledBefore": false,
615
+ "entitledAfter": true,
616
+ "emit": [
617
+ "subscription.refunded",
618
+ "entitlement.granted"
619
+ ]
620
+ },
621
+ {
622
+ "name": "subscription.refunded (entitled -> entitled)",
623
+ "lifecycleEvent": "subscription.refunded",
624
+ "entitledBefore": true,
625
+ "entitledAfter": true,
626
+ "emit": [
627
+ "subscription.refunded"
628
+ ]
629
+ },
630
+ {
631
+ "name": "subscription.refunded (entitled -> not entitled)",
632
+ "lifecycleEvent": "subscription.refunded",
633
+ "entitledBefore": true,
634
+ "entitledAfter": false,
635
+ "emit": [
636
+ "subscription.refunded",
637
+ "entitlement.revoked"
638
+ ]
639
+ },
640
+ {
641
+ "name": "subscription.product_changed (not entitled -> not entitled)",
642
+ "lifecycleEvent": "subscription.product_changed",
643
+ "entitledBefore": false,
644
+ "entitledAfter": false,
645
+ "emit": [
646
+ "subscription.product_changed"
647
+ ]
648
+ },
649
+ {
650
+ "name": "subscription.product_changed (not entitled -> entitled)",
651
+ "lifecycleEvent": "subscription.product_changed",
652
+ "entitledBefore": false,
653
+ "entitledAfter": true,
654
+ "emit": [
655
+ "subscription.product_changed",
656
+ "entitlement.granted"
657
+ ]
658
+ },
659
+ {
660
+ "name": "subscription.product_changed (entitled -> entitled)",
661
+ "lifecycleEvent": "subscription.product_changed",
662
+ "entitledBefore": true,
663
+ "entitledAfter": true,
664
+ "emit": [
665
+ "subscription.product_changed"
666
+ ]
667
+ },
668
+ {
669
+ "name": "subscription.product_changed (entitled -> not entitled)",
670
+ "lifecycleEvent": "subscription.product_changed",
671
+ "entitledBefore": true,
672
+ "entitledAfter": false,
673
+ "emit": [
674
+ "subscription.product_changed",
675
+ "entitlement.revoked"
676
+ ]
677
+ },
678
+ {
679
+ "name": "subscription.price_changed (not entitled -> not entitled)",
680
+ "lifecycleEvent": "subscription.price_changed",
681
+ "entitledBefore": false,
682
+ "entitledAfter": false,
683
+ "emit": [
684
+ "subscription.price_changed"
685
+ ]
686
+ },
687
+ {
688
+ "name": "subscription.price_changed (not entitled -> entitled)",
689
+ "lifecycleEvent": "subscription.price_changed",
690
+ "entitledBefore": false,
691
+ "entitledAfter": true,
692
+ "emit": [
693
+ "subscription.price_changed",
694
+ "entitlement.granted"
695
+ ]
696
+ },
697
+ {
698
+ "name": "subscription.price_changed (entitled -> entitled)",
699
+ "lifecycleEvent": "subscription.price_changed",
700
+ "entitledBefore": true,
701
+ "entitledAfter": true,
702
+ "emit": [
703
+ "subscription.price_changed"
704
+ ]
705
+ },
706
+ {
707
+ "name": "subscription.price_changed (entitled -> not entitled)",
708
+ "lifecycleEvent": "subscription.price_changed",
709
+ "entitledBefore": true,
710
+ "entitledAfter": false,
711
+ "emit": [
712
+ "subscription.price_changed",
713
+ "entitlement.revoked"
714
+ ]
715
+ },
716
+ {
717
+ "name": "subscription.deferred (not entitled -> not entitled)",
718
+ "lifecycleEvent": "subscription.deferred",
719
+ "entitledBefore": false,
720
+ "entitledAfter": false,
721
+ "emit": [
722
+ "subscription.deferred"
723
+ ]
724
+ },
725
+ {
726
+ "name": "subscription.deferred (not entitled -> entitled)",
727
+ "lifecycleEvent": "subscription.deferred",
728
+ "entitledBefore": false,
729
+ "entitledAfter": true,
730
+ "emit": [
731
+ "subscription.deferred",
732
+ "entitlement.granted"
733
+ ]
734
+ },
735
+ {
736
+ "name": "subscription.deferred (entitled -> entitled)",
737
+ "lifecycleEvent": "subscription.deferred",
738
+ "entitledBefore": true,
739
+ "entitledAfter": true,
740
+ "emit": [
741
+ "subscription.deferred"
742
+ ]
743
+ },
744
+ {
745
+ "name": "subscription.deferred (entitled -> not entitled)",
746
+ "lifecycleEvent": "subscription.deferred",
747
+ "entitledBefore": true,
748
+ "entitledAfter": false,
749
+ "emit": [
750
+ "subscription.deferred",
751
+ "entitlement.revoked"
752
+ ]
753
+ },
754
+ {
755
+ "name": "subscription.paused (not entitled -> not entitled)",
756
+ "lifecycleEvent": "subscription.paused",
757
+ "entitledBefore": false,
758
+ "entitledAfter": false,
759
+ "emit": [
760
+ "subscription.paused"
761
+ ]
762
+ },
763
+ {
764
+ "name": "subscription.paused (not entitled -> entitled)",
765
+ "lifecycleEvent": "subscription.paused",
766
+ "entitledBefore": false,
767
+ "entitledAfter": true,
768
+ "emit": [
769
+ "subscription.paused",
770
+ "entitlement.granted"
771
+ ]
772
+ },
773
+ {
774
+ "name": "subscription.paused (entitled -> entitled)",
775
+ "lifecycleEvent": "subscription.paused",
776
+ "entitledBefore": true,
777
+ "entitledAfter": true,
778
+ "emit": [
779
+ "subscription.paused"
780
+ ]
781
+ },
782
+ {
783
+ "name": "subscription.paused (entitled -> not entitled)",
784
+ "lifecycleEvent": "subscription.paused",
785
+ "entitledBefore": true,
786
+ "entitledAfter": false,
787
+ "emit": [
788
+ "subscription.paused",
789
+ "entitlement.revoked"
790
+ ]
791
+ },
792
+ {
793
+ "name": "subscription.resumed (not entitled -> not entitled)",
794
+ "lifecycleEvent": "subscription.resumed",
795
+ "entitledBefore": false,
796
+ "entitledAfter": false,
797
+ "emit": [
798
+ "subscription.resumed"
799
+ ]
800
+ },
801
+ {
802
+ "name": "subscription.resumed (not entitled -> entitled)",
803
+ "lifecycleEvent": "subscription.resumed",
804
+ "entitledBefore": false,
805
+ "entitledAfter": true,
806
+ "emit": [
807
+ "subscription.resumed",
808
+ "entitlement.granted"
809
+ ]
810
+ },
811
+ {
812
+ "name": "subscription.resumed (entitled -> entitled)",
813
+ "lifecycleEvent": "subscription.resumed",
814
+ "entitledBefore": true,
815
+ "entitledAfter": true,
816
+ "emit": [
817
+ "subscription.resumed"
818
+ ]
819
+ },
820
+ {
821
+ "name": "subscription.resumed (entitled -> not entitled)",
822
+ "lifecycleEvent": "subscription.resumed",
823
+ "entitledBefore": true,
824
+ "entitledAfter": false,
825
+ "emit": [
826
+ "subscription.resumed",
827
+ "entitlement.revoked"
828
+ ]
829
+ },
830
+ {
831
+ "name": "no lifecycle change (not entitled -> not entitled)",
832
+ "lifecycleEvent": null,
833
+ "entitledBefore": false,
834
+ "entitledAfter": false,
835
+ "emit": []
836
+ },
837
+ {
838
+ "name": "no lifecycle change (not entitled -> entitled)",
839
+ "lifecycleEvent": null,
840
+ "entitledBefore": false,
841
+ "entitledAfter": true,
842
+ "emit": [
843
+ "entitlement.granted"
844
+ ]
845
+ },
846
+ {
847
+ "name": "no lifecycle change (entitled -> entitled)",
848
+ "lifecycleEvent": null,
849
+ "entitledBefore": true,
850
+ "entitledAfter": true,
851
+ "emit": []
852
+ },
853
+ {
854
+ "name": "no lifecycle change (entitled -> not entitled)",
855
+ "lifecycleEvent": null,
856
+ "entitledBefore": true,
857
+ "entitledAfter": false,
858
+ "emit": [
859
+ "entitlement.revoked"
860
+ ]
861
+ }
862
+ ]
863
+ },
864
+ "binding": {
865
+ "rule": "SPEC.md 2.4. At first binding, coalesce every unbound gate change into the current predicate evaluated at binding processedAt. The unbound baseline is not entitled: emit one entitlement.granted only when the current gate is open; never replay an obsolete historical grant or revoke.",
866
+ "cases": [
867
+ {
868
+ "name": "bind while the first grant is current",
869
+ "unboundGateChanges": [
870
+ "entitlement.granted"
871
+ ],
872
+ "entitledAtBinding": true,
873
+ "emit": [
874
+ "entitlement.granted"
875
+ ]
876
+ },
877
+ {
878
+ "name": "bind after the unbound grant expired",
879
+ "unboundGateChanges": [
880
+ "entitlement.granted"
881
+ ],
882
+ "entitledAtBinding": false,
883
+ "emit": []
884
+ },
885
+ {
886
+ "name": "bind after an unbound grant was revoked",
887
+ "unboundGateChanges": [
888
+ "entitlement.granted",
889
+ "entitlement.revoked"
890
+ ],
891
+ "entitledAtBinding": false,
892
+ "emit": []
893
+ },
894
+ {
895
+ "name": "bind after an unbound gate reopened",
896
+ "unboundGateChanges": [
897
+ "entitlement.granted",
898
+ "entitlement.revoked",
899
+ "entitlement.granted"
900
+ ],
901
+ "entitledAtBinding": true,
902
+ "emit": [
903
+ "entitlement.granted"
904
+ ]
905
+ }
906
+ ]
907
+ }
908
+ }