restless-sdk 0.1.1 → 0.2.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.
@@ -0,0 +1,738 @@
1
+ {
2
+ "$generated": "GENERATED FILE - do not edit by hand. Inputs live in spec/cases.ts; run `npm run spec:vectors` to regenerate.",
3
+ "$contract": "spec/CONTRACT.md",
4
+ "specVersion": "1.1.0",
5
+ "cases": [
6
+ {
7
+ "id": "sampling/config-absent",
8
+ "requirement": "SAMPLE-014",
9
+ "op": "samplingConfig",
10
+ "input": {
11
+ "settings": null,
12
+ "code": null
13
+ },
14
+ "expected": {
15
+ "default": "on",
16
+ "routes": []
17
+ },
18
+ "note": "No config anywhere is default \"on\" with no routes: today's behaviour."
19
+ },
20
+ {
21
+ "id": "sampling/config-modes",
22
+ "requirement": "SAMPLE-001",
23
+ "op": "samplingConfig",
24
+ "input": {
25
+ "settings": null,
26
+ "code": {
27
+ "default": 0.25,
28
+ "routes": {
29
+ "GET /health": "off",
30
+ "GET /status": "errors",
31
+ "GET /pets": 0.1,
32
+ "* /pets/{id}": 0.5,
33
+ "DELETE /pets/{id}": "on"
34
+ }
35
+ }
36
+ },
37
+ "expected": {
38
+ "default": 0.25,
39
+ "routes": [
40
+ [
41
+ "* /pets/{id}",
42
+ 0.5
43
+ ],
44
+ [
45
+ "DELETE /pets/{id}",
46
+ "on"
47
+ ],
48
+ [
49
+ "GET /health",
50
+ "off"
51
+ ],
52
+ [
53
+ "GET /pets",
54
+ 0.1
55
+ ],
56
+ [
57
+ "GET /status",
58
+ "errors"
59
+ ]
60
+ ]
61
+ }
62
+ },
63
+ {
64
+ "id": "sampling/rate-quantized",
65
+ "requirement": "SAMPLE-002",
66
+ "op": "samplingConfig",
67
+ "input": {
68
+ "settings": null,
69
+ "code": {
70
+ "routes": {
71
+ "GET /a": 0.12345,
72
+ "GET /b": 0.00004,
73
+ "GET /c": 0.00005,
74
+ "GET /d": 0.99995,
75
+ "GET /e": 0.99994
76
+ }
77
+ }
78
+ },
79
+ "expected": {
80
+ "default": "on",
81
+ "routes": [
82
+ [
83
+ "GET /a",
84
+ 0.1235
85
+ ],
86
+ [
87
+ "GET /b",
88
+ 0
89
+ ],
90
+ [
91
+ "GET /c",
92
+ 0.0001
93
+ ],
94
+ [
95
+ "GET /d",
96
+ "on"
97
+ ],
98
+ [
99
+ "GET /e",
100
+ 0.9999
101
+ ]
102
+ ]
103
+ },
104
+ "note": "floor(r * 10000 + 0.5) / 10000. 0.00004 rounds to 0 (stubs only); 0.99995 rounds to 1 and becomes \"on\"."
105
+ },
106
+ {
107
+ "id": "sampling/rate-bounds",
108
+ "requirement": "SAMPLE-002",
109
+ "op": "samplingConfig",
110
+ "input": {
111
+ "settings": null,
112
+ "code": {
113
+ "routes": {
114
+ "GET /zero": 0,
115
+ "GET /one": 1
116
+ }
117
+ }
118
+ },
119
+ "expected": {
120
+ "default": "on",
121
+ "routes": [
122
+ [
123
+ "GET /one",
124
+ "on"
125
+ ],
126
+ [
127
+ "GET /zero",
128
+ 0
129
+ ]
130
+ ]
131
+ },
132
+ "note": "0 stays a rate (a stub for every success). 1 is spelled \"on\"."
133
+ },
134
+ {
135
+ "id": "sampling/invalid-modes",
136
+ "requirement": "SAMPLE-003",
137
+ "op": "samplingConfig",
138
+ "input": {
139
+ "settings": null,
140
+ "code": {
141
+ "default": "sometimes",
142
+ "routes": {
143
+ "GET /a": 1.5,
144
+ "GET /b": -0.1,
145
+ "GET /c": "OFF",
146
+ "GET /d": true,
147
+ "GET /e": null,
148
+ "GET /f": "0.5"
149
+ }
150
+ }
151
+ },
152
+ "expected": {
153
+ "default": "on",
154
+ "routes": [
155
+ [
156
+ "GET /a",
157
+ "on"
158
+ ],
159
+ [
160
+ "GET /b",
161
+ "on"
162
+ ],
163
+ [
164
+ "GET /c",
165
+ "on"
166
+ ],
167
+ [
168
+ "GET /d",
169
+ "on"
170
+ ],
171
+ [
172
+ "GET /e",
173
+ "on"
174
+ ],
175
+ [
176
+ "GET /f",
177
+ "on"
178
+ ]
179
+ ]
180
+ },
181
+ "note": "Every invalid mode degrades to \"on\", including near-misses: modes are case-sensitive and a numeric string is not a number."
182
+ },
183
+ {
184
+ "id": "sampling/invalid-shapes",
185
+ "requirement": "SAMPLE-003",
186
+ "op": "samplingConfig",
187
+ "input": {
188
+ "settings": [
189
+ "GET /a"
190
+ ],
191
+ "code": {
192
+ "routes": "off"
193
+ }
194
+ },
195
+ "expected": {
196
+ "default": "on",
197
+ "routes": []
198
+ },
199
+ "note": "A non-object config or routes value is ignored, never raised on."
200
+ },
201
+ {
202
+ "id": "sampling/key-normalized",
203
+ "requirement": "SAMPLE-011",
204
+ "op": "samplingConfig",
205
+ "input": {
206
+ "settings": null,
207
+ "code": {
208
+ "routes": {
209
+ "get /pets/:id": "off",
210
+ " POST /orders/:orderId/items/ ": 0.5,
211
+ "PUT /": "errors",
212
+ "PATCH //": "off"
213
+ }
214
+ }
215
+ },
216
+ "expected": {
217
+ "default": "on",
218
+ "routes": [
219
+ [
220
+ "GET /pets/{id}",
221
+ "off"
222
+ ],
223
+ [
224
+ "PATCH /",
225
+ "off"
226
+ ],
227
+ [
228
+ "POST /orders/{orderId}/items",
229
+ 0.5
230
+ ],
231
+ [
232
+ "PUT /",
233
+ "errors"
234
+ ]
235
+ ]
236
+ },
237
+ "note": "Method uppercased, :param becomes {param}, trailing slashes removed (but / stays /), surrounding and separating spaces collapsed."
238
+ },
239
+ {
240
+ "id": "sampling/key-invalid",
241
+ "requirement": "SAMPLE-012",
242
+ "op": "samplingConfig",
243
+ "input": {
244
+ "settings": null,
245
+ "code": {
246
+ "routes": {
247
+ "/no-method": "off",
248
+ "GET": "off",
249
+ "GET no-slash": "off",
250
+ "GET /has space": "off",
251
+ "G3T /x": "off",
252
+ "GET\t/tab": "off",
253
+ "GET /café": "off",
254
+ "* /ok": "off"
255
+ }
256
+ }
257
+ },
258
+ "expected": {
259
+ "default": "on",
260
+ "routes": [
261
+ [
262
+ "* /ok",
263
+ "off"
264
+ ]
265
+ ]
266
+ },
267
+ "note": "Only \"* /ok\" survives. Keys are printable ASCII so every SDK sorts and hashes them byte-for-byte alike."
268
+ },
269
+ {
270
+ "id": "sampling/key-duplicates",
271
+ "requirement": "SAMPLE-013",
272
+ "op": "samplingConfig",
273
+ "input": {
274
+ "settings": null,
275
+ "code": {
276
+ "routes": {
277
+ "GET /x": "off",
278
+ "get /x/": 0.5
279
+ }
280
+ }
281
+ },
282
+ "expected": {
283
+ "default": "on",
284
+ "routes": [
285
+ [
286
+ "GET /x",
287
+ 0.5
288
+ ]
289
+ ]
290
+ },
291
+ "note": "Two keys that normalize alike: the later one in source order wins."
292
+ },
293
+ {
294
+ "id": "sampling/merge",
295
+ "requirement": "SAMPLE-014",
296
+ "op": "samplingConfig",
297
+ "input": {
298
+ "settings": {
299
+ "default": "errors",
300
+ "routes": {
301
+ "GET /a": "off",
302
+ "GET /b": 0.5
303
+ }
304
+ },
305
+ "code": {
306
+ "routes": {
307
+ "GET /b": "on",
308
+ "GET /c": 0.1
309
+ }
310
+ }
311
+ },
312
+ "expected": {
313
+ "default": "errors",
314
+ "routes": [
315
+ [
316
+ "GET /a",
317
+ "off"
318
+ ],
319
+ [
320
+ "GET /b",
321
+ "on"
322
+ ],
323
+ [
324
+ "GET /c",
325
+ 0.1
326
+ ]
327
+ ]
328
+ },
329
+ "note": "Routes merge by key with code winning; settings' default survives because code set none."
330
+ },
331
+ {
332
+ "id": "sampling/merge-default",
333
+ "requirement": "SAMPLE-014",
334
+ "op": "samplingConfig",
335
+ "input": {
336
+ "settings": {
337
+ "default": "errors"
338
+ },
339
+ "code": {
340
+ "default": 0.5
341
+ }
342
+ },
343
+ "expected": {
344
+ "default": 0.5,
345
+ "routes": []
346
+ }
347
+ },
348
+ {
349
+ "id": "sampling/lookup-exact",
350
+ "requirement": "SAMPLE-020",
351
+ "op": "samplingMode",
352
+ "input": {
353
+ "code": {
354
+ "routes": {
355
+ "GET /health": "off",
356
+ "GET /status": "errors",
357
+ "GET /pets": 0.1,
358
+ "* /pets/{id}": 0.5,
359
+ "DELETE /pets/{id}": "on"
360
+ }
361
+ },
362
+ "method": "GET",
363
+ "route": "/health"
364
+ },
365
+ "expected": "off"
366
+ },
367
+ {
368
+ "id": "sampling/lookup-exact-beats-wildcard",
369
+ "requirement": "SAMPLE-020",
370
+ "op": "samplingMode",
371
+ "input": {
372
+ "code": {
373
+ "routes": {
374
+ "GET /health": "off",
375
+ "GET /status": "errors",
376
+ "GET /pets": 0.1,
377
+ "* /pets/{id}": 0.5,
378
+ "DELETE /pets/{id}": "on"
379
+ }
380
+ },
381
+ "method": "DELETE",
382
+ "route": "/pets/{id}"
383
+ },
384
+ "expected": "on"
385
+ },
386
+ {
387
+ "id": "sampling/lookup-wildcard",
388
+ "requirement": "SAMPLE-020",
389
+ "op": "samplingMode",
390
+ "input": {
391
+ "code": {
392
+ "routes": {
393
+ "GET /health": "off",
394
+ "GET /status": "errors",
395
+ "GET /pets": 0.1,
396
+ "* /pets/{id}": 0.5,
397
+ "DELETE /pets/{id}": "on"
398
+ }
399
+ },
400
+ "method": "PUT",
401
+ "route": "/pets/{id}"
402
+ },
403
+ "expected": 0.5
404
+ },
405
+ {
406
+ "id": "sampling/lookup-default",
407
+ "requirement": "SAMPLE-020",
408
+ "op": "samplingMode",
409
+ "input": {
410
+ "code": {
411
+ "default": "errors",
412
+ "routes": {
413
+ "GET /health": "off",
414
+ "GET /status": "errors",
415
+ "GET /pets": 0.1,
416
+ "* /pets/{id}": 0.5,
417
+ "DELETE /pets/{id}": "on"
418
+ }
419
+ },
420
+ "method": "GET",
421
+ "route": "/orders"
422
+ },
423
+ "expected": "errors"
424
+ },
425
+ {
426
+ "id": "sampling/lookup-colon-route",
427
+ "requirement": "SAMPLE-021",
428
+ "op": "samplingMode",
429
+ "input": {
430
+ "code": {
431
+ "routes": {
432
+ "GET /health": "off",
433
+ "GET /status": "errors",
434
+ "GET /pets": 0.1,
435
+ "* /pets/{id}": 0.5,
436
+ "DELETE /pets/{id}": "on"
437
+ }
438
+ },
439
+ "method": "PATCH",
440
+ "route": "/pets/:id/"
441
+ },
442
+ "expected": 0.5,
443
+ "note": "Koa and Hono report :param patterns. The request route is normalized exactly like a key."
444
+ },
445
+ {
446
+ "id": "sampling/lookup-lowercase-method",
447
+ "requirement": "SAMPLE-021",
448
+ "op": "samplingMode",
449
+ "input": {
450
+ "code": {
451
+ "routes": {
452
+ "GET /health": "off",
453
+ "GET /status": "errors",
454
+ "GET /pets": 0.1,
455
+ "* /pets/{id}": 0.5,
456
+ "DELETE /pets/{id}": "on"
457
+ }
458
+ },
459
+ "method": "get",
460
+ "route": "/health"
461
+ },
462
+ "expected": "off"
463
+ },
464
+ {
465
+ "id": "sampling/lookup-no-route",
466
+ "requirement": "SAMPLE-021",
467
+ "op": "samplingMode",
468
+ "input": {
469
+ "code": {
470
+ "default": 0.5,
471
+ "routes": {
472
+ "GET /health": "off",
473
+ "GET /status": "errors",
474
+ "GET /pets": 0.1,
475
+ "* /pets/{id}": 0.5,
476
+ "DELETE /pets/{id}": "on"
477
+ }
478
+ },
479
+ "method": "GET",
480
+ "route": null
481
+ },
482
+ "expected": 0.5,
483
+ "note": "No matched route (bare http, an unmatched 404): the default."
484
+ },
485
+ {
486
+ "id": "sampling/lookup-head-is-not-get",
487
+ "requirement": "SAMPLE-022",
488
+ "op": "samplingMode",
489
+ "input": {
490
+ "code": {
491
+ "routes": {
492
+ "GET /health": "off",
493
+ "GET /status": "errors",
494
+ "GET /pets": 0.1,
495
+ "* /pets/{id}": 0.5,
496
+ "DELETE /pets/{id}": "on"
497
+ }
498
+ },
499
+ "method": "HEAD",
500
+ "route": "/health"
501
+ },
502
+ "expected": "on",
503
+ "note": "No implicit HEAD-to-GET mapping. Use a * key to cover both."
504
+ },
505
+ {
506
+ "id": "sampling/lookup-no-config",
507
+ "requirement": "SAMPLE-020",
508
+ "op": "samplingMode",
509
+ "input": {
510
+ "method": "GET",
511
+ "route": "/anything"
512
+ },
513
+ "expected": "on"
514
+ },
515
+ {
516
+ "id": "sampling/decide-on",
517
+ "requirement": "SAMPLE-030",
518
+ "op": "samplingDecide",
519
+ "input": {
520
+ "mode": "on",
521
+ "status": 200,
522
+ "u": 0.99
523
+ },
524
+ "expected": "full"
525
+ },
526
+ {
527
+ "id": "sampling/decide-off-success",
528
+ "requirement": "SAMPLE-030",
529
+ "op": "samplingDecide",
530
+ "input": {
531
+ "mode": "off",
532
+ "status": 200,
533
+ "u": 0
534
+ },
535
+ "expected": "drop"
536
+ },
537
+ {
538
+ "id": "sampling/decide-off-error",
539
+ "requirement": "SAMPLE-030",
540
+ "op": "samplingDecide",
541
+ "input": {
542
+ "mode": "off",
543
+ "status": 500,
544
+ "u": 0
545
+ },
546
+ "expected": "drop",
547
+ "note": "\"off\" drops errors too. That is what \"errors\" is for."
548
+ },
549
+ {
550
+ "id": "sampling/decide-errors-399",
551
+ "requirement": "SAMPLE-030",
552
+ "op": "samplingDecide",
553
+ "input": {
554
+ "mode": "errors",
555
+ "status": 399,
556
+ "u": 0
557
+ },
558
+ "expected": "drop"
559
+ },
560
+ {
561
+ "id": "sampling/decide-errors-400",
562
+ "requirement": "SAMPLE-030",
563
+ "op": "samplingDecide",
564
+ "input": {
565
+ "mode": "errors",
566
+ "status": 400,
567
+ "u": 0.99
568
+ },
569
+ "expected": "full"
570
+ },
571
+ {
572
+ "id": "sampling/decide-rate-hit",
573
+ "requirement": "SAMPLE-031",
574
+ "op": "samplingDecide",
575
+ "input": {
576
+ "mode": 0.1,
577
+ "status": 200,
578
+ "u": 0.05
579
+ },
580
+ "expected": "full"
581
+ },
582
+ {
583
+ "id": "sampling/decide-rate-miss",
584
+ "requirement": "SAMPLE-031",
585
+ "op": "samplingDecide",
586
+ "input": {
587
+ "mode": 0.1,
588
+ "status": 200,
589
+ "u": 0.5
590
+ },
591
+ "expected": "stub"
592
+ },
593
+ {
594
+ "id": "sampling/decide-rate-boundary",
595
+ "requirement": "SAMPLE-031",
596
+ "op": "samplingDecide",
597
+ "input": {
598
+ "mode": 0.1,
599
+ "status": 200,
600
+ "u": 0.1
601
+ },
602
+ "expected": "stub",
603
+ "note": "Full iff u < rate, strictly. u == rate is a stub."
604
+ },
605
+ {
606
+ "id": "sampling/decide-rate-zero",
607
+ "requirement": "SAMPLE-031",
608
+ "op": "samplingDecide",
609
+ "input": {
610
+ "mode": 0,
611
+ "status": 200,
612
+ "u": 0
613
+ },
614
+ "expected": "stub",
615
+ "note": "Rate 0 never sends a success in full, even at u = 0."
616
+ },
617
+ {
618
+ "id": "sampling/decide-rate-error-bypass",
619
+ "requirement": "SAMPLE-030",
620
+ "op": "samplingDecide",
621
+ "input": {
622
+ "mode": 0,
623
+ "status": 404,
624
+ "u": 0.99
625
+ },
626
+ "expected": "full",
627
+ "note": "A rate never applies to errors."
628
+ },
629
+ {
630
+ "id": "sampling/decide-rate-399",
631
+ "requirement": "SAMPLE-030",
632
+ "op": "samplingDecide",
633
+ "input": {
634
+ "mode": 0.5,
635
+ "status": 399,
636
+ "u": 0.9
637
+ },
638
+ "expected": "stub"
639
+ },
640
+ {
641
+ "id": "sampling/hash-default-is-null",
642
+ "requirement": "SAMPLE-041",
643
+ "op": "samplingHash",
644
+ "input": {
645
+ "settings": null,
646
+ "code": {
647
+ "default": "on",
648
+ "routes": {}
649
+ }
650
+ },
651
+ "expected": {
652
+ "canonical": null,
653
+ "hash": null
654
+ },
655
+ "note": "An all-defaults config has nothing to report: no canonical form, no hash."
656
+ },
657
+ {
658
+ "id": "sampling/hash-routes-only",
659
+ "requirement": "SAMPLE-040",
660
+ "op": "samplingHash",
661
+ "input": {
662
+ "code": {
663
+ "routes": {
664
+ "GET /health": "off",
665
+ "GET /status": "errors",
666
+ "GET /pets": 0.1,
667
+ "* /pets/{id}": 0.5,
668
+ "DELETE /pets/{id}": "on"
669
+ }
670
+ }
671
+ },
672
+ "expected": {
673
+ "canonical": "default\ton\n* /pets/{id}\t0.5000\nDELETE /pets/{id}\ton\nGET /health\toff\nGET /pets\t0.1000\nGET /status\terrors\n",
674
+ "hash": "c6986c5a718af0f107de149e913bbaaaa3cc75413140707191363f95cc27f7eb"
675
+ }
676
+ },
677
+ {
678
+ "id": "sampling/hash-default-only",
679
+ "requirement": "SAMPLE-040",
680
+ "op": "samplingHash",
681
+ "input": {
682
+ "code": {
683
+ "default": "errors"
684
+ }
685
+ },
686
+ "expected": {
687
+ "canonical": "default\terrors\n",
688
+ "hash": "13fdc6fcde0e700a29e9207f70a81bbaea3f8d97544795e893b175a9a3107e8c"
689
+ }
690
+ },
691
+ {
692
+ "id": "sampling/hash-order-independent",
693
+ "requirement": "SAMPLE-040",
694
+ "op": "samplingHash",
695
+ "input": {
696
+ "code": {
697
+ "routes": {
698
+ "DELETE /pets/{id}": "on",
699
+ "* /pets/{id}": 0.5,
700
+ "GET /pets": 0.1,
701
+ "GET /status": "errors",
702
+ "GET /health": "off"
703
+ }
704
+ }
705
+ },
706
+ "expected": {
707
+ "canonical": "default\ton\n* /pets/{id}\t0.5000\nDELETE /pets/{id}\ton\nGET /health\toff\nGET /pets\t0.1000\nGET /status\terrors\n",
708
+ "hash": "c6986c5a718af0f107de149e913bbaaaa3cc75413140707191363f95cc27f7eb"
709
+ },
710
+ "note": "S_ROUTES in reverse. Must hash identically to sampling/hash-routes-only."
711
+ },
712
+ {
713
+ "id": "sampling/hash-spelling-independent",
714
+ "requirement": "SAMPLE-040",
715
+ "op": "samplingHash",
716
+ "input": {
717
+ "settings": {
718
+ "routes": {
719
+ "get /health/": "off",
720
+ "GET /pets": 0.10004
721
+ }
722
+ },
723
+ "code": {
724
+ "routes": {
725
+ "GET /status": "errors",
726
+ "* /pets/:id": 0.5,
727
+ "DELETE /pets/:id": 1
728
+ }
729
+ }
730
+ },
731
+ "expected": {
732
+ "canonical": "default\ton\n* /pets/{id}\t0.5000\nDELETE /pets/{id}\ton\nGET /health\toff\nGET /pets\t0.1000\nGET /status\terrors\n",
733
+ "hash": "c6986c5a718af0f107de149e913bbaaaa3cc75413140707191363f95cc27f7eb"
734
+ },
735
+ "note": "Different spelling, same normalized config: must hash identically to sampling/hash-routes-only."
736
+ }
737
+ ]
738
+ }