proactive-gate 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +83 -14
  2. package/README.tr.md +55 -3
  3. package/dist/src/checks.js +3 -1
  4. package/dist/src/cli.d.ts +2 -0
  5. package/dist/src/cli.js +92 -1
  6. package/dist/src/demo-week.d.ts +38 -0
  7. package/dist/src/demo-week.js +115 -0
  8. package/dist/src/explain.js +8 -2
  9. package/dist/src/presets.js +16 -2
  10. package/dist/src/simulate-report.d.ts +27 -0
  11. package/dist/src/simulate-report.js +128 -0
  12. package/dist/src/simulate.d.ts +118 -0
  13. package/dist/src/simulate.js +269 -0
  14. package/package.json +4 -3
  15. package/spec/CONFORMANCE.md +49 -3
  16. package/spec/SPEC.md +3 -0
  17. package/spec/SPEC_VERSION +1 -1
  18. package/spec/fixtures/adaptive-timing/placeholder.json +1 -1
  19. package/spec/fixtures/budget/bypass-priority.json +1 -1
  20. package/spec/fixtures/budget/daily-atomic-commit.json +1 -1
  21. package/spec/fixtures/budget/near-limit.json +1 -1
  22. package/spec/fixtures/budget/race-second-commit-loses.json +1 -1
  23. package/spec/fixtures/budget/weekly-iso-week.json +1 -1
  24. package/spec/fixtures/consent/required.json +1 -1
  25. package/spec/fixtures/cooldown/three-dismissals.json +1 -1
  26. package/spec/fixtures/dedupe/already-delivered.json +1 -1
  27. package/spec/fixtures/dedupe/no-key-skips.json +1 -1
  28. package/spec/fixtures/defer/snooze-as-defer.json +1 -1
  29. package/spec/fixtures/mode/allow-list.json +1 -1
  30. package/spec/fixtures/ordering/kill-switch.json +1 -1
  31. package/spec/fixtures/ordering/short-circuit.json +1 -1
  32. package/spec/fixtures/policy/unknown-check-is-an-error.json +1 -1
  33. package/spec/fixtures/presets/cn-minor-mode.json +1 -1
  34. package/spec/fixtures/presets/in-tcccp.json +258 -0
  35. package/spec/fixtures/presets/kakao-brand-message.json +1 -1
  36. package/spec/fixtures/presets/kr-network-act-50.json +1 -1
  37. package/spec/fixtures/presets/telegram-bot.json +1 -1
  38. package/spec/fixtures/presets/us-tcpa.json +1 -1
  39. package/spec/fixtures/quiet-hours/apia.json +1 -1
  40. package/spec/fixtures/quiet-hours/caller-supplied-dates.json +1 -1
  41. package/spec/fixtures/quiet-hours/crosses-midnight-by-day.json +1 -1
  42. package/spec/fixtures/quiet-hours/dst-new-york.json +1 -1
  43. package/spec/fixtures/quiet-hours/istanbul.json +1 -1
  44. package/spec/fixtures/quiet-hours/wall-clock.json +1 -1
  45. package/spec/fixtures/quiet-hours/weekday-schedule.json +1 -1
  46. package/spec/fixtures/shadow/reject-continues.json +1 -1
  47. package/spec/fixtures/trust-ramp/first-week.json +1 -1
  48. package/spec/fixtures/utility/bounded-deferral-cap.json +1 -1
  49. package/spec/fixtures/utility/bounded-deferral.json +1 -1
  50. package/spec/fixtures/utility/floor.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "budget/bypass-priority",
5
5
  "description": "A bypass priority passes the budget without reading or consuming it.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "budget/daily-atomic-commit",
5
5
  "description": "evaluate reads the counter, commit consumes it, the extra commit is refused.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "budget/near-limit",
5
5
  "description": "A pass close to the limit carries nearLimit.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "budget/race-second-commit-loses",
5
5
  "description": "Two evaluations before either commit: only the first commit wins.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "budget/weekly-iso-week",
