@particle-academy/fancy-conformance 0.13.0 → 0.15.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,1434 @@
1
+ {
2
+ "$schema": "../../../schema/case-table.schema.json",
3
+ "suite": "expr/evaluate",
4
+ "cases": [
5
+ {
6
+ "id": "0101-simple-path",
7
+ "title": "A dot-path resolves.",
8
+ "since": "0.14.0",
9
+ "tags": [
10
+ "expr"
11
+ ],
12
+ "input": {
13
+ "expression": "in.content",
14
+ "context": {
15
+ "in": {
16
+ "content": "hello",
17
+ "count": 3,
18
+ "deal_id": "D-1",
19
+ "transcript": null,
20
+ "items": [
21
+ 1,
22
+ 2
23
+ ],
24
+ "empty": [],
25
+ "obj": {},
26
+ "flag": true,
27
+ "zero": 0,
28
+ "blank": ""
29
+ },
30
+ "user": {
31
+ "name": "Ada"
32
+ }
33
+ }
34
+ },
35
+ "expected": {
36
+ "ok": true,
37
+ "value": "hello"
38
+ }
39
+ },
40
+ {
41
+ "id": "0102-nested-path",
42
+ "title": "Nesting resolves.",
43
+ "since": "0.14.0",
44
+ "tags": [
45
+ "expr"
46
+ ],
47
+ "input": {
48
+ "expression": "user.name",
49
+ "context": {
50
+ "in": {
51
+ "content": "hello",
52
+ "count": 3,
53
+ "deal_id": "D-1",
54
+ "transcript": null,
55
+ "items": [
56
+ 1,
57
+ 2
58
+ ],
59
+ "empty": [],
60
+ "obj": {},
61
+ "flag": true,
62
+ "zero": 0,
63
+ "blank": ""
64
+ },
65
+ "user": {
66
+ "name": "Ada"
67
+ }
68
+ }
69
+ },
70
+ "expected": {
71
+ "ok": true,
72
+ "value": "Ada"
73
+ }
74
+ },
75
+ {
76
+ "id": "0103-absent-path-is-null",
77
+ "title": "An absent path is null, NOT an error.",
78
+ "since": "0.14.0",
79
+ "tags": [
80
+ "expr",
81
+ "the-point"
82
+ ],
83
+ "input": {
84
+ "expression": "in.nope",
85
+ "context": {
86
+ "in": {
87
+ "content": "hello",
88
+ "count": 3,
89
+ "deal_id": "D-1",
90
+ "transcript": null,
91
+ "items": [
92
+ 1,
93
+ 2
94
+ ],
95
+ "empty": [],
96
+ "obj": {},
97
+ "flag": true,
98
+ "zero": 0,
99
+ "blank": ""
100
+ },
101
+ "user": {
102
+ "name": "Ada"
103
+ }
104
+ }
105
+ },
106
+ "expected": {
107
+ "ok": true,
108
+ "value": null
109
+ },
110
+ "notes": "The legitimate null, and the one this grammar keeps. A path that is absent today may be present tomorrow; that is not a malformed expression and must never throw."
111
+ },
112
+ {
113
+ "id": "0104-bracket-index",
114
+ "title": "A bracket index resolves.",
115
+ "since": "0.14.0",
116
+ "tags": [
117
+ "expr"
118
+ ],
119
+ "input": {
120
+ "expression": "in.items[0]",
121
+ "context": {
122
+ "in": {
123
+ "content": "hello",
124
+ "count": 3,
125
+ "deal_id": "D-1",
126
+ "transcript": null,
127
+ "items": [
128
+ 1,
129
+ 2
130
+ ],
131
+ "empty": [],
132
+ "obj": {},
133
+ "flag": true,
134
+ "zero": 0,
135
+ "blank": ""
136
+ },
137
+ "user": {
138
+ "name": "Ada"
139
+ }
140
+ }
141
+ },
142
+ "expected": {
143
+ "ok": true,
144
+ "value": 1
145
+ }
146
+ },
147
+ {
148
+ "id": "0105-absent-through-null",
149
+ "title": "Walking through a null yields null rather than throwing.",
150
+ "since": "0.14.0",
151
+ "tags": [
152
+ "expr"
153
+ ],
154
+ "input": {
155
+ "expression": "in.transcript.anything",
156
+ "context": {
157
+ "in": {
158
+ "content": "hello",
159
+ "count": 3,
160
+ "deal_id": "D-1",
161
+ "transcript": null,
162
+ "items": [
163
+ 1,
164
+ 2
165
+ ],
166
+ "empty": [],
167
+ "obj": {},
168
+ "flag": true,
169
+ "zero": 0,
170
+ "blank": ""
171
+ },
172
+ "user": {
173
+ "name": "Ada"
174
+ }
175
+ }
176
+ },
177
+ "expected": {
178
+ "ok": true,
179
+ "value": null
180
+ }
181
+ },
182
+ {
183
+ "id": "0201-number",
184
+ "title": "A number literal.",
185
+ "since": "0.14.0",
186
+ "tags": [
187
+ "expr"
188
+ ],
189
+ "input": {
190
+ "expression": "42",
191
+ "context": {}
192
+ },
193
+ "expected": {
194
+ "ok": true,
195
+ "value": 42
196
+ }
197
+ },
198
+ {
199
+ "id": "0202-string",
200
+ "title": "A string literal.",
201
+ "since": "0.14.0",
202
+ "tags": [
203
+ "expr"
204
+ ],
205
+ "input": {
206
+ "expression": "'hi'",
207
+ "context": {}
208
+ },
209
+ "expected": {
210
+ "ok": true,
211
+ "value": "hi"
212
+ }
213
+ },
214
+ {
215
+ "id": "0203-true",
216
+ "title": "true.",
217
+ "since": "0.14.0",
218
+ "tags": [
219
+ "expr"
220
+ ],
221
+ "input": {
222
+ "expression": "true",
223
+ "context": {}
224
+ },
225
+ "expected": {
226
+ "ok": true,
227
+ "value": true
228
+ }
229
+ },
230
+ {
231
+ "id": "0204-false",
232
+ "title": "false.",
233
+ "since": "0.14.0",
234
+ "tags": [
235
+ "expr"
236
+ ],
237
+ "input": {
238
+ "expression": "false",
239
+ "context": {}
240
+ },
241
+ "expected": {
242
+ "ok": true,
243
+ "value": false
244
+ }
245
+ },
246
+ {
247
+ "id": "0205-null",
248
+ "title": "null.",
249
+ "since": "0.14.0",
250
+ "tags": [
251
+ "expr"
252
+ ],
253
+ "input": {
254
+ "expression": "null",
255
+ "context": {}
256
+ },
257
+ "expected": {
258
+ "ok": true,
259
+ "value": null
260
+ }
261
+ },
262
+ {
263
+ "id": "0301-empty-array-is-truthy",
264
+ "title": "An EMPTY ARRAY is truthy.",
265
+ "since": "0.14.0",
266
+ "tags": [
267
+ "expr",
268
+ "truthiness",
269
+ "deliberate"
270
+ ],
271
+ "input": {
272
+ "expression": "in.empty ? 'yes' : 'no'",
273
+ "context": {
274
+ "in": {
275
+ "content": "hello",
276
+ "count": 3,
277
+ "deal_id": "D-1",
278
+ "transcript": null,
279
+ "items": [
280
+ 1,
281
+ 2
282
+ ],
283
+ "empty": [],
284
+ "obj": {},
285
+ "flag": true,
286
+ "zero": 0,
287
+ "blank": ""
288
+ },
289
+ "user": {
290
+ "name": "Ada"
291
+ }
292
+ }
293
+ },
294
+ "expected": {
295
+ "ok": true,
296
+ "value": "yes"
297
+ },
298
+ "notes": "Deliberate, and against PHP's and Python's native instincts, both of which call an empty container false. The data arrived as JSON: an array that EXISTS is a value, and asking whether it is empty is what `.length` is for. Conflating the two turns 'did we get results?' into 'did the call succeed?'."
299
+ },
300
+ {
301
+ "id": "0302-empty-object-is-truthy",
302
+ "title": "An EMPTY OBJECT is truthy, for the same reason.",
303
+ "since": "0.14.0",
304
+ "tags": [
305
+ "expr",
306
+ "truthiness",
307
+ "deliberate"
308
+ ],
309
+ "input": {
310
+ "expression": "in.obj ? 'yes' : 'no'",
311
+ "context": {
312
+ "in": {
313
+ "content": "hello",
314
+ "count": 3,
315
+ "deal_id": "D-1",
316
+ "transcript": null,
317
+ "items": [
318
+ 1,
319
+ 2
320
+ ],
321
+ "empty": [],
322
+ "obj": {},
323
+ "flag": true,
324
+ "zero": 0,
325
+ "blank": ""
326
+ },
327
+ "user": {
328
+ "name": "Ada"
329
+ }
330
+ }
331
+ },
332
+ "expected": {
333
+ "ok": true,
334
+ "value": "yes"
335
+ }
336
+ },
337
+ {
338
+ "id": "0303-zero-is-falsy",
339
+ "title": "0 is falsy.",
340
+ "since": "0.14.0",
341
+ "tags": [
342
+ "expr",
343
+ "truthiness"
344
+ ],
345
+ "input": {
346
+ "expression": "in.zero ? 'yes' : 'no'",
347
+ "context": {
348
+ "in": {
349
+ "content": "hello",
350
+ "count": 3,
351
+ "deal_id": "D-1",
352
+ "transcript": null,
353
+ "items": [
354
+ 1,
355
+ 2
356
+ ],
357
+ "empty": [],
358
+ "obj": {},
359
+ "flag": true,
360
+ "zero": 0,
361
+ "blank": ""
362
+ },
363
+ "user": {
364
+ "name": "Ada"
365
+ }
366
+ }
367
+ },
368
+ "expected": {
369
+ "ok": true,
370
+ "value": "no"
371
+ }
372
+ },
373
+ {
374
+ "id": "0304-empty-string-is-falsy",
375
+ "title": "An empty string is falsy.",
376
+ "since": "0.14.0",
377
+ "tags": [
378
+ "expr",
379
+ "truthiness"
380
+ ],
381
+ "input": {
382
+ "expression": "in.blank ? 'yes' : 'no'",
383
+ "context": {
384
+ "in": {
385
+ "content": "hello",
386
+ "count": 3,
387
+ "deal_id": "D-1",
388
+ "transcript": null,
389
+ "items": [
390
+ 1,
391
+ 2
392
+ ],
393
+ "empty": [],
394
+ "obj": {},
395
+ "flag": true,
396
+ "zero": 0,
397
+ "blank": ""
398
+ },
399
+ "user": {
400
+ "name": "Ada"
401
+ }
402
+ }
403
+ },
404
+ "expected": {
405
+ "ok": true,
406
+ "value": "no"
407
+ }
408
+ },
409
+ {
410
+ "id": "0305-null-is-falsy",
411
+ "title": "null is falsy.",
412
+ "since": "0.14.0",
413
+ "tags": [
414
+ "expr",
415
+ "truthiness"
416
+ ],
417
+ "input": {
418
+ "expression": "in.transcript ? 'yes' : 'no'",
419
+ "context": {
420
+ "in": {
421
+ "content": "hello",
422
+ "count": 3,
423
+ "deal_id": "D-1",
424
+ "transcript": null,
425
+ "items": [
426
+ 1,
427
+ 2
428
+ ],
429
+ "empty": [],
430
+ "obj": {},
431
+ "flag": true,
432
+ "zero": 0,
433
+ "blank": ""
434
+ },
435
+ "user": {
436
+ "name": "Ada"
437
+ }
438
+ }
439
+ },
440
+ "expected": {
441
+ "ok": true,
442
+ "value": "no"
443
+ }
444
+ },
445
+ {
446
+ "id": "0401-or-returns-the-operand",
447
+ "title": "`||` returns the OPERAND, not a boolean.",
448
+ "since": "0.14.0",
449
+ "tags": [
450
+ "expr",
451
+ "short-circuit"
452
+ ],
453
+ "input": {
454
+ "expression": "in.transcript || in.content",
455
+ "context": {
456
+ "in": {
457
+ "content": "hello",
458
+ "count": 3,
459
+ "deal_id": "D-1",
460
+ "transcript": null,
461
+ "items": [
462
+ 1,
463
+ 2
464
+ ],
465
+ "empty": [],
466
+ "obj": {},
467
+ "flag": true,
468
+ "zero": 0,
469
+ "blank": ""
470
+ },
471
+ "user": {
472
+ "name": "Ada"
473
+ }
474
+ }
475
+ },
476
+ "expected": {
477
+ "ok": true,
478
+ "value": "hello"
479
+ },
480
+ "notes": "What makes `||` useful rather than merely true. The reported production case wrote exactly this shape to fall back from a missing transcript to the content, and got null."
481
+ },
482
+ {
483
+ "id": "0402-or-short-circuits",
484
+ "title": "`||` yields the left operand when truthy.",
485
+ "since": "0.14.0",
486
+ "tags": [
487
+ "expr"
488
+ ],
489
+ "input": {
490
+ "expression": "in.content || in.nope",
491
+ "context": {
492
+ "in": {
493
+ "content": "hello",
494
+ "count": 3,
495
+ "deal_id": "D-1",
496
+ "transcript": null,
497
+ "items": [
498
+ 1,
499
+ 2
500
+ ],
501
+ "empty": [],
502
+ "obj": {},
503
+ "flag": true,
504
+ "zero": 0,
505
+ "blank": ""
506
+ },
507
+ "user": {
508
+ "name": "Ada"
509
+ }
510
+ }
511
+ },
512
+ "expected": {
513
+ "ok": true,
514
+ "value": "hello"
515
+ }
516
+ },
517
+ {
518
+ "id": "0403-and-returns-the-operand",
519
+ "title": "`&&` returns the right operand when the left is truthy.",
520
+ "since": "0.14.0",
521
+ "tags": [
522
+ "expr"
523
+ ],
524
+ "input": {
525
+ "expression": "in.content && in.deal_id",
526
+ "context": {
527
+ "in": {
528
+ "content": "hello",
529
+ "count": 3,
530
+ "deal_id": "D-1",
531
+ "transcript": null,
532
+ "items": [
533
+ 1,
534
+ 2
535
+ ],
536
+ "empty": [],
537
+ "obj": {},
538
+ "flag": true,
539
+ "zero": 0,
540
+ "blank": ""
541
+ },
542
+ "user": {
543
+ "name": "Ada"
544
+ }
545
+ }
546
+ },
547
+ "expected": {
548
+ "ok": true,
549
+ "value": "D-1"
550
+ }
551
+ },
552
+ {
553
+ "id": "0404-and-short-circuits",
554
+ "title": "`&&` yields the left operand when falsy.",
555
+ "since": "0.14.0",
556
+ "tags": [
557
+ "expr"
558
+ ],
559
+ "input": {
560
+ "expression": "in.zero && in.content",
561
+ "context": {
562
+ "in": {
563
+ "content": "hello",
564
+ "count": 3,
565
+ "deal_id": "D-1",
566
+ "transcript": null,
567
+ "items": [
568
+ 1,
569
+ 2
570
+ ],
571
+ "empty": [],
572
+ "obj": {},
573
+ "flag": true,
574
+ "zero": 0,
575
+ "blank": ""
576
+ },
577
+ "user": {
578
+ "name": "Ada"
579
+ }
580
+ }
581
+ },
582
+ "expected": {
583
+ "ok": true,
584
+ "value": 0
585
+ }
586
+ },
587
+ {
588
+ "id": "0405-not",
589
+ "title": "`!` yields a boolean.",
590
+ "since": "0.14.0",
591
+ "tags": [
592
+ "expr"
593
+ ],
594
+ "input": {
595
+ "expression": "!in.transcript",
596
+ "context": {
597
+ "in": {
598
+ "content": "hello",
599
+ "count": 3,
600
+ "deal_id": "D-1",
601
+ "transcript": null,
602
+ "items": [
603
+ 1,
604
+ 2
605
+ ],
606
+ "empty": [],
607
+ "obj": {},
608
+ "flag": true,
609
+ "zero": 0,
610
+ "blank": ""
611
+ },
612
+ "user": {
613
+ "name": "Ada"
614
+ }
615
+ }
616
+ },
617
+ "expected": {
618
+ "ok": true,
619
+ "value": true
620
+ }
621
+ },
622
+ {
623
+ "id": "0406-the-reported-condition",
624
+ "title": "The exact condition that silently routed a production graph the wrong way.",
625
+ "since": "0.14.0",
626
+ "tags": [
627
+ "expr",
628
+ "regression",
629
+ "the-point"
630
+ ],
631
+ "input": {
632
+ "expression": "in.deal_id && in.deal_id !== ''",
633
+ "context": {
634
+ "in": {
635
+ "content": "hello",
636
+ "count": 3,
637
+ "deal_id": "D-1",
638
+ "transcript": null,
639
+ "items": [
640
+ 1,
641
+ 2
642
+ ],
643
+ "empty": [],
644
+ "obj": {},
645
+ "flag": true,
646
+ "zero": 0,
647
+ "blank": ""
648
+ },
649
+ "user": {
650
+ "name": "Ada"
651
+ }
652
+ }
653
+ },
654
+ "expected": {
655
+ "ok": true,
656
+ "value": true
657
+ },
658
+ "notes": "Verbatim from the report. It returned null, `branch` read that as false, and the graph took the wrong road on EVERY run while reporting success."
659
+ },
660
+ {
661
+ "id": "0501-strict-equal",
662
+ "title": "`===` compares by value without coercion.",
663
+ "since": "0.14.0",
664
+ "tags": [
665
+ "expr"
666
+ ],
667
+ "input": {
668
+ "expression": "in.count === 3",
669
+ "context": {
670
+ "in": {
671
+ "content": "hello",
672
+ "count": 3,
673
+ "deal_id": "D-1",
674
+ "transcript": null,
675
+ "items": [
676
+ 1,
677
+ 2
678
+ ],
679
+ "empty": [],
680
+ "obj": {},
681
+ "flag": true,
682
+ "zero": 0,
683
+ "blank": ""
684
+ },
685
+ "user": {
686
+ "name": "Ada"
687
+ }
688
+ }
689
+ },
690
+ "expected": {
691
+ "ok": true,
692
+ "value": true
693
+ }
694
+ },
695
+ {
696
+ "id": "0502-double-equals-is-the-same",
697
+ "title": "`==` is the SAME operator as `===`.",
698
+ "since": "0.14.0",
699
+ "tags": [
700
+ "expr",
701
+ "deliberate"
702
+ ],
703
+ "input": {
704
+ "expression": "in.count == 3",
705
+ "context": {
706
+ "in": {
707
+ "content": "hello",
708
+ "count": 3,
709
+ "deal_id": "D-1",
710
+ "transcript": null,
711
+ "items": [
712
+ 1,
713
+ 2
714
+ ],
715
+ "empty": [],
716
+ "obj": {},
717
+ "flag": true,
718
+ "zero": 0,
719
+ "blank": ""
720
+ },
721
+ "user": {
722
+ "name": "Ada"
723
+ }
724
+ }
725
+ },
726
+ "expected": {
727
+ "ok": true,
728
+ "value": true
729
+ },
730
+ "notes": "Both spellings are accepted because authors write both, and NEITHER coerces. A language with two equality operators that differ subtly generates bug reports, and there are three runtimes to keep in step."
731
+ },
732
+ {
733
+ "id": "0503-no-coercion",
734
+ "title": "A string is never equal to a number.",
735
+ "since": "0.14.0",
736
+ "tags": [
737
+ "expr",
738
+ "deliberate"
739
+ ],
740
+ "input": {
741
+ "expression": "'3' === in.count",
742
+ "context": {
743
+ "in": {
744
+ "content": "hello",
745
+ "count": 3,
746
+ "deal_id": "D-1",
747
+ "transcript": null,
748
+ "items": [
749
+ 1,
750
+ 2
751
+ ],
752
+ "empty": [],
753
+ "obj": {},
754
+ "flag": true,
755
+ "zero": 0,
756
+ "blank": ""
757
+ },
758
+ "user": {
759
+ "name": "Ada"
760
+ }
761
+ }
762
+ },
763
+ "expected": {
764
+ "ok": true,
765
+ "value": false
766
+ }
767
+ },
768
+ {
769
+ "id": "0504-not-equal",
770
+ "title": "`!==` negates it.",
771
+ "since": "0.14.0",
772
+ "tags": [
773
+ "expr"
774
+ ],
775
+ "input": {
776
+ "expression": "in.count !== 4",
777
+ "context": {
778
+ "in": {
779
+ "content": "hello",
780
+ "count": 3,
781
+ "deal_id": "D-1",
782
+ "transcript": null,
783
+ "items": [
784
+ 1,
785
+ 2
786
+ ],
787
+ "empty": [],
788
+ "obj": {},
789
+ "flag": true,
790
+ "zero": 0,
791
+ "blank": ""
792
+ },
793
+ "user": {
794
+ "name": "Ada"
795
+ }
796
+ }
797
+ },
798
+ "expected": {
799
+ "ok": true,
800
+ "value": true
801
+ }
802
+ },
803
+ {
804
+ "id": "0601-greater-than",
805
+ "title": "Numeric comparison.",
806
+ "since": "0.14.0",
807
+ "tags": [
808
+ "expr"
809
+ ],
810
+ "input": {
811
+ "expression": "in.count > 2",
812
+ "context": {
813
+ "in": {
814
+ "content": "hello",
815
+ "count": 3,
816
+ "deal_id": "D-1",
817
+ "transcript": null,
818
+ "items": [
819
+ 1,
820
+ 2
821
+ ],
822
+ "empty": [],
823
+ "obj": {},
824
+ "flag": true,
825
+ "zero": 0,
826
+ "blank": ""
827
+ },
828
+ "user": {
829
+ "name": "Ada"
830
+ }
831
+ }
832
+ },
833
+ "expected": {
834
+ "ok": true,
835
+ "value": true
836
+ }
837
+ },
838
+ {
839
+ "id": "0602-less-or-equal",
840
+ "title": "Inclusive comparison.",
841
+ "since": "0.14.0",
842
+ "tags": [
843
+ "expr"
844
+ ],
845
+ "input": {
846
+ "expression": "in.count <= 3",
847
+ "context": {
848
+ "in": {
849
+ "content": "hello",
850
+ "count": 3,
851
+ "deal_id": "D-1",
852
+ "transcript": null,
853
+ "items": [
854
+ 1,
855
+ 2
856
+ ],
857
+ "empty": [],
858
+ "obj": {},
859
+ "flag": true,
860
+ "zero": 0,
861
+ "blank": ""
862
+ },
863
+ "user": {
864
+ "name": "Ada"
865
+ }
866
+ }
867
+ },
868
+ "expected": {
869
+ "ok": true,
870
+ "value": true
871
+ }
872
+ },
873
+ {
874
+ "id": "0603-addition",
875
+ "title": "`+` on two numbers adds.",
876
+ "since": "0.14.0",
877
+ "tags": [
878
+ "expr"
879
+ ],
880
+ "input": {
881
+ "expression": "in.count + 1",
882
+ "context": {
883
+ "in": {
884
+ "content": "hello",
885
+ "count": 3,
886
+ "deal_id": "D-1",
887
+ "transcript": null,
888
+ "items": [
889
+ 1,
890
+ 2
891
+ ],
892
+ "empty": [],
893
+ "obj": {},
894
+ "flag": true,
895
+ "zero": 0,
896
+ "blank": ""
897
+ },
898
+ "user": {
899
+ "name": "Ada"
900
+ }
901
+ }
902
+ },
903
+ "expected": {
904
+ "ok": true,
905
+ "value": 4
906
+ }
907
+ },
908
+ {
909
+ "id": "0604-string-concat",
910
+ "title": "`+` with a string concatenates.",
911
+ "since": "0.14.0",
912
+ "tags": [
913
+ "expr"
914
+ ],
915
+ "input": {
916
+ "expression": "'n=' + in.count",
917
+ "context": {
918
+ "in": {
919
+ "content": "hello",
920
+ "count": 3,
921
+ "deal_id": "D-1",
922
+ "transcript": null,
923
+ "items": [
924
+ 1,
925
+ 2
926
+ ],
927
+ "empty": [],
928
+ "obj": {},
929
+ "flag": true,
930
+ "zero": 0,
931
+ "blank": ""
932
+ },
933
+ "user": {
934
+ "name": "Ada"
935
+ }
936
+ }
937
+ },
938
+ "expected": {
939
+ "ok": true,
940
+ "value": "n=3"
941
+ }
942
+ },
943
+ {
944
+ "id": "0605-unary-minus",
945
+ "title": "Unary minus.",
946
+ "since": "0.14.0",
947
+ "tags": [
948
+ "expr"
949
+ ],
950
+ "input": {
951
+ "expression": "-in.count",
952
+ "context": {
953
+ "in": {
954
+ "content": "hello",
955
+ "count": 3,
956
+ "deal_id": "D-1",
957
+ "transcript": null,
958
+ "items": [
959
+ 1,
960
+ 2
961
+ ],
962
+ "empty": [],
963
+ "obj": {},
964
+ "flag": true,
965
+ "zero": 0,
966
+ "blank": ""
967
+ },
968
+ "user": {
969
+ "name": "Ada"
970
+ }
971
+ }
972
+ },
973
+ "expected": {
974
+ "ok": true,
975
+ "value": -3
976
+ }
977
+ },
978
+ {
979
+ "id": "0701-ternary",
980
+ "title": "A ternary picks a branch.",
981
+ "since": "0.14.0",
982
+ "tags": [
983
+ "expr"
984
+ ],
985
+ "input": {
986
+ "expression": "in.count > 2 ? 'big' : 'small'",
987
+ "context": {
988
+ "in": {
989
+ "content": "hello",
990
+ "count": 3,
991
+ "deal_id": "D-1",
992
+ "transcript": null,
993
+ "items": [
994
+ 1,
995
+ 2
996
+ ],
997
+ "empty": [],
998
+ "obj": {},
999
+ "flag": true,
1000
+ "zero": 0,
1001
+ "blank": ""
1002
+ },
1003
+ "user": {
1004
+ "name": "Ada"
1005
+ }
1006
+ }
1007
+ },
1008
+ "expected": {
1009
+ "ok": true,
1010
+ "value": "big"
1011
+ }
1012
+ },
1013
+ {
1014
+ "id": "0702-object-literal",
1015
+ "title": "An object literal builds a value.",
1016
+ "since": "0.14.0",
1017
+ "tags": [
1018
+ "expr",
1019
+ "regression"
1020
+ ],
1021
+ "input": {
1022
+ "expression": "{ transcript: in.content }",
1023
+ "context": {
1024
+ "in": {
1025
+ "content": "hello",
1026
+ "count": 3,
1027
+ "deal_id": "D-1",
1028
+ "transcript": null,
1029
+ "items": [
1030
+ 1,
1031
+ 2
1032
+ ],
1033
+ "empty": [],
1034
+ "obj": {},
1035
+ "flag": true,
1036
+ "zero": 0,
1037
+ "blank": ""
1038
+ },
1039
+ "user": {
1040
+ "name": "Ada"
1041
+ }
1042
+ }
1043
+ },
1044
+ "expected": {
1045
+ "ok": true,
1046
+ "value": {
1047
+ "transcript": "hello"
1048
+ }
1049
+ },
1050
+ "notes": "The shape an agent authoring a graph reaches for first, and the one that used to yield null."
1051
+ },
1052
+ {
1053
+ "id": "0703-array-literal",
1054
+ "title": "An array literal builds a value.",
1055
+ "since": "0.14.0",
1056
+ "tags": [
1057
+ "expr"
1058
+ ],
1059
+ "input": {
1060
+ "expression": "[in.count, 'x']",
1061
+ "context": {
1062
+ "in": {
1063
+ "content": "hello",
1064
+ "count": 3,
1065
+ "deal_id": "D-1",
1066
+ "transcript": null,
1067
+ "items": [
1068
+ 1,
1069
+ 2
1070
+ ],
1071
+ "empty": [],
1072
+ "obj": {},
1073
+ "flag": true,
1074
+ "zero": 0,
1075
+ "blank": ""
1076
+ },
1077
+ "user": {
1078
+ "name": "Ada"
1079
+ }
1080
+ }
1081
+ },
1082
+ "expected": {
1083
+ "ok": true,
1084
+ "value": [
1085
+ 3,
1086
+ "x"
1087
+ ]
1088
+ }
1089
+ },
1090
+ {
1091
+ "id": "0704-parens",
1092
+ "title": "Parentheses group.",
1093
+ "since": "0.14.0",
1094
+ "tags": [
1095
+ "expr"
1096
+ ],
1097
+ "input": {
1098
+ "expression": "(1 + 2) * 1 === 3",
1099
+ "context": {
1100
+ "in": {
1101
+ "content": "hello",
1102
+ "count": 3,
1103
+ "deal_id": "D-1",
1104
+ "transcript": null,
1105
+ "items": [
1106
+ 1,
1107
+ 2
1108
+ ],
1109
+ "empty": [],
1110
+ "obj": {},
1111
+ "flag": true,
1112
+ "zero": 0,
1113
+ "blank": ""
1114
+ },
1115
+ "user": {
1116
+ "name": "Ada"
1117
+ }
1118
+ }
1119
+ },
1120
+ "expected": {
1121
+ "ok": true,
1122
+ "value": true
1123
+ }
1124
+ },
1125
+ {
1126
+ "id": "0801-unclosed-string",
1127
+ "title": "An unterminated string does not parse.",
1128
+ "since": "0.14.0",
1129
+ "tags": [
1130
+ "expr",
1131
+ "malformed",
1132
+ "the-point"
1133
+ ],
1134
+ "input": {
1135
+ "expression": "'oops",
1136
+ "context": {}
1137
+ },
1138
+ "expected": {
1139
+ "ok": false
1140
+ },
1141
+ "notes": "Malformed expressions must FAIL, never return null. A null here would be indistinguishable from an absent path, which is the entire defect this package exists to remove."
1142
+ },
1143
+ {
1144
+ "id": "0802-dangling-operator",
1145
+ "title": "A dangling operator does not parse.",
1146
+ "since": "0.14.0",
1147
+ "tags": [
1148
+ "expr",
1149
+ "malformed"
1150
+ ],
1151
+ "input": {
1152
+ "expression": "in.count &&",
1153
+ "context": {
1154
+ "in": {
1155
+ "content": "hello",
1156
+ "count": 3,
1157
+ "deal_id": "D-1",
1158
+ "transcript": null,
1159
+ "items": [
1160
+ 1,
1161
+ 2
1162
+ ],
1163
+ "empty": [],
1164
+ "obj": {},
1165
+ "flag": true,
1166
+ "zero": 0,
1167
+ "blank": ""
1168
+ },
1169
+ "user": {
1170
+ "name": "Ada"
1171
+ }
1172
+ }
1173
+ },
1174
+ "expected": {
1175
+ "ok": false
1176
+ }
1177
+ },
1178
+ {
1179
+ "id": "0803-unclosed-paren",
1180
+ "title": "An unclosed paren does not parse.",
1181
+ "since": "0.14.0",
1182
+ "tags": [
1183
+ "expr",
1184
+ "malformed"
1185
+ ],
1186
+ "input": {
1187
+ "expression": "(1 + 2",
1188
+ "context": {}
1189
+ },
1190
+ "expected": {
1191
+ "ok": false
1192
+ }
1193
+ },
1194
+ {
1195
+ "id": "0804-empty-expression",
1196
+ "title": "An empty expression does not parse.",
1197
+ "since": "0.14.0",
1198
+ "tags": [
1199
+ "expr",
1200
+ "malformed"
1201
+ ],
1202
+ "input": {
1203
+ "expression": "",
1204
+ "context": {}
1205
+ },
1206
+ "expected": {
1207
+ "ok": false
1208
+ }
1209
+ },
1210
+ {
1211
+ "id": "0805-no-function-calls",
1212
+ "title": "A call does not parse — this is not a programming language.",
1213
+ "since": "0.14.0",
1214
+ "tags": [
1215
+ "expr",
1216
+ "malformed",
1217
+ "sandbox"
1218
+ ],
1219
+ "input": {
1220
+ "expression": "count(in.items)",
1221
+ "context": {
1222
+ "in": {
1223
+ "content": "hello",
1224
+ "count": 3,
1225
+ "deal_id": "D-1",
1226
+ "transcript": null,
1227
+ "items": [
1228
+ 1,
1229
+ 2
1230
+ ],
1231
+ "empty": [],
1232
+ "obj": {},
1233
+ "flag": true,
1234
+ "zero": 0,
1235
+ "blank": ""
1236
+ },
1237
+ "user": {
1238
+ "name": "Ada"
1239
+ }
1240
+ }
1241
+ },
1242
+ "expected": {
1243
+ "ok": false
1244
+ },
1245
+ "notes": "Sandboxing is a security property, not a style preference: these expressions arrive from end users and from agents, over the wire. No calls, no host reach, in any implementation."
1246
+ },
1247
+ {
1248
+ "id": "0901-array-length",
1249
+ "title": "`.length` counts an array.",
1250
+ "since": "0.15.0",
1251
+ "tags": [
1252
+ "expr",
1253
+ "length"
1254
+ ],
1255
+ "input": {
1256
+ "expression": "in.items.length",
1257
+ "context": {
1258
+ "in": {
1259
+ "content": "hello",
1260
+ "count": 3,
1261
+ "deal_id": "D-1",
1262
+ "transcript": null,
1263
+ "items": [
1264
+ 1,
1265
+ 2
1266
+ ],
1267
+ "empty": [],
1268
+ "obj": {},
1269
+ "flag": true,
1270
+ "zero": 0,
1271
+ "blank": ""
1272
+ },
1273
+ "user": {
1274
+ "name": "Ada"
1275
+ }
1276
+ }
1277
+ },
1278
+ "expected": {
1279
+ "ok": true,
1280
+ "value": 2
1281
+ },
1282
+ "notes": "The one pseudo-property, and it is load-bearing: `[]` is TRUTHY (0301), so without `.length` a consumer has no way to ask whether a collection is empty. The grammar would assert that an array which exists is a value and then leave nobody able to test the thing they care about."
1283
+ },
1284
+ {
1285
+ "id": "0902-empty-array-length",
1286
+ "title": "An empty array has length 0 while still being truthy.",
1287
+ "since": "0.15.0",
1288
+ "tags": [
1289
+ "expr",
1290
+ "length"
1291
+ ],
1292
+ "input": {
1293
+ "expression": "in.empty.length",
1294
+ "context": {
1295
+ "in": {
1296
+ "content": "hello",
1297
+ "count": 3,
1298
+ "deal_id": "D-1",
1299
+ "transcript": null,
1300
+ "items": [
1301
+ 1,
1302
+ 2
1303
+ ],
1304
+ "empty": [],
1305
+ "obj": {},
1306
+ "flag": true,
1307
+ "zero": 0,
1308
+ "blank": ""
1309
+ },
1310
+ "user": {
1311
+ "name": "Ada"
1312
+ }
1313
+ }
1314
+ },
1315
+ "expected": {
1316
+ "ok": true,
1317
+ "value": 0
1318
+ },
1319
+ "notes": "The pair that makes the truthiness decision coherent: truthy AND countable. An implementation that made [] falsy would pass this row and fail 0301."
1320
+ },
1321
+ {
1322
+ "id": "0903-string-length",
1323
+ "title": "`.length` counts a string.",
1324
+ "since": "0.15.0",
1325
+ "tags": [
1326
+ "expr",
1327
+ "length"
1328
+ ],
1329
+ "input": {
1330
+ "expression": "in.content.length",
1331
+ "context": {
1332
+ "in": {
1333
+ "content": "hello",
1334
+ "count": 3,
1335
+ "deal_id": "D-1",
1336
+ "transcript": null,
1337
+ "items": [
1338
+ 1,
1339
+ 2
1340
+ ],
1341
+ "empty": [],
1342
+ "obj": {},
1343
+ "flag": true,
1344
+ "zero": 0,
1345
+ "blank": ""
1346
+ },
1347
+ "user": {
1348
+ "name": "Ada"
1349
+ }
1350
+ }
1351
+ },
1352
+ "expected": {
1353
+ "ok": true,
1354
+ "value": 5
1355
+ }
1356
+ },
1357
+ {
1358
+ "id": "0904-object-has-no-length",
1359
+ "title": "An object has NO `.length`.",
1360
+ "since": "0.15.0",
1361
+ "tags": [
1362
+ "expr",
1363
+ "length",
1364
+ "deliberate"
1365
+ ],
1366
+ "input": {
1367
+ "expression": "in.obj.length",
1368
+ "context": {
1369
+ "in": {
1370
+ "content": "hello",
1371
+ "count": 3,
1372
+ "deal_id": "D-1",
1373
+ "transcript": null,
1374
+ "items": [
1375
+ 1,
1376
+ 2
1377
+ ],
1378
+ "empty": [],
1379
+ "obj": {},
1380
+ "flag": true,
1381
+ "zero": 0,
1382
+ "blank": ""
1383
+ },
1384
+ "user": {
1385
+ "name": "Ada"
1386
+ }
1387
+ }
1388
+ },
1389
+ "expected": {
1390
+ "ok": true,
1391
+ "value": null
1392
+ },
1393
+ "notes": "Deliberate. There is no count three languages would agree on for an object, and an object used as a collection is rare enough not to justify inventing one. Null here is the ordinary absent-path answer, not a special case."
1394
+ },
1395
+ {
1396
+ "id": "0905-length-is-not-host-reach",
1397
+ "title": "No other host property is reachable.",
1398
+ "since": "0.15.0",
1399
+ "tags": [
1400
+ "expr",
1401
+ "length",
1402
+ "sandbox"
1403
+ ],
1404
+ "input": {
1405
+ "expression": "in.items.constructor",
1406
+ "context": {
1407
+ "in": {
1408
+ "content": "hello",
1409
+ "count": 3,
1410
+ "deal_id": "D-1",
1411
+ "transcript": null,
1412
+ "items": [
1413
+ 1,
1414
+ 2
1415
+ ],
1416
+ "empty": [],
1417
+ "obj": {},
1418
+ "flag": true,
1419
+ "zero": 0,
1420
+ "blank": ""
1421
+ },
1422
+ "user": {
1423
+ "name": "Ada"
1424
+ }
1425
+ }
1426
+ },
1427
+ "expected": {
1428
+ "ok": true,
1429
+ "value": null
1430
+ },
1431
+ "notes": "`.length` is a computed count, not an opening. Nothing else on the prototype chain resolves -- these expressions arrive from end users and from agents, over the wire."
1432
+ }
1433
+ ]
1434
+ }