@mamund/tram 0.0.0-stage → 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.
@@ -0,0 +1,1410 @@
1
+ # TRAM Manifest Specification v0.2
2
+
3
+ ## Purpose
4
+
5
+ The TRAM manifest defines executable behavioral API tests.
6
+
7
+ The manifest is intentionally:
8
+
9
+ * human-readable
10
+ * machine-executable
11
+ * framework-independent
12
+ * declarative
13
+ * coaching-friendly
14
+
15
+ The manifest acts as both:
16
+
17
+ * executable configuration
18
+ * behavioral operational artifact
19
+
20
+ TRAM manifests are designed to support layered behavioral modeling for HTTP APIs.
21
+
22
+ TRAM manifests are intended to remain readable, reviewable operational artifacts even as implementation code evolves.
23
+
24
+ ---
25
+
26
+ # Behavioral layering
27
+
28
+ TRAM organizes behavioral testing into six progressive layers.
29
+
30
+ | Level | Focus | Question |
31
+ |---|---|---|
32
+ | 0 | Surface | Can the API be reached? |
33
+ | 1 | Shape | Do resources and affordances appear correctly? |
34
+ | 2 | Safe behavior | Do navigation, lookup, filtering, and query interactions behave correctly? |
35
+ | 3 | Unsafe behavior | Do isolated state-changing actions behave correctly? |
36
+ | 4 | Workflow | Can meaningful operational narratives be completed successfully? |
37
+ | 5 | Governance | Are policies, constraints, permissions, and semantic rules enforced correctly? |
38
+
39
+ The layers are additive.
40
+
41
+ Each layer answers a different behavioral question while narrowing debugging scope.
42
+
43
+ ---
44
+
45
+ # Runner execution
46
+
47
+ TRAM manifests are typically executed using the `tram` CLI.
48
+
49
+ Example:
50
+
51
+ ```bash
52
+ tram api-tests.json
53
+ ```
54
+
55
+ Manifest validation
56
+
57
+ TRAM validates manifests before execution. Validation can also be invoked directly from the command line:
58
+
59
+ ```bash
60
+ tram api-tests.json --validate
61
+ ```
62
+
63
+ Validation checks manifest structure, required fields, supported request methods, request body types, duplicate test identifiers, and other structural constraints. Validation does not contact the target API.
64
+
65
+ Verbose mode:
66
+
67
+ ```bash
68
+ tram api-tests.json --verbose
69
+ ```
70
+
71
+ Machine-readable report generation:
72
+
73
+ ```bash
74
+ tram api-tests.json --report results.json
75
+ ```
76
+
77
+ Reports are generated only for successfully validated manifests.
78
+
79
+ Typical execution flow:
80
+
81
+ ```text
82
+ manifest load
83
+ ↓
84
+ manifest validation
85
+ ↓
86
+ runtime execution
87
+ ↓
88
+ assertion evaluation
89
+ ↓
90
+ response capture
91
+ ↓
92
+ next request
93
+ ```
94
+
95
+ ---
96
+
97
+ # File format
98
+
99
+ TRAM manifests are JSON documents.
100
+
101
+ Typical filename:
102
+
103
+ ```text
104
+ api-tests.json
105
+ ```
106
+
107
+ Recommended layered filenames:
108
+
109
+ ```text
110
+ tram-level-0-surface-manifest.json
111
+ tram-level-1-shape-manifest.json
112
+ tram-level-2-behavior-safe-manifest.json
113
+ tram-level-3-behavior-unsafe-manifest.json
114
+ tram-level-4-workflow-manifest.json
115
+ tram-level-5-governance-manifest.json
116
+ ```
117
+
118
+ ---
119
+
120
+ # Top-level structure
121
+
122
+ ```json
123
+ {
124
+ "manifestVersion": "0.2",
125
+ "version": "1.0.0",
126
+ "name": "Task Management API Tests",
127
+ "description": "Defines a set of behavioral tests for a task-management API.",
128
+ "author": "Mike Amundsen",
129
+ "config": {
130
+ "baseUrl": "http://localhost:3000"
131
+ },
132
+ "data": {},
133
+ "tests": []
134
+ }
135
+ ```
136
+
137
+ ---
138
+
139
+ # Top-level properties
140
+
141
+ | Property | Required | Description |
142
+ |---|---|---|
143
+ | `manifestVersion` | No | Manifest specification version (`0.1` or `0.2`); the runner currently permits omission |
144
+ | `version` | No | Version of this manifest/test collection |
145
+ | `name` | Yes | Human-readable test collection name |
146
+ | `description` | No | Description of the collection |
147
+ | `author` | No | Manifest author |
148
+ | `config` | Yes | Runner configuration |
149
+ | `data` | No | Shared request/test data |
150
+ | `tests` | Yes | Array of test definitions |
151
+
152
+ ---
153
+
154
+ # Manifest validation
155
+
156
+ TRAM validates manifests before executing HTTP requests.
157
+
158
+ Validation currently includes:
159
+
160
+ * manifest JSON structure
161
+ * required top-level properties
162
+ * required test properties
163
+ * supported HTTP methods
164
+ * supported `bodyType` values
165
+ * duplicate test IDs
166
+
167
+ Invalid manifests fail before execution begins.
168
+
169
+ TRAM distinguishes between:
170
+
171
+ * manifest authoring failures
172
+ * request/runtime failures
173
+ * behavioral assertion failures
174
+
175
+ ---
176
+
177
+ # Config structure
178
+
179
+ Example:
180
+
181
+ ```json
182
+ "config": {
183
+ "baseUrl": "http://localhost:3000",
184
+ "timeoutMs": 5000,
185
+ "defaultHeaders": {
186
+ "accept": "application/json"
187
+ }
188
+ }
189
+ ```
190
+
191
+ ## Config properties
192
+
193
+ | Property | Required | Description |
194
+ |---|---|---|
195
+ | `baseUrl` | Yes | Base URL for all requests |
196
+ | `timeoutMs` | No | Request timeout in milliseconds |
197
+ | `defaultHeaders` | No | Headers added to all requests |
198
+
199
+ ---
200
+
201
+ # Test structure
202
+
203
+ Example:
204
+
205
+ ```json
206
+ {
207
+ "name": "Create task",
208
+ "description": "Create a valid task.",
209
+ "enabled": true,
210
+ "tags": ["create", "happy-path"],
211
+ "method": "POST",
212
+ "path": "/tasks",
213
+ "headers": {
214
+ "content-type": "application/json"
215
+ },
216
+ "bodyType": "json",
217
+ "body": "$data.task.valid",
218
+ "expect": {
219
+ "status": 201,
220
+ "headers": [
221
+ {
222
+ "name": "content-type",
223
+ "contains": "application/json"
224
+ }
225
+ ],
226
+ "body": [
227
+ {
228
+ "path": "$.status",
229
+ "equals": "active"
230
+ }
231
+ ]
232
+ }
233
+ }
234
+ ```
235
+
236
+ ---
237
+
238
+ # Test properties
239
+
240
+ | Property | Required | Description |
241
+ |---|---|---|
242
+ | `id` | No | Stable unique identifier for the test |
243
+ | `name` | Yes | Human-readable test name |
244
+ | `description` | No | Additional test explanation |
245
+ | `enabled` | No | Enable/disable test execution. Default: `true` |
246
+ | `tags` | No | Array of classification tags |
247
+ | `method` | Yes | HTTP method |
248
+ | `path` | Yes | Request path |
249
+ | `headers` | No | Request headers |
250
+ | `query` | No | Query parameter object |
251
+ | `bodyType` | No | Request body encoding |
252
+ | `body` | No | Request body or `$data` reference |
253
+ | `expect` | Yes | Expected response assertions |
254
+ | `capture` | No | Capture values from the response for use by later requests |
255
+
256
+ If present, test `id` values must be unique within a manifest.
257
+
258
+ Tests execute sequentially in manifest order.
259
+
260
+ ---
261
+
262
+ # Supported HTTP methods
263
+
264
+ ```text
265
+ GET
266
+ POST
267
+ PUT
268
+ PATCH
269
+ DELETE
270
+ HEAD
271
+ OPTIONS
272
+ ```
273
+
274
+ ---
275
+
276
+ # bodyType
277
+
278
+ Supported values:
279
+
280
+ ```text
281
+ json
282
+ form
283
+ text
284
+ ```
285
+
286
+ Default:
287
+
288
+ ```text
289
+ json
290
+ ```
291
+
292
+ ## JSON example
293
+
294
+ ```json
295
+ "bodyType": "json"
296
+ ```
297
+
298
+ Sends:
299
+
300
+ ```http
301
+ content-type: application/json
302
+ ```
303
+
304
+ ## Form example
305
+
306
+ ```json
307
+ "bodyType": "form"
308
+ ```
309
+
310
+ Sends:
311
+
312
+ ```http
313
+ content-type: application/x-www-form-urlencoded
314
+ ```
315
+
316
+ The body is encoded using:
317
+
318
+ ```text
319
+ URLSearchParams
320
+ ```
321
+
322
+ ## Text example
323
+
324
+ ```json
325
+ "bodyType": "text"
326
+ ```
327
+
328
+ ---
329
+
330
+ # Shared data
331
+
332
+ The `data` section stores reusable request/test data.
333
+
334
+ Example:
335
+
336
+ ```json
337
+ "data": {
338
+ "task": {
339
+ "valid": {
340
+ "task": {
341
+ "id": "${randomId}",
342
+ "title": "Buy milk",
343
+ "status": "active",
344
+ "priority": 3,
345
+ "assignedUser": "alice"
346
+ }
347
+ }
348
+ }
349
+ }
350
+ ```
351
+
352
+ Referenced using:
353
+
354
+ ```json
355
+ "body": "$data.task.valid"
356
+ ```
357
+
358
+ ---
359
+
360
+ # Runtime interpolation semantics
361
+
362
+ TRAM distinguishes between:
363
+
364
+ * object injection
365
+ * string interpolation
366
+
367
+ Use:
368
+
369
+ ```json
370
+ "$data.someObject"
371
+ ```
372
+
373
+ when injecting structured runtime objects.
374
+
375
+ Use:
376
+
377
+ ```json
378
+ "${data.someValue}"
379
+ ```
380
+
381
+ when interpolating values inside strings.
382
+
383
+ Correct object injection:
384
+
385
+ ```json
386
+ "body": "$data.createTask"
387
+ ```
388
+
389
+ Object injection may also be used inside assertions:
390
+
391
+ ```json
392
+ {
393
+ "path": "$.type",
394
+ "anyOf": "$data.validTypes"
395
+ }
396
+ ```
397
+
398
+ Correct string interpolation:
399
+
400
+ ```json
401
+ "path": "/tasks/${data.knownTaskId}"
402
+ ```
403
+
404
+ Captured response values:
405
+
406
+ ```json
407
+ "path": "/tasks/${capture.taskId}"
408
+ ```
409
+
410
+ Capture values become available only after the request that defines them has completed successfully.
411
+
412
+ Unresolved interpolation references fail manifest execution.
413
+
414
+ ---
415
+
416
+ # Response capture
417
+
418
+ The optional `capture` section records values observed in an HTTP response and makes them available to later requests.
419
+
420
+ Simple example:
421
+
422
+ ```json
423
+ "capture": {
424
+ "taskId": "body.id"
425
+ }
426
+ ```
427
+
428
+ Multiple values may be captured:
429
+
430
+ ```json
431
+ "capture": {
432
+ "taskId": "body.id",
433
+ "title": "body.title",
434
+ "self": "body._links.self.href"
435
+ }
436
+ ```
437
+
438
+ Optional captures use the extended form:
439
+
440
+ ```json
441
+ "capture": {
442
+ "etag": {
443
+ "from": "headers.etag",
444
+ "optional": true
445
+ }
446
+ }
447
+ ```
448
+
449
+ Supported capture sources:
450
+
451
+ | Source | Example |
452
+ |---|---|
453
+ | Response body | `body.id` |
454
+ | Nested body | `body._links.self.href` |
455
+ | Response headers | `headers.location` |
456
+ | HTTP status | `status` |
457
+ | Raw response body | `rawBody` |
458
+
459
+ Required captures cause the test to fail if the value cannot be observed. Optional captures are skipped when the value is absent.
460
+
461
+ ---
462
+
463
+ # Stable run-scoped variables
464
+
465
+ TRAM supports stable run-scoped values initialized once per test run.
466
+
467
+ Example:
468
+
469
+ ```json
470
+ "data": {
471
+ "stableId": "${randomId}"
472
+ }
473
+ ```
474
+
475
+ Then referenced later:
476
+
477
+ ```json
478
+ "path": "/tasks/${data.stableId}"
479
+ ```
480
+
481
+ The value remains stable throughout the current test run.
482
+
483
+ A new value is generated on the next execution.
484
+
485
+ ---
486
+
487
+ # Runtime tokens
488
+
489
+ Current runtime token support:
490
+
491
+ ```text
492
+ ${randomId}
493
+ ${timestamp}
494
+ ${uuid}
495
+ ${randomEmail}
496
+ ```
497
+
498
+ Example:
499
+
500
+ ```json
501
+ {
502
+ "id": "${randomId}"
503
+ }
504
+ ```
505
+
506
+ ## Runtime token behavior
507
+
508
+ ### Direct usage
509
+
510
+ Tokens used directly inside requests are generated per encounter.
511
+
512
+ Example:
513
+
514
+ ```json
515
+ {
516
+ "id": "${randomId}"
517
+ }
518
+ ```
519
+
520
+ Each occurrence generates a new value.
521
+
522
+ ### Run-scoped initialization
523
+
524
+ Tokens inside `data` initialize once per test run.
525
+
526
+ Example:
527
+
528
+ ```json
529
+ "data": {
530
+ "stableId": "${randomId}"
531
+ }
532
+ ```
533
+
534
+ All later references to:
535
+
536
+ ```json
537
+ "${data.stableId}"
538
+ ```
539
+
540
+ reuse the same generated value.
541
+
542
+ ---
543
+
544
+ # Expectations
545
+
546
+ Structure:
547
+
548
+ ```json
549
+ "expect": {
550
+ "status": 200,
551
+ "headers": [],
552
+ "body": []
553
+ }
554
+ ```
555
+
556
+ ---
557
+
558
+ # status
559
+
560
+ Simple HTTP status assertion.
561
+
562
+ Example:
563
+
564
+ ```json
565
+ "status": 200
566
+ ```
567
+
568
+ ---
569
+
570
+ # headers
571
+
572
+ Array of header assertions.
573
+
574
+ Header assertions use `name`.
575
+
576
+ Example:
577
+
578
+ ```json
579
+ "headers": [
580
+ {
581
+ "name": "content-type",
582
+ "contains": "application/json"
583
+ }
584
+ ]
585
+ ```
586
+
587
+ Do not use `path` for header assertions.
588
+
589
+ ---
590
+
591
+ # body
592
+
593
+ Array of response body assertions.
594
+
595
+ Body assertions operate on parsed JSON responses using JSONPath-like traversal.
596
+
597
+ Example:
598
+
599
+ ```json
600
+ "body": [
601
+ {
602
+ "path": "$.status",
603
+ "equals": "active"
604
+ }
605
+ ]
606
+ ```
607
+
608
+ ---
609
+
610
+ # Supported assertions
611
+
612
+ ```text
613
+ exists
614
+ equals
615
+ contains
616
+ oneOf
617
+ anyOf
618
+ allOf
619
+ noneOf
620
+ type
621
+ range
622
+ length
623
+ isArray
624
+ hasProperties
625
+ minLength
626
+ each
627
+ eachProperty
628
+ ```
629
+
630
+ ---
631
+
632
+ # Traversal semantics
633
+
634
+ TRAM distinguishes between arrays and object maps.
635
+
636
+ Use:
637
+
638
+ * `each` for arrays
639
+ * `eachProperty` for object maps
640
+
641
+ Examples:
642
+
643
+ ```json
644
+ [
645
+ {...},
646
+ {...}
647
+ ]
648
+ ```
649
+
650
+ ```text
651
+ => each
652
+ ```
653
+
654
+ ```json
655
+ {
656
+ "self": {...},
657
+ "edit": {...}
658
+ }
659
+ ```
660
+
661
+ ```text
662
+ => eachProperty
663
+ ```
664
+
665
+ TRAM also distinguishes between:
666
+
667
+ * `path` for structural traversal
668
+ * `property` for scalar leaf checks
669
+
670
+ Use `path` when:
671
+ - continuing traversal
672
+ - applying nested assertions
673
+ - re-entering the assertion engine
674
+
675
+ Use `property` when:
676
+ - checking direct scalar child values
677
+
678
+ ---
679
+
680
+ # Assertion modifiers
681
+
682
+ ## optional
683
+
684
+ Marks a property-oriented assertion as optional.
685
+
686
+ If the property is absent:
687
+
688
+ ```text
689
+ the assertion passes
690
+ ```
691
+
692
+ If the property exists:
693
+
694
+ ```text
695
+ the assertion must still validate successfully
696
+ ```
697
+
698
+ Default:
699
+
700
+ ```json
701
+ "optional": false
702
+ ```
703
+
704
+ Examples:
705
+
706
+ ```json
707
+ {
708
+ "path": "$",
709
+ "each": {
710
+ "property": "description",
711
+ "optional": true,
712
+ "type": "string"
713
+ }
714
+ }
715
+ ```
716
+
717
+ This assertion means:
718
+
719
+ ```text
720
+ "description" may be absent
721
+ if present, it must be a string
722
+ ```
723
+
724
+ Optional assertions also work inside nested `eachProperty` assertions.
725
+
726
+ Example:
727
+
728
+ ```json
729
+ {
730
+ "path": "$._links",
731
+ "eachProperty": {
732
+ "path": "$.title",
733
+ "optional": true,
734
+ "type": "string"
735
+ }
736
+ }
737
+ ```
738
+
739
+ ## Optional assertion scope
740
+
741
+ Optional assertions apply only to:
742
+
743
+ ```text
744
+ property-oriented assertions
745
+ ```
746
+
747
+ Current supported usage includes:
748
+
749
+ ```text
750
+ each.property.equals
751
+ each.property.contains
752
+ each.property.oneOf
753
+ each.property.anyOf
754
+ each.property.allOf
755
+ each.property.noneOf
756
+ each.property.type
757
+ each.property.range
758
+ each.property.length
759
+ each.property.minLength
760
+ nested eachProperty path assertions
761
+ ```
762
+
763
+ Optional assertions do not apply to:
764
+
765
+ ```text
766
+ status
767
+ exists
768
+ isArray
769
+ each
770
+ eachProperty
771
+ hasProperties
772
+ ```
773
+
774
+ ---
775
+
776
+ # Assertion reference
777
+
778
+ ## exists
779
+
780
+ Checks that a path exists.
781
+
782
+ Example:
783
+
784
+ ```json
785
+ {
786
+ "path": "$.id",
787
+ "exists": true
788
+ }
789
+ ```
790
+
791
+ ---
792
+
793
+ ## equals
794
+
795
+ Checks exact equality.
796
+
797
+ Example:
798
+
799
+ ```json
800
+ {
801
+ "path": "$.status",
802
+ "equals": "active"
803
+ }
804
+ ```
805
+
806
+ ---
807
+
808
+ ## contains
809
+
810
+ Checks substring or array membership.
811
+
812
+ Example:
813
+
814
+ ```json
815
+ {
816
+ "path": "$.title",
817
+ "contains": "milk"
818
+ }
819
+ ```
820
+
821
+ ---
822
+
823
+ ## oneOf
824
+
825
+ Checks that a scalar value matches one of several allowed values.
826
+
827
+ Example:
828
+
829
+ ```json
830
+ {
831
+ "path": "$.status",
832
+ "oneOf": ["active", "pending", "completed"]
833
+ }
834
+ ```
835
+
836
+ Rules:
837
+
838
+ ```text
839
+ oneOf applies to scalar values only
840
+ ```
841
+
842
+ ---
843
+
844
+ ## anyOf
845
+
846
+ Checks that an array contains at least one expected value.
847
+
848
+ ```json
849
+ {
850
+ "path": "$.type",
851
+ "anyOf": ["Fire","Flying"]
852
+ }
853
+ ```
854
+
855
+ ---
856
+
857
+ ## allOf
858
+
859
+ Checks that an array contains every expected value.
860
+
861
+ ```json
862
+ {
863
+ "path": "$.type",
864
+ "allOf": ["Fire","Flying"]
865
+ }
866
+ ```
867
+
868
+ ---
869
+
870
+ ## noneOf
871
+
872
+ Checks that an array contains none of the expected values.
873
+
874
+ ```json
875
+ {
876
+ "path": "$.type",
877
+ "noneOf": ["Water","Electric"]
878
+ }
879
+ ```
880
+
881
+ Rules:
882
+
883
+ ```text
884
+ anyOf applies only to arrays
885
+ allOf applies only to arrays
886
+ noneOf applies only to arrays
887
+ array members are compared using deep equality
888
+ ```
889
+
890
+ ---
891
+
892
+ ## type
893
+
894
+ Checks that a value matches a native JSON/JavaScript type.
895
+
896
+ Example:
897
+
898
+ ```json
899
+ {
900
+ "path": "$.id",
901
+ "type": "string"
902
+ }
903
+ ```
904
+
905
+ Supported values:
906
+
907
+ ```text
908
+ string
909
+ number
910
+ boolean
911
+ array
912
+ object
913
+ null
914
+ ```
915
+
916
+ Rules:
917
+
918
+ ```text
919
+ type checks native value categories only
920
+ semantic formats are intentionally excluded
921
+ ```
922
+
923
+ Out of scope:
924
+
925
+ ```text
926
+ uuid
927
+ email
928
+ uri
929
+ date-time
930
+ regex formats
931
+ schema validation
932
+ ```
933
+
934
+ ---
935
+
936
+ ## range
937
+
938
+ Checks that a numeric value falls within a valid range.
939
+
940
+ Example:
941
+
942
+ ```json
943
+ {
944
+ "path": "$.priority",
945
+ "range": {
946
+ "min": 1,
947
+ "max": 5
948
+ }
949
+ }
950
+ ```
951
+
952
+ Rules:
953
+
954
+ ```text
955
+ min optional
956
+ max optional
957
+ inclusive bounds
958
+ numeric values only
959
+ negative values supported
960
+ ```
961
+
962
+ ---
963
+
964
+ ## isArray
965
+
966
+ Checks that the selected value is an array.
967
+
968
+ Example:
969
+
970
+ ```json
971
+ {
972
+ "path": "$",
973
+ "isArray": true
974
+ }
975
+ ```
976
+
977
+ ---
978
+
979
+ ## hasProperties
980
+
981
+ Checks that an object contains required properties.
982
+
983
+ Example:
984
+
985
+ ```json
986
+ {
987
+ "path": "$",
988
+ "hasProperties": [
989
+ "id",
990
+ "title",
991
+ "status"
992
+ ]
993
+ }
994
+ ```
995
+
996
+ ---
997
+
998
+ ## length
999
+
1000
+ Checks exact, minimum, maximum, or bounded array/string length.
1001
+
1002
+ Exact length example:
1003
+
1004
+ ```json
1005
+ {
1006
+ "path": "$.items",
1007
+ "length": 3
1008
+ }
1009
+ ```
1010
+
1011
+ Minimum length example:
1012
+
1013
+ ```json
1014
+ {
1015
+ "path": "$.items",
1016
+ "length": {
1017
+ "min": 1
1018
+ }
1019
+ }
1020
+ ```
1021
+
1022
+ Maximum length example:
1023
+
1024
+ ```json
1025
+ {
1026
+ "path": "$.title",
1027
+ "length": {
1028
+ "max": 120
1029
+ }
1030
+ }
1031
+ ```
1032
+
1033
+ Bounded length example:
1034
+
1035
+ ```json
1036
+ {
1037
+ "path": "$.title",
1038
+ "length": {
1039
+ "min": 3,
1040
+ "max": 120
1041
+ }
1042
+ }
1043
+ ```
1044
+
1045
+ Rules:
1046
+
1047
+ ```text
1048
+ length applies only to arrays and strings
1049
+ numeric length values check exact length
1050
+ object length values support optional min and max
1051
+ bounds are inclusive
1052
+ objects, numbers, booleans, and null fail the assertion
1053
+ ```
1054
+
1055
+ ---
1056
+
1057
+ ## minLength
1058
+
1059
+ Deprecated. Use `length` with `min` instead.
1060
+
1061
+ Current form:
1062
+
1063
+ ```json
1064
+ {
1065
+ "path": "$",
1066
+ "minLength": 1
1067
+ }
1068
+ ```
1069
+
1070
+ Preferred form:
1071
+
1072
+ ```json
1073
+ {
1074
+ "path": "$",
1075
+ "length": {
1076
+ "min": 1
1077
+ }
1078
+ }
1079
+ ```
1080
+
1081
+ ---
1082
+
1083
+ ## each
1084
+
1085
+ Iterates over all elements of an array and applies assertions to each item.
1086
+
1087
+ Example:
1088
+
1089
+ ```json
1090
+ {
1091
+ "path": "$",
1092
+ "each": {
1093
+ "hasProperties": [
1094
+ "id",
1095
+ "title",
1096
+ "status"
1097
+ ]
1098
+ }
1099
+ }
1100
+ ```
1101
+
1102
+ Rules:
1103
+
1104
+ ```text
1105
+ each only operates on arrays
1106
+ non-array values fail the assertion
1107
+ ```
1108
+
1109
+ ### each.property
1110
+
1111
+ Applies assertions to a property on each array item.
1112
+
1113
+ Example:
1114
+
1115
+ ```json
1116
+ {
1117
+ "path": "$",
1118
+ "each": {
1119
+ "property": "status",
1120
+ "oneOf": [
1121
+ "active",
1122
+ "pending",
1123
+ "completed"
1124
+ ]
1125
+ }
1126
+ }
1127
+ ```
1128
+
1129
+ ### each.property.type
1130
+
1131
+ Applies type assertions to a property on each array item.
1132
+
1133
+ Example:
1134
+
1135
+ ```json
1136
+ {
1137
+ "path": "$",
1138
+ "each": {
1139
+ "property": "priority",
1140
+ "type": "number"
1141
+ }
1142
+ }
1143
+ ```
1144
+
1145
+ ### each.property.range
1146
+
1147
+ Applies range assertions to a property on each array item.
1148
+
1149
+ Example:
1150
+
1151
+ ```json
1152
+ {
1153
+ "path": "$",
1154
+ "each": {
1155
+ "property": "priority",
1156
+ "range": {
1157
+ "min": 1,
1158
+ "max": 5
1159
+ }
1160
+ }
1161
+ }
1162
+ ```
1163
+
1164
+ ### each.property.length
1165
+
1166
+ Applies length assertions to a string or array property on each array item.
1167
+
1168
+ Example:
1169
+
1170
+ ```json
1171
+ {
1172
+ "path": "$",
1173
+ "each": {
1174
+ "property": "code",
1175
+ "length": 2
1176
+ }
1177
+ }
1178
+ ```
1179
+
1180
+ Bounded length example:
1181
+
1182
+ ```json
1183
+ {
1184
+ "path": "$",
1185
+ "each": {
1186
+ "property": "title",
1187
+ "length": {
1188
+ "min": 3,
1189
+ "max": 120
1190
+ }
1191
+ }
1192
+ }
1193
+ ```
1194
+
1195
+ ---
1196
+
1197
+ ## eachProperty
1198
+
1199
+ Iterates over all properties in an object map and applies assertions to each property value.
1200
+
1201
+ Example:
1202
+
1203
+ ```json
1204
+ {
1205
+ "path": "$._links",
1206
+ "eachProperty": {
1207
+ "hasProperties": [
1208
+ "href",
1209
+ "method"
1210
+ ]
1211
+ }
1212
+ }
1213
+ ```
1214
+
1215
+ Rules:
1216
+
1217
+ ```text
1218
+ eachProperty only operates on object maps
1219
+ arrays fail the assertion
1220
+ primitive values fail the assertion
1221
+ ```
1222
+
1223
+ ---
1224
+
1225
+ # Nested assertions
1226
+
1227
+ Nested assertions are supported.
1228
+
1229
+ Example nested traversal:
1230
+
1231
+ ```json
1232
+ {
1233
+ "path": "$",
1234
+ "each": {
1235
+ "path": "$._links",
1236
+ "eachProperty": {
1237
+ "hasProperties": [
1238
+ "href",
1239
+ "method"
1240
+ ]
1241
+ }
1242
+ }
1243
+ }
1244
+ ```
1245
+
1246
+ This assertion verifies:
1247
+
1248
+ ```text
1249
+ for each record
1250
+ for each link relation
1251
+ ensure href and method exist
1252
+ ```
1253
+
1254
+ Additional example:
1255
+
1256
+ ```json
1257
+ {
1258
+ "path": "$._links",
1259
+ "eachProperty": {
1260
+ "path": "$.method",
1261
+ "oneOf": [
1262
+ "GET",
1263
+ "POST",
1264
+ "PUT",
1265
+ "PATCH",
1266
+ "DELETE"
1267
+ ]
1268
+ }
1269
+ }
1270
+ ```
1271
+
1272
+ ---
1273
+
1274
+ # Workflow-oriented behavioral modeling
1275
+
1276
+ TRAM manifests can model operational workflows rather than isolated endpoint checks.
1277
+
1278
+ TRAM models workflows through declarative sequencing rather than embedded scripting.
1279
+
1280
+ A workflow manifest may:
1281
+
1282
+ * create resources
1283
+ * retrieve intermediate state
1284
+ * apply mutations
1285
+ * verify accumulated final state
1286
+
1287
+ Example workflow sequence:
1288
+
1289
+ ```text
1290
+ create
1291
+ read after create
1292
+ edit
1293
+ update status
1294
+ assign user
1295
+ set due date
1296
+ read final accumulated state
1297
+ ```
1298
+
1299
+ Final-state verification example:
1300
+
1301
+ ```json
1302
+ {
1303
+ "path": "$.assignedUser",
1304
+ "equals": "${data.workflowAssigneeUpdate.task.assignedUser}"
1305
+ }
1306
+ ```
1307
+
1308
+ Workflow manifests should read like operational narratives.
1309
+
1310
+ ---
1311
+
1312
+ # Governance-oriented assertions
1313
+
1314
+ Governance assertions verify:
1315
+
1316
+ * required fields
1317
+ * allowed values
1318
+ * semantic legitimacy
1319
+ * ranges
1320
+ * error consistency
1321
+ * policy constraints
1322
+
1323
+ Example allowed-value assertion:
1324
+
1325
+ ```json
1326
+ {
1327
+ "path": "$.status",
1328
+ "oneOf": [
1329
+ "pending",
1330
+ "active",
1331
+ "cancelled",
1332
+ "completed"
1333
+ ]
1334
+ }
1335
+ ```
1336
+
1337
+ Example range assertion:
1338
+
1339
+ ```json
1340
+ {
1341
+ "path": "$.priority",
1342
+ "range": {
1343
+ "min": 1,
1344
+ "max": 5
1345
+ }
1346
+ }
1347
+ ```
1348
+
1349
+ Example length assertion:
1350
+
1351
+ ```json
1352
+ {
1353
+ "path": "$.title",
1354
+ "length": {
1355
+ "min": 3,
1356
+ "max": 120
1357
+ }
1358
+ }
1359
+ ```
1360
+
1361
+ Example array membership assertion:
1362
+
1363
+ ```json
1364
+ {
1365
+ "path": "$.roles",
1366
+ "allOf": [
1367
+ "read",
1368
+ "write"
1369
+ ]
1370
+ }
1371
+ ```
1372
+
1373
+ Example required-field failure assertion:
1374
+
1375
+ ```json
1376
+ {
1377
+ "path": "$.error",
1378
+ "contains": "title"
1379
+ }
1380
+ ```
1381
+
1382
+ ---
1383
+
1384
+ # Unsupported features
1385
+
1386
+ The current specification intentionally excludes:
1387
+
1388
+ ```text
1389
+ custom scripting
1390
+ dedicated setup/teardown orchestration
1391
+ parallel execution
1392
+ schema engines
1393
+ plugin systems
1394
+ browser automation
1395
+ ```
1396
+
1397
+ ---
1398
+
1399
+ # Design philosophy
1400
+
1401
+ The manifest design currently emphasizes:
1402
+
1403
+ ```text
1404
+ clarity
1405
+ behavior visibility
1406
+ predictability
1407
+ reviewability
1408
+ human understanding
1409
+ low-noise execution
1410
+ ```