5
5
  "description": "The weekly counter is keyed on the local ISO week.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "consent/required",
5
5
  "description": "Consent is the first thing about the person that is checked.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "cooldown/three-dismissals",
5
5
  "description": "Three dismissals of a type within 30 days silence it for 7 days from the latest.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.2.0",
4
4
  "name": "dedupe/already-delivered",
5
5
  "description": "A key claimed inside the window rejects the second attempt at the same event.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.2.0",
4
4
  "name": "dedupe/no-key-skips",
5
5
  "description": "With no dedupeKey on the candidate the check skips rather than guessing an identity.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "defer/snooze-as-defer",
5
5
  "description": "snooze with defer true produces a deferred decision with retryAt.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "mode/allow-list",
5
5
  "description": "Only listed operating modes receive proactive messages; an undefined mode passes.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "ordering/kill-switch",
5
5
  "description": "A kill switch that is on stops everything before consent is even read.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "ordering/short-circuit",
5
5
  "description": "Checks run in policy order and stop at the first rejection.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "policy/unknown-check-is-an-error",
5
5
  "description": "The runner never sees this fixture's expectation matter; it documents that compile errors are for the schema, not the runtime. A minimal valid policy passes.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "presets/cn-minor-mode",
5
5
  "description": "cnMinorMode restricts minors to 06:00 to 22:00 China time and one message a day; adults pass.",
