@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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +15 -0
- package/README.md +996 -2
- package/bin/tram +1117 -0
- package/docs/behavioral-modeling-for-apis.md +749 -0
- package/docs/beta-status.md +29 -0
- package/docs/explainer.md +256 -0
- package/docs/images/tram-logo.png +0 -0
- package/docs/images/tram-test-run.png +0 -0
- package/docs/manifest-spec.md +1410 -0
- package/docs/quick-start.md +92 -0
- package/docs/roadmap.md +557 -0
- package/docs/tasks-api-tutorial.md +115 -0
- package/lib/assertions.js +918 -0
- package/package.json +47 -4
|
@@ -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
|
+
```
|