@@ -0,0 +1,258 @@
1
+ {
2
+ "spec_version": "1.3.0",
3
+ "since": "1.3.0",
4
+ "name": "presets/in-tcccp",
5
+ "description": "inTcccp needs promotional consent always, and each Schedule-II default-off band passes only when the subscriber opted that band in.",
6
+ "policy": {
7
+ "specVersion": "1.0.0",
8
+ "checks": [
9
+ {
10
+ "preset": "inTcccp"
11
+ }
12
+ ]
13
+ },
14
+ "tests": [
15
+ {
16
+ "description": "no promotional consent",
17
+ "input": {
18
+ "user": {
19
+ "id": "u1",
20
+ "consent": true,
21
+ "proactiveEnabled": true,
22
+ "mode": "normal",
23
+ "intensity": "normal",
24
+ "timezone": "Asia/Kolkata",
25
+ "quietHours": {
26
+ "start": "22:00",
27
+ "end": "08:00"
28
+ },
29
+ "createdAt": "2026-01-01T00:00:00Z",
30
+ "consents": {}
31
+ },
32
+ "candidate": {
33
+ "id": "c1",
34
+ "type": "reminder",
35
+ "priority": "normal",
36
+ "surfaces": [
37
+ "push",
38
+ "feed"
39
+ ]
40
+ },
41
+ "now": "2026-09-04T06:30:00Z"
42
+ },
43
+ "expect": {
44
+ "allowed": false,
45
+ "rejectedBy": "consent:promotional",
46
+ "trace": [
47
+ "consent:promotional"
48
+ ]
49
+ }
50
+ },
51
+ {
52
+ "description": "promotional consent at 12:00 IST passes",
53
+ "input": {
54
+ "user": {
55
+ "id": "u1",
56
+ "consent": true,
57
+ "proactiveEnabled": true,
58
+ "mode": "normal",
59
+ "intensity": "normal",
60
+ "timezone": "Asia/Kolkata",
61
+ "quietHours": {
62
+ "start": "22:00",
63
+ "end": "08:00"
64
+ },
65
+ "createdAt": "2026-01-01T00:00:00Z",
66
+ "consents": {
67
+ "promotional": true
68
+ }
69
+ },
70
+ "candidate": {
71
+ "id": "c1",
72
+ "type": "reminder",
73
+ "priority": "normal",
74
+ "surfaces": [
75
+ "push",
76
+ "feed"
77
+ ]
78
+ },
79
+ "now": "2026-09-04T06:30:00Z"
80
+ },
81
+ "expect": {
82
+ "allowed": true,
83
+ "trace": [
84
+ "consent:promotional",
85
+ "consent:band00to06",
86
+ "consent:band06to08",
87
+ "consent:band08to10",
88
+ "consent:band21to24"
89
+ ]
90
+ }
91
+ },
92
+ {
93
+ "description": "22:30 IST without the band ix opt-in rejects",
94
+ "input": {
95
+ "user": {
96
+ "id": "u1",
97
+ "consent": true,
98
+ "proactiveEnabled": true,
99
+ "mode": "normal",
100
+ "intensity": "normal",
101
+ "timezone": "Asia/Kolkata",
102
+ "quietHours": {
103
+ "start": "22:00",
104
+ "end": "08:00"
105
+ },
106
+ "createdAt": "2026-01-01T00:00:00Z",
107
+ "consents": {
108
+ "promotional": true
109
+ }
110
+ },
111
+ "candidate": {
112
+ "id": "c1",
113
+ "type": "reminder",
114
+ "priority": "normal",
115
+ "surfaces": [
116
+ "push",
117
+ "feed"
118
+ ]
119
+ },
120
+ "now": "2026-09-04T17:00:00Z"
121
+ },
122
+ "expect": {
123
+ "allowed": false,
124
+ "rejectedBy": "consent:band21to24",
125
+ "trace": [
126
+ "consent:promotional",
127
+ "consent:band00to06",
128
+ "consent:band06to08",
129
+ "consent:band08to10",
130
+ "consent:band21to24"
131
+ ]
132
+ }
133
+ },
134
+ {
135
+ "description": "22:30 IST with the band ix opt-in passes",
136
+ "input": {
137
+ "user": {
138
+ "id": "u1",
139
+ "consent": true,
140
+ "proactiveEnabled": true,
141
+ "mode": "normal",
142
+ "intensity": "normal",
143
+ "timezone": "Asia/Kolkata",
144
+ "quietHours": {
145
+ "start": "22:00",
146
+ "end": "08:00"
147
+ },
148
+ "createdAt": "2026-01-01T00:00:00Z",
149
+ "consents": {
150
+ "promotional": true,
151
+ "band21to24": true
152
+ }
153
+ },
154
+ "candidate": {
155
+ "id": "c1",
156
+ "type": "reminder",
157
+ "priority": "normal",
158
+ "surfaces": [
159
+ "push",
160
+ "feed"
161
+ ]
162
+ },
163
+ "now": "2026-09-04T17:00:00Z"
164
+ },
165
+ "expect": {
166
+ "allowed": true,
167
+ "trace": [
168
+ "consent:promotional",
169
+ "consent:band00to06",
170
+ "consent:band06to08",
171
+ "consent:band08to10",
172
+ "consent:band21to24"
173
+ ]
174
+ }
175
+ },
176
+ {
177
+ "description": "04:00 IST rejects when only band 06:00 to 08:00 was opted in",
178
+ "input": {
179
+ "user": {
180
+ "id": "u1",
181
+ "consent": true,
182
+ "proactiveEnabled": true,
183
+ "mode": "normal",
184
+ "intensity": "normal",
185
+ "timezone": "Asia/Kolkata",
186
+ "quietHours": {
187
+ "start": "22:00",
188
+ "end": "08:00"
189
+ },
190
+ "createdAt": "2026-01-01T00:00:00Z",
191
+ "consents": {
192
+ "promotional": true,
193
+ "band06to08": true
194
+ }
195
+ },
196
+ "candidate": {
197
+ "id": "c1",
198
+ "type": "reminder",
199
+ "priority": "normal",
200
+ "surfaces": [
201
+ "push",
202
+ "feed"
203
+ ]
204
+ },
205
+ "now": "2026-09-04T22:30:00Z"
206
+ },
207
+ "expect": {
208
+ "allowed": false,
209
+ "rejectedBy": "consent:band00to06",
210
+ "trace": [
211
+ "consent:promotional",
212
+ "consent:band00to06"
213
+ ]
214
+ }
215
+ },
216
+ {
217
+ "description": "09:00 IST without the band iii opt-in rejects",
218
+ "input": {
219
+ "user": {
220
+ "id": "u1",
221
+ "consent": true,
222
+ "proactiveEnabled": true,
223
+ "mode": "normal",
224
+ "intensity": "normal",
225
+ "timezone": "Asia/Kolkata",
226
+ "quietHours": {
227
+ "start": "22:00",
228
+ "end": "08:00"
229
+ },
230
+ "createdAt": "2026-01-01T00:00:00Z",
231
+ "consents": {
232
+ "promotional": true
233
+ }
234
+ },
235
+ "candidate": {
236
+ "id": "c1",
237
+ "type": "reminder",
238
+ "priority": "normal",
239
+ "surfaces": [
240
+ "push",
241
+ "feed"
242
+ ]
243
+ },
244
+ "now": "2026-09-04T03:30:00Z"
245
+ },
246
+ "expect": {
247
+ "allowed": false,
248
+ "rejectedBy": "consent:band08to10",
249
+ "trace": [
250
+ "consent:promotional",
251
+ "consent:band00to06",
252
+ "consent:band06to08",
253
+ "consent:band08to10"
254
+ ]
255
+ }
256
+ }
257
+ ]
258
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "presets/kakao-brand-message",
5
5
  "description": "kakaoBrandMessage runs 08:00 to 20:50 Korea time regardless of the recipient's zone.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "presets/kr-network-act-50",
5
5
  "description": "krNetworkAct50 needs ad consent always and night consent 21:00 to 08:00.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "presets/telegram-bot",
5
5
  "description": "telegramBot limits one message a second and twenty a minute per channel.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "presets/us-tcpa",
5
5
  "description": "usTcpa allows 08:00 to 21:00 in the user's zone.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "quiet-hours/apia",
5
5
  "description": "Pacific/Apia is UTC+13; 10:00Z is 23:00 local.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.1.0",
4
4
  "name": "quiet-hours/caller-supplied-dates",
5
5
  "description": "A calendar date the caller supplies beats the weekday and the default, in both directions. There is no bundled holiday calendar.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.1.0",
4
4
  "name": "quiet-hours/crosses-midnight-by-day",
5
5
  "description": "A window belongs to the day it opens on, so Friday 18:00 to 02:00 silences Saturday morning. Saturday has no window of its own, so the carry is the only thing that can explain the silence.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "quiet-hours/dst-new-york",
5
5
  "description": "On 2026-03-08 New York springs forward at 02:00; 06:30Z is 01:30 EST, 07:30Z is 03:30 EDT.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "quiet-hours/istanbul",
5
5
  "description": "Quiet hours cross midnight in the user's zone; the priority floor bypasses them.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "quiet-hours/wall-clock",
5
5
  "description": "now is in 2031; the outcome only holds when the implementation used the supplied instant, not the wall clock.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.1.0",
4
4
  "name": "quiet-hours/weekday-schedule",
5
5
  "description": "A Friday and Saturday weekend in Asia/Dubai (UTC+4, no daylight saving). A date beats a weekday beats the default, and null means the day has no quiet hours.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "shadow/reject-continues",
5
5
  "description": "A shadow check records its rejection and evaluation continues.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "trust-ramp/first-week",
5
5
  "description": "During the first days only high priority and above gets through.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "utility/bounded-deferral-cap",
5
5
  "description": "The bound caps t*.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "utility/bounded-deferral",
5
5
  "description": "boundedDeferral moves deliverAt by t* = min(bound, lambda * c / (2 * staleness)) when the candidate is busy.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec_version": "1.2.0",
2
+ "spec_version": "1.3.0",
3
3
  "since": "1.0.0",
4
4
  "name": "utility/floor",
5
5
  "description": "utilityFloor rejects when pAccept is below tau = cFA / (cFA + pNeed * cFN).",