@openwop/spec-artifacts 2.44.5 → 2.44.7
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/CORPUS-STAMP.json +49 -47
- package/api/seams-v2.yaml +1 -1
- package/api/v2/asyncapi.yaml +1 -1
- package/api/v2/openapi.yaml +31 -122
- package/package.json +1 -1
- package/schemas/v2/agent-ref.schema.json +2 -3
- package/schemas/v2/budget-policy.schema.json +2 -3
- package/schemas/v2/capabilities.schema.json +42 -38
- package/schemas/v2/debug-bundle.schema.json +2 -2
- package/schemas/v2/error-envelope.schema.json +282 -4
- package/schemas/v2/node-pack-manifest.schema.json +2 -2
- package/schemas/v2/prompt-ref.schema.json +2 -3
- package/schemas/v2/prompt-template.schema.json +3 -3
- package/schemas/v2/run-event-payloads.schema.json +5 -5
- package/spec/v1/alias-detectors.json +25 -1
- package/spec/v1/core-standard-manifest.json +2 -2
- package/spec/v1/deprecations.json +89 -1
- package/spec/v1/gaps.json +46 -12
- package/spec/v1/migrations.json +57 -1
- package/spec/v2/core/artifact-type-packs.md +5 -1
- package/spec/v2/core/capabilities.md +20 -22
- package/spec/v2/core/conformance.md +24 -7
- package/spec/v2/core/errors.md +17 -119
- package/spec/v2/core/events.md +7 -7
- package/spec/v2/core/execution.md +56 -16
- package/spec/v2/core/host-services.md +57 -58
- package/spec/v2/core/i18n.md +10 -10
- package/spec/v2/core/idempotency.md +3 -3
- package/spec/v2/core/identity.md +11 -9
- package/spec/v2/core/interop.md +6 -8
- package/spec/v2/core/interrupt.md +8 -8
- package/spec/v2/core/oauth.md +4 -4
- package/spec/v2/core/packs.md +11 -5
- package/spec/v2/core/persistence.md +11 -13
- package/spec/v2/core/portability.md +4 -4
- package/spec/v2/core/replay.md +8 -10
- package/spec/v2/core/runs.md +20 -7
- package/spec/v2/core/security-defaults.md +0 -2
- package/spec/v2/core/storage.md +18 -20
- package/spec/v2/core/versioning.md +4 -9
- package/spec/v2/core/webhooks.md +13 -8
- package/spec/v2/declaration.json +3 -3
- package/spec/v2/errors.json +206 -2
- package/spec/v2/facets/agents.schema.json +367 -0
- package/spec/v2/facets/aiProviders.schema.json +2 -2
- package/spec/v2/generated/error-codes.md +130 -0
- package/spec/v2/release.json +2 -2
- package/spec/v2/surface-baseline.json +38 -2
package/spec/v1/gaps.json
CHANGED
|
@@ -7279,8 +7279,8 @@
|
|
|
7279
7279
|
"surface": "`openwop.requirement.replay.suppression-execution-ordinal` (openwop#1718) has no witness on any host, positive or negative. `conformance-replay-ordinal-loop` needs a host that both runs cycles and suppresses a side-effecting node in replay. The v2 reference host does neither for this fixture: it does not loop, it does not execute `core.conformance.side-effect`, and its `effectOutcome` (`store.ts:586` at openwop-examples `f56f092`) selects the latest outcome regardless of ordinal, which would fail the row if it looped. openwop-app does not index by ordinal (`nodeAttempts` is never reset), and MyndHyve does not seed `replaySourceOutcomes` from the prefix.",
|
|
7280
7280
|
"witness": "unclassified",
|
|
7281
7281
|
"requirementId": null,
|
|
7282
|
-
"disposition": "
|
|
7283
|
-
"target":
|
|
7282
|
+
"disposition": "closed",
|
|
7283
|
+
"target": null,
|
|
7284
7284
|
"sources": [
|
|
7285
7285
|
{
|
|
7286
7286
|
"file": "RFCS/registers/0140-replay-side-effect-suppression.gaps.md",
|
|
@@ -10688,6 +10688,23 @@
|
|
|
10688
10688
|
}
|
|
10689
10689
|
]
|
|
10690
10690
|
},
|
|
10691
|
+
{
|
|
10692
|
+
"id": "openwop.gap.0189.17",
|
|
10693
|
+
"rfc": "0189",
|
|
10694
|
+
"local": "G17",
|
|
10695
|
+
"section": "—",
|
|
10696
|
+
"surface": "`spec/v1/multi-agent-execution.md` (RFC 0039 MAE-2) names an opt-in `memoryScopeIsolation: \"isolated\"` field on the dispatch config, but no v1 schema ever defined it. Found while restating `multiAgent` levels 2–6 in `execution.md` (#1802).",
|
|
10697
|
+
"witness": "unclassified",
|
|
10698
|
+
"requirementId": null,
|
|
10699
|
+
"disposition": "closed",
|
|
10700
|
+
"target": null,
|
|
10701
|
+
"sources": [
|
|
10702
|
+
{
|
|
10703
|
+
"file": "RFCS/registers/0189-normative-home.gaps.md",
|
|
10704
|
+
"token": "G17"
|
|
10705
|
+
}
|
|
10706
|
+
]
|
|
10707
|
+
},
|
|
10691
10708
|
{
|
|
10692
10709
|
"id": "openwop.gap.0190.1",
|
|
10693
10710
|
"rfc": "0190",
|
|
@@ -13433,8 +13450,8 @@
|
|
|
13433
13450
|
"surface": "*(Renumbered from a second `G9` on 2026-09-28: two rows shared the local ID, so `spec/v1/gaps.json` carried `openwop.gap.0223.9` twice.)* `openwop.requirement.0223.reject-loopback-reasks` (openwop #1697) has no positive witness on any host. The v2 reference host does not run cycles. MyndHyve runs request-changes loops over condition edges on a successful gate output, and does not route a failed node over `any_failed` (its Cloud Run executor has no `triggerRule` handling), so the row stays `inapplicable` there. openwop-app rejects cycles. The row exists and is negatively controlled: registering the fixture on a host that does not loop records `executed-fail`.",
|
|
13434
13451
|
"witness": "unclassified",
|
|
13435
13452
|
"requirementId": null,
|
|
13436
|
-
"disposition": "
|
|
13437
|
-
"target":
|
|
13453
|
+
"disposition": "closed",
|
|
13454
|
+
"target": null,
|
|
13438
13455
|
"sources": [
|
|
13439
13456
|
{
|
|
13440
13457
|
"file": "RFCS/registers/0223-approval-reject-disposition.gaps.md",
|
|
@@ -13510,6 +13527,23 @@
|
|
|
13510
13527
|
}
|
|
13511
13528
|
]
|
|
13512
13529
|
},
|
|
13530
|
+
{
|
|
13531
|
+
"id": "openwop.gap.0227.1",
|
|
13532
|
+
"rfc": "0227",
|
|
13533
|
+
"local": "G1",
|
|
13534
|
+
"section": "§A",
|
|
13535
|
+
"surface": "Other registries with generated restatements inside `spec/v2/core/` (for example `headers.md`, which `derive-v2-api.py` writes) are not moved by this RFC.",
|
|
13536
|
+
"witness": "unclassified",
|
|
13537
|
+
"requirementId": null,
|
|
13538
|
+
"disposition": "closed",
|
|
13539
|
+
"target": null,
|
|
13540
|
+
"sources": [
|
|
13541
|
+
{
|
|
13542
|
+
"file": "RFCS/registers/0227-generated-registry-tables-outside-the-budget.gaps.md",
|
|
13543
|
+
"token": "G1"
|
|
13544
|
+
}
|
|
13545
|
+
]
|
|
13546
|
+
},
|
|
13513
13547
|
{
|
|
13514
13548
|
"id": "openwop.gap.0228.1",
|
|
13515
13549
|
"rfc": "0228",
|
|
@@ -13519,7 +13553,7 @@
|
|
|
13519
13553
|
"witness": "unclassified",
|
|
13520
13554
|
"requirementId": null,
|
|
13521
13555
|
"disposition": "externally-gated",
|
|
13522
|
-
"target": "
|
|
13556
|
+
"target": "myndhyve-host-cut",
|
|
13523
13557
|
"sources": [
|
|
13524
13558
|
{
|
|
13525
13559
|
"file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
|
|
@@ -13536,7 +13570,7 @@
|
|
|
13536
13570
|
"witness": "unclassified",
|
|
13537
13571
|
"requirementId": null,
|
|
13538
13572
|
"disposition": "externally-gated",
|
|
13539
|
-
"target": "
|
|
13573
|
+
"target": "v2-reference-recut",
|
|
13540
13574
|
"sources": [
|
|
13541
13575
|
{
|
|
13542
13576
|
"file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
|
|
@@ -13553,7 +13587,7 @@
|
|
|
13553
13587
|
"witness": "unclassified",
|
|
13554
13588
|
"requirementId": null,
|
|
13555
13589
|
"disposition": "externally-gated",
|
|
13556
|
-
"target": "
|
|
13590
|
+
"target": "openwop-app-host-cut",
|
|
13557
13591
|
"sources": [
|
|
13558
13592
|
{
|
|
13559
13593
|
"file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
|
|
@@ -13570,7 +13604,7 @@
|
|
|
13570
13604
|
"witness": "unclassified",
|
|
13571
13605
|
"requirementId": null,
|
|
13572
13606
|
"disposition": "externally-gated",
|
|
13573
|
-
"target": "
|
|
13607
|
+
"target": "v2-host-service-ports",
|
|
13574
13608
|
"sources": [
|
|
13575
13609
|
{
|
|
13576
13610
|
"file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
|
|
@@ -13604,7 +13638,7 @@
|
|
|
13604
13638
|
"witness": "unclassified",
|
|
13605
13639
|
"requirementId": null,
|
|
13606
13640
|
"disposition": "externally-gated",
|
|
13607
|
-
"target": "
|
|
13641
|
+
"target": "openwop-app-host-cut",
|
|
13608
13642
|
"sources": [
|
|
13609
13643
|
{
|
|
13610
13644
|
"file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
|
|
@@ -13621,7 +13655,7 @@
|
|
|
13621
13655
|
"witness": "unclassified",
|
|
13622
13656
|
"requirementId": null,
|
|
13623
13657
|
"disposition": "externally-gated",
|
|
13624
|
-
"target": "
|
|
13658
|
+
"target": "myndhyve-host-cut",
|
|
13625
13659
|
"sources": [
|
|
13626
13660
|
{
|
|
13627
13661
|
"file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
|
|
@@ -13637,8 +13671,8 @@
|
|
|
13637
13671
|
"surface": "`api/v2/openapi.yaml` is derived from the v1 document by `scripts/derive-v2-api.py`, which copies v1's `501` responses and the descriptions naming unregistered codes.",
|
|
13638
13672
|
"witness": "unclassified",
|
|
13639
13673
|
"requirementId": null,
|
|
13640
|
-
"disposition": "
|
|
13641
|
-
"target":
|
|
13674
|
+
"disposition": "closed",
|
|
13675
|
+
"target": null,
|
|
13642
13676
|
"sources": [
|
|
13643
13677
|
{
|
|
13644
13678
|
"file": "RFCS/registers/0228-v1-host-service-error-codes.gaps.md",
|
package/spec/v1/migrations.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./migrations.schema.json",
|
|
3
3
|
"version": 1,
|
|
4
|
-
"updated": "2026-09-
|
|
4
|
+
"updated": "2026-09-29",
|
|
5
5
|
"rows": [
|
|
6
6
|
{
|
|
7
7
|
"id": "openwop.migration.C2.1",
|
|
@@ -485,6 +485,44 @@
|
|
|
485
485
|
"deprecationId": null,
|
|
486
486
|
"note": "A host emitting it fails v2 schema validation; MyndHyve drops it in its Phase 4 leg."
|
|
487
487
|
},
|
|
488
|
+
{
|
|
489
|
+
"id": "openwop.migration.C2.11",
|
|
490
|
+
"child": "C2",
|
|
491
|
+
"kind": "retype",
|
|
492
|
+
"v1": {
|
|
493
|
+
"surface": "`aiProviders.selfHosted[]`, `realtimeVoice.{transcription, synthesis, turnDetection, bargeIn}`, `promptPrefixCache.{supported, providers[]}`, per-provider `authModes` map",
|
|
494
|
+
"path": "spec/v1/capabilities.md"
|
|
495
|
+
},
|
|
496
|
+
"v2": {
|
|
497
|
+
"surface": "`selfHosted`, `realtimeVoice`, `promptPrefixCache` booleans; `authModes` one list of modes (spec/v2/core/host-services.md §aiProviders)",
|
|
498
|
+
"path": "schemas/v2/capabilities.schema.json"
|
|
499
|
+
},
|
|
500
|
+
"codemod": "openwop.codemod.ai-providers-v2-narrow",
|
|
501
|
+
"persistedData": "not-persisted",
|
|
502
|
+
"requirementIds": [],
|
|
503
|
+
"gapIds": [],
|
|
504
|
+
"deprecationId": "openwop.deprecation.ai-providers-per-provider-facets",
|
|
505
|
+
"note": "Recorded 2026-09-28: v2 accepts the narrowing. Seven v1 rules with no v2 seat retire: `selfHosted[]` ⊆ `supported[]` (capabilities.md A.1); per-provider `authModes` rules 1, 2, 3, 5 and 7 (keys ⊆ `supported`, `apiKey` and `subscription` ⇒ `byok`, `[\"none\"]` ⇒ not `byok`, the absent-map defaults); the `promptPrefixCache.providers` list (v2: per routed provider); and the `realtimeVoice` sub-flags (v2: one boolean)."
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
"id": "openwop.migration.C2.12",
|
|
509
|
+
"child": "C2",
|
|
510
|
+
"kind": "rename",
|
|
511
|
+
"v1": {
|
|
512
|
+
"surface": "`aiProviders.supported` (the providers the host's AI proxy routes to)",
|
|
513
|
+
"path": "spec/v1/capabilities.md"
|
|
514
|
+
},
|
|
515
|
+
"v2": {
|
|
516
|
+
"surface": "`aiProviders.providers` (spec/v2/core/host-services.md §aiProviders)",
|
|
517
|
+
"path": "schemas/v2/capabilities.schema.json"
|
|
518
|
+
},
|
|
519
|
+
"codemod": "openwop.codemod.ai-providers-v2-narrow",
|
|
520
|
+
"persistedData": "not-persisted",
|
|
521
|
+
"requirementIds": [],
|
|
522
|
+
"gapIds": [],
|
|
523
|
+
"deprecationId": "openwop.deprecation.ai-providers-supported-rename",
|
|
524
|
+
"note": "Recorded 2026-09-29: the v2 facet renamed the list without a row. Same values, same meaning; `byok` stays a subset of it. The codemod refuses a record carrying both names with different values."
|
|
525
|
+
},
|
|
488
526
|
{
|
|
489
527
|
"id": "openwop.migration.C5.3",
|
|
490
528
|
"child": "C5",
|
|
@@ -1057,6 +1095,24 @@
|
|
|
1057
1095
|
"deprecationId": "openwop.deprecation.interrupt-locale-field",
|
|
1058
1096
|
"note": "Recorded 2026-09-27: the v2 schema closure (C4.9) left the v1 field no place, and it is dropped rather than re-added. A v1 `interrupt.requested` event carrying `data.locale` is read through the C.9 adapter like any other extra key."
|
|
1059
1097
|
},
|
|
1098
|
+
{
|
|
1099
|
+
"id": "openwop.migration.C4.18",
|
|
1100
|
+
"child": "C4",
|
|
1101
|
+
"kind": "remove",
|
|
1102
|
+
"v1": {
|
|
1103
|
+
"surface": "`RunSnapshot.parentRunId` and `RunSnapshot.parentNodeId` on a `core.subWorkflow` child run (node-packs.md §Parent linkage)",
|
|
1104
|
+
"path": "spec/v1/node-packs.md"
|
|
1105
|
+
},
|
|
1106
|
+
"v2": {
|
|
1107
|
+
"surface": "none on the child snapshot — `parentRunId` is fork lineage; the child's parent is `getRunAncestry` `parent.runId` with `cause: \"core.subWorkflow\"` (spec/v2/core/execution.md §subWorkflow)"
|
|
1108
|
+
},
|
|
1109
|
+
"codemod": "openwop.codemod.subworkflow-parent-link-drop",
|
|
1110
|
+
"persistedData": "translated",
|
|
1111
|
+
"requirementIds": [],
|
|
1112
|
+
"gapIds": [],
|
|
1113
|
+
"deprecationId": "openwop.deprecation.subworkflow-parent-link-fields",
|
|
1114
|
+
"note": "Recorded 2026-09-28. Ancestry names the parent run, not the dispatching node; that node is the one whose `node.completed` carries the child's id in `outputs.childRunId`. A host migrating a persisted v1 child keeps the link in whatever backs its ancestry answer before the codemod drops the fields."
|
|
1115
|
+
},
|
|
1060
1116
|
{
|
|
1061
1117
|
"id": "openwop.migration.C6.1",
|
|
1062
1118
|
"child": "C6",
|
|
@@ -16,7 +16,11 @@ The schema at `schemaRef`, inside the signed tarball, is the source of truth. It
|
|
|
16
16
|
- A host advertising `artifactTypes` SHOULD serve each installed type's schema there, and MUST for a host-registered type whose `schemaVersion` it advertises.
|
|
17
17
|
- A tarball copy and a served copy of one `(artifactTypeId, schemaVersion)` MUST be byte-identical.
|
|
18
18
|
|
|
19
|
-
At registry publish and at install, a host
|
|
19
|
+
At registry publish and at install, a host (invariant `artifact-schema-compile-bounded`):
|
|
20
|
+
|
|
21
|
+
- MUST reject, with `pack_validation_failed`, an artifact schema exceeding its bounds on serialized size, `$ref` depth or keyword/subschema count;
|
|
22
|
+
- MUST compile it under a wall-clock timeout;
|
|
23
|
+
- SHOULD reject a `pattern` it cannot evaluate in linear time.
|
|
20
24
|
|
|
21
25
|
## Registration
|
|
22
26
|
|
|
@@ -115,15 +115,15 @@ Operation paths live in `spec/v2/path-manifest.json` ([`versioning.md`](versioni
|
|
|
115
115
|
|
|
116
116
|
Each heading below is a `spec/v2/declaration.json` row with `anchor: core`. `scripts/check-declaration.mjs` MUST fail when a heading here, a root key in the generated schema, or a pack peer-dependency identifier names a family the declaration does not. The peer-dependency identifier is identical to the key ([`packs.md`](packs.md)).
|
|
117
117
|
|
|
118
|
-
Under each heading: the family's witness class
|
|
118
|
+
Under each heading: the family's witness class. Its facets are named in its normative home (§2 says where they are decided). Maturity axes are §8; the owning RFC is the row's `owningRfc`.
|
|
119
119
|
|
|
120
120
|
### § supportedEnvelopes
|
|
121
121
|
|
|
122
|
-
Witness `witnessable-gated`.
|
|
122
|
+
Witness `witnessable-gated`.
|
|
123
123
|
|
|
124
124
|
### § schemaVersions
|
|
125
125
|
|
|
126
|
-
Witness `witnessable-gated`.
|
|
126
|
+
Witness `witnessable-gated`.
|
|
127
127
|
|
|
128
128
|
### § limits
|
|
129
129
|
|
|
@@ -131,7 +131,7 @@ Witness `witnessable-gated`.
|
|
|
131
131
|
|
|
132
132
|
### § envelopeStrictness
|
|
133
133
|
|
|
134
|
-
Witness `claims-check`.
|
|
134
|
+
Witness `claims-check`.
|
|
135
135
|
|
|
136
136
|
### § envelopeContracts
|
|
137
137
|
|
|
@@ -183,7 +183,7 @@ Witness `witnessable-gated`.
|
|
|
183
183
|
|
|
184
184
|
### § replay
|
|
185
185
|
|
|
186
|
-
Witness `witnessable-gated`.
|
|
186
|
+
Witness `witnessable-gated`.
|
|
187
187
|
|
|
188
188
|
### § oauth
|
|
189
189
|
|
|
@@ -219,7 +219,7 @@ Witness `witnessable-gated`.
|
|
|
219
219
|
|
|
220
220
|
### § aiProviders
|
|
221
221
|
|
|
222
|
-
Witness `witnessable-gated`.
|
|
222
|
+
Witness `witnessable-gated`.
|
|
223
223
|
|
|
224
224
|
### § agents
|
|
225
225
|
|
|
@@ -259,7 +259,7 @@ Witness `witnessable-gated`.
|
|
|
259
259
|
|
|
260
260
|
### § heartbeat
|
|
261
261
|
|
|
262
|
-
Witness `witnessable-gated`.
|
|
262
|
+
Witness `witnessable-gated`.
|
|
263
263
|
|
|
264
264
|
### § toolHooks
|
|
265
265
|
|
|
@@ -299,7 +299,7 @@ Witness `witnessable-gated`.
|
|
|
299
299
|
|
|
300
300
|
### § webhooks
|
|
301
301
|
|
|
302
|
-
Witness `witnessable-gated`.
|
|
302
|
+
Witness `witnessable-gated`.
|
|
303
303
|
|
|
304
304
|
### § triggerBridge
|
|
305
305
|
|
|
@@ -307,7 +307,7 @@ Witness `witnessable-gated`.
|
|
|
307
307
|
|
|
308
308
|
### § a2a
|
|
309
309
|
|
|
310
|
-
Witness `seam-gated`.
|
|
310
|
+
Witness `seam-gated`.
|
|
311
311
|
|
|
312
312
|
A facet MAY name a URL on another origin; that is a claim about the facet, not the origin. `agentCardUrl` (and `mcp.serverUrls[]`) are `format: uri` with no origin constraint.
|
|
313
313
|
|
|
@@ -358,21 +358,21 @@ Witness `witnessable-gated`.
|
|
|
358
358
|
|
|
359
359
|
### § workflowChainPacks
|
|
360
360
|
|
|
361
|
-
Witness `witnessable-gated`.
|
|
361
|
+
Witness `witnessable-gated`.
|
|
362
362
|
|
|
363
363
|
### § packs
|
|
364
364
|
|
|
365
|
-
Witness `claims-check`.
|
|
365
|
+
Witness `claims-check`.
|
|
366
366
|
|
|
367
367
|
### § mcp
|
|
368
368
|
|
|
369
|
-
Witness `seam-gated`.
|
|
369
|
+
Witness `seam-gated`.
|
|
370
370
|
|
|
371
371
|
`serverUrls[]` MAY name other origins; the off-origin rule under § a2a applies.
|
|
372
372
|
|
|
373
373
|
### § sandbox
|
|
374
374
|
|
|
375
|
-
Witness `witnessable-gated`.
|
|
375
|
+
Witness `witnessable-gated`.
|
|
376
376
|
|
|
377
377
|
### § compensation
|
|
378
378
|
|
|
@@ -392,11 +392,11 @@ Witness `witnessable-gated`.
|
|
|
392
392
|
|
|
393
393
|
### § auth
|
|
394
394
|
|
|
395
|
-
Witness `seam-gated`.
|
|
395
|
+
Witness `seam-gated`.
|
|
396
396
|
|
|
397
397
|
### § auditLogIntegrity
|
|
398
398
|
|
|
399
|
-
Witness `witnessable-gated
|
|
399
|
+
Witness `witnessable-gated`; see [security-defaults.md](security-defaults.md) §Audit-log integrity.
|
|
400
400
|
|
|
401
401
|
### § i18n
|
|
402
402
|
|
|
@@ -412,11 +412,11 @@ Witness `witnessable-gated`.
|
|
|
412
412
|
|
|
413
413
|
### § interrupt
|
|
414
414
|
|
|
415
|
-
Witness `witnessable-gated`.
|
|
415
|
+
Witness `witnessable-gated`.
|
|
416
416
|
|
|
417
417
|
### § runList
|
|
418
418
|
|
|
419
|
-
Witness `witnessable-gated
|
|
419
|
+
Witness `witnessable-gated`; see [runs.md](runs.md) §List.
|
|
420
420
|
|
|
421
421
|
|
|
422
422
|
## 6. Extension families (13)
|
|
@@ -438,10 +438,8 @@ A profile is a predicate over the declaration file, published in `spec/v2/profil
|
|
|
438
438
|
| `openwop-conformance-seams-v2` | The seams profile ([`conformance.md`](conformance.md)); forbidden from the capability namespace |
|
|
439
439
|
|
|
440
440
|
- The v2 root has no `profiles[]`; a host that emits one MUST fail schema validation (§3).
|
|
441
|
-
- The facet `auth.lanes[]` ([`identity.md`](identity.md)) replaces `auth.profiles`.
|
|
442
|
-
-
|
|
443
|
-
- The discovery-only id is `openwop-discovery-core`; the `openwop-core` alias is deleted (row `C2.3`).
|
|
444
|
-
- The claim vocabulary is in [`overview.md`](overview.md). The invariant `profile-claim-floor-not-overstated` is registered in `SECURITY/invariants.yaml` with its test.
|
|
441
|
+
- The facet `auth.lanes[]` ([`identity.md`](identity.md)) replaces `auth.profiles`. `a2a.profiles[]` and `mcp.profiles[]` are facets too ([`interop.md`](interop.md)).
|
|
442
|
+
- The claim vocabulary is in [`overview.md`](overview.md) (invariant `profile-claim-floor-not-overstated`).
|
|
445
443
|
|
|
446
444
|
## 8. Maturity axes
|
|
447
445
|
|
|
@@ -465,6 +463,6 @@ A profile is a predicate over the declaration file, published in `spec/v2/profil
|
|
|
465
463
|
|
|
466
464
|
## 10. Migration rows
|
|
467
465
|
|
|
468
|
-
Rows `C2.1`–`C2.10` are `spec/v1/migrations.json` entries. `openwop.codemod.discovery-document-v2` transforms `C2.2`–`C2.8`: it drops a family with `supported: false` and promotes a dotted-only declared family to its plain key.
|
|
466
|
+
Rows `C2.1`–`C2.10` are `spec/v1/migrations.json` entries. `openwop.codemod.discovery-document-v2` transforms `C2.2`–`C2.8`: it drops a family with `supported: false` and promotes a dotted-only declared family to its plain key.
|
|
469
467
|
|
|
470
468
|
*Sources: RFCs 0144, 0169, 0175, 0176, 0179, 0197. Each family's owning RFC is its `owningRfc` in [`declaration.json`](../declaration.json).*
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Conformance
|
|
2
2
|
|
|
3
3
|
> **Status: Stable.**
|
|
4
|
+
> **Normative home:** `production`.
|
|
4
5
|
|
|
5
6
|
## Why this exists
|
|
6
7
|
|
|
7
|
-
The v2 evidence contract: how a requirement is asserted
|
|
8
|
+
The v2 evidence contract: how a requirement is asserted and witnessed, how the seams are mounted, what the suite ships, and what a bundle proves. Profiles are in [overview.md](overview.md); the capability vocabulary the suite gates on is in [capabilities.md](capabilities.md).
|
|
8
9
|
|
|
9
10
|
## Requirement ids
|
|
10
11
|
|
|
@@ -46,7 +47,7 @@ Every family in `spec/v2/declaration.json`, every requirement in `conformance/re
|
|
|
46
47
|
|
|
47
48
|
## The seams profile
|
|
48
49
|
|
|
49
|
-
Test seams are the profile `openwop-conformance-seams-v2` (`spec/v2/profiles.json`), described by `api/seams-v2.yaml` with schemas under `schemas/v2/seams/`,
|
|
50
|
+
Test seams are the profile `openwop-conformance-seams-v2` (`spec/v2/profiles.json`), described by `api/seams-v2.yaml` with schemas under `schemas/v2/seams/`, at `/conformance/seams/…`. The seam schemas `$ref` the canonical error and event schemas with no tolerance path. The profile is versioned with the suite (`seams-v2` for 2.x).
|
|
50
51
|
|
|
51
52
|
- A host that mounts the seams MUST advertise the profile as `conformance.seamsProfile: "openwop-conformance-seams-v2"` at the discovery root (the closed root has no `profiles[]`; [capabilities.md](capabilities.md) §3).
|
|
52
53
|
- A host MUST NOT advertise a `testSeams` capability flag.
|
|
@@ -87,7 +88,7 @@ A certification bundle validates against `schemas/v2/certification-bundle.schema
|
|
|
87
88
|
- A verifier MUST resolve `keyId` there — in the discovery document of the host the bundle is *about* — and MUST verify the attestation under the published key.
|
|
88
89
|
- A retired key MUST stay listed.
|
|
89
90
|
|
|
90
|
-
A signature that cannot be resolved to a published key attests **integrity only**, and the bundle MUST NOT be read as attributable evidence. A gate MUST distinguish three outcomes
|
|
91
|
+
A signature that cannot be resolved to a published key attests **integrity only**, and the bundle MUST NOT be read as attributable evidence. A gate MUST distinguish three outcomes: *no discovery document was read*, *read and the key is not published*, and *the attestation does not verify*.
|
|
91
92
|
|
|
92
93
|
`evidenceTier: independent` MUST carry a `verifierKeyId` distinct from the host's signing key. The verifier MUST refuse, not warn, on a missing or self-signed independent claim.
|
|
93
94
|
|
|
@@ -101,7 +102,7 @@ A signature that cannot be resolved to a published key attests **integrity only*
|
|
|
101
102
|
|
|
102
103
|
### Colocated companion
|
|
103
104
|
|
|
104
|
-
A *colocated companion* is the served host's image run beside the suite,
|
|
105
|
+
A *colocated companion* is the served host's image run beside the suite, trusting a suite-held trust anchor.
|
|
105
106
|
|
|
106
107
|
- A bundle cut from one MUST carry `host.deployment: "colocated-companion"`, which the preimage covers (§"Canonical JSON").
|
|
107
108
|
- A host serving production traffic MUST NOT list a suite-held trust anchor among the trust roots it advertises.
|
|
@@ -126,7 +127,7 @@ The value MUST be I-JSON (RFC 7493):
|
|
|
126
127
|
|
|
127
128
|
### Recovery evidence
|
|
128
129
|
|
|
129
|
-
|
|
130
|
+
A row's `evidence` enters `witnessSha256` only when present.
|
|
130
131
|
|
|
131
132
|
| Row | `evidence` member |
|
|
132
133
|
| --- | --- |
|
|
@@ -139,10 +140,26 @@ Recovery evidence rides on rows: a row's `evidence` enters `witnessSha256` only
|
|
|
139
140
|
|
|
140
141
|
`durability.rung` is outside the attestation, so a verifier MUST re-derive it and MUST reject (`rung-not-derivable`) a claim it cannot derive. Derivation requires every row of the rung to be `executed-pass`, and each kill row's `class` to name a declared entry whose `bound` equals its `boundMs` and is not exceeded by its `observedMs`. Only `durable-single-instance` is derivable in this revision; a higher claim is refused.
|
|
141
142
|
|
|
142
|
-
|
|
143
|
+
The evidence does not prove the kill landed in the class it names; killing only once the execution claim is held is the host's obligation.
|
|
144
|
+
|
|
145
|
+
## Production profile
|
|
146
|
+
|
|
147
|
+
`production` is the operational bar for a public host. A host claiming it:
|
|
148
|
+
|
|
149
|
+
- MUST pass `openwop-core-standard`, publish the suite version and command used, and document every optional profile it claims;
|
|
150
|
+
- MUST serve the events channel or poll, and SHOULD serve both;
|
|
151
|
+
- MUST persist run state and event logs outside process memory, replayable after restart, including stale-claim recovery;
|
|
152
|
+
- MUST tolerate at least five retries of one `Idempotency-Key` within its retention;
|
|
153
|
+
- MUST log run id, tenant or project id, terminal status, error code and correlation id, and SHOULD export the `openwop.*` OTel spans and metrics.
|
|
154
|
+
|
|
155
|
+
Its facets:
|
|
156
|
+
|
|
157
|
+
- **`backpressure`.** At capacity it MUST answer `503 service_unavailable` with `Retry-After`. A hold beyond 24 hours SHOULD omit the header.
|
|
158
|
+
- **`retention`.** It MUST document event-log retention, at least 7 days for snapshots and events unless labelled development-only. An expired run MUST answer `404 not_found` or `410 run_expired`, and SHOULD answer `410` when expiry is known.
|
|
159
|
+
- **`debugBundle`.** A debug bundle (`schemas/v2/debug-bundle.schema.json`) MUST redact secrets and tokens and MUST mark truncation explicitly; the host MUST document its truncation limits.
|
|
143
160
|
|
|
144
161
|
## Corpus-gate evidence
|
|
145
162
|
|
|
146
163
|
An RFC whose acceptance criteria are corpus gates rather than host scenarios records the evidence label **corpus gate — no host tier** in its `Updated` line. For such an RFC the accepted-predicate check reads `(corpus)` rows from `evidence/corpus-ledger.json` and MUST NOT require a host bundle.
|
|
147
164
|
|
|
148
|
-
*Sources: RFCs 0158, 0168, 0212, 0216.*
|
|
165
|
+
*Sources: RFCs 0009, 0158, 0168, 0212, 0216, 0228.*
|
package/spec/v2/core/errors.md
CHANGED
|
@@ -8,7 +8,7 @@ Every error a v2 host returns is a row in one registry. A client routes on `erro
|
|
|
8
8
|
|
|
9
9
|
## The registry
|
|
10
10
|
|
|
11
|
-
`spec/v2/errors.json` holds one row per code, defined by `spec/v2/errors.schema.json`. It registers **
|
|
11
|
+
`spec/v2/errors.json` holds one row per code, defined by `spec/v2/errors.schema.json`. It registers **120** codes. `schemas/v2/error-envelope.schema.json` is GENERATED from it and MUST NOT be edited by hand.
|
|
12
12
|
|
|
13
13
|
- A host MUST emit a registered code, or a vendor code, wherever it emits an error code: the `error` of every error response, and `error.code` on `run.failed`, `node.failed` and the snapshot's `error` (overview.md §0). A recorded event re-emitted by replay or `:fork` is carried as recorded ([replay.md](replay.md)).
|
|
14
14
|
- A vendor code MUST match `^(?!openwop\.)[a-z][a-z0-9]*(-[a-z0-9]+)*\.[a-z][a-z0-9_]*$`, with its first segment an org registered in `spec/v2/declaration.json`. `openwop.` is reserved.
|
|
@@ -32,7 +32,7 @@ Retry timing lives in the `Retry-After` header only.
|
|
|
32
32
|
- A host MUST NOT emit `details.retryAfter`, `details.retryAfterMs` or `details.retryAfterSeconds`.
|
|
33
33
|
- A `429 rate_limited` response MUST set `Retry-After`.
|
|
34
34
|
|
|
35
|
-
The retriable rows are `residency_unavailable`, `rate_limited`, `internal_error`, `pack_registry_unreachable`, `runner_unavailable`.
|
|
35
|
+
The retriable rows are `residency_unavailable`, `rate_limited`, `internal_error`, `pack_registry_unreachable`, `runner_unavailable`, `service_unavailable`, `upstream_unavailable`.
|
|
36
36
|
|
|
37
37
|
## One code per state
|
|
38
38
|
|
|
@@ -45,122 +45,20 @@ An interrupt has one code per state ([interrupt.md](interrupt.md), [identity.md]
|
|
|
45
45
|
|
|
46
46
|
The idempotency mismatch code is `idempotency_key_mismatch` only ([idempotency.md](idempotency.md)).
|
|
47
47
|
|
|
48
|
+
## Host-service refusals
|
|
49
|
+
|
|
50
|
+
A `ctx.*` call that rejects MUST use a registered or vendor code. A host MAY carry an uncaught rejection unchanged as the `node.failed` code.
|
|
51
|
+
|
|
52
|
+
- A generic code (`not_found`, `forbidden`, `validation_error`, `rate_limited`, `credential_not_found`, `credential_forbidden`) MUST carry `details.service`, the family key.
|
|
53
|
+
- `details.reason` MAY name a finer cause in lower-kebab. A client MUST NOT route on it.
|
|
54
|
+
- A vendor code MUST NOT stand for a state a registered code names.
|
|
55
|
+
|
|
56
|
+
## Unadvertised operations
|
|
57
|
+
|
|
58
|
+
An operation gated on a family or facet the host does not advertise MUST answer `404 not_found`.
|
|
59
|
+
|
|
48
60
|
## Codes by HTTP status
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
--- | ---
|
|
54
|
-
`connection_provider_unresolved` | 400
|
|
55
|
-
`connector_action_unresolved` | 400
|
|
56
|
-
`credential_scope_unsupported` | 400
|
|
57
|
-
`delegation_chain_cyclic` | 400
|
|
58
|
-
`delegation_chain_too_long` | 400
|
|
59
|
-
`idempotency_key_invalid` | 400
|
|
60
|
-
`interop_version_unsupported` | 400
|
|
61
|
-
`oauth_provider_unsupported` | 400
|
|
62
|
-
`oauth_scope_unsupported` | 400
|
|
63
|
-
`pack_dependency_cycle` | 400
|
|
64
|
-
`pack_engine_unsupported` | 400
|
|
65
|
-
`pack_integrity_failure` | 400
|
|
66
|
-
`pack_kind_invalid` | 400
|
|
67
|
-
`pack_lockfile_incomplete` | 400
|
|
68
|
-
`pack_peer_dependency_missing` | 400
|
|
69
|
-
`pack_peer_dependency_undefined` | 400
|
|
70
|
-
`pack_signature_invalid` | 400
|
|
71
|
-
`pack_validation_failed` | 400
|
|
72
|
-
`protocol_version_mismatch` | 400
|
|
73
|
-
`schedule_horizon_exceeded` | 400
|
|
74
|
-
`sub_chain_cycle` | 400
|
|
75
|
-
`sub_chain_depth_exceeded` | 400
|
|
76
|
-
`unsupported_runtime` | 400
|
|
77
|
-
`unsupported_stream_mode` | 400
|
|
78
|
-
`until_in_past` | 400
|
|
79
|
-
`validation_error` | 400
|
|
80
|
-
`webhook_endpoint_unverified` | 400
|
|
81
|
-
`webhook_url_rejected` | 400
|
|
82
|
-
`audience_mismatch` | 401
|
|
83
|
-
`connector_auth_declined` | 401
|
|
84
|
-
`connector_auth_expired` | 401
|
|
85
|
-
`credential_lifetime_exceeded` | 401
|
|
86
|
-
`credential_revoked` | 401
|
|
87
|
-
`delegation_expired` | 401
|
|
88
|
-
`identity_unresolvable` | 401
|
|
89
|
-
`identity_unverified` | 401
|
|
90
|
-
`interrupt_token_invalid` | 401
|
|
91
|
-
`key_revoked` | 401
|
|
92
|
-
`sender_constraint_missing` | 401
|
|
93
|
-
`unauthenticated` | 401
|
|
94
|
-
`credential_forbidden` | 403
|
|
95
|
-
`credential_scope_forbidden` | 403
|
|
96
|
-
`delegation_scope_amplified` | 403
|
|
97
|
-
`forbidden` | 403
|
|
98
|
-
`force_engine_version_forbidden` | 403
|
|
99
|
-
`id_tenant_mismatch` | 403
|
|
100
|
-
`mock_provider_forbidden` | 403
|
|
101
|
-
`pack_namespace_unauthorized` | 403
|
|
102
|
-
`run_forbidden` | 403
|
|
103
|
-
`sandbox_capability_denied` | 403
|
|
104
|
-
`sandbox_escape_attempt` | 403
|
|
105
|
-
`workspace_membership_required` | 403
|
|
106
|
-
`credential_not_found` | 404
|
|
107
|
-
`interrupt_not_found` | 404
|
|
108
|
-
`not_found` | 404
|
|
109
|
-
`pack_version_not_found` | 404
|
|
110
|
-
`replay_source_missing` | 404
|
|
111
|
-
`signature_not_available` | 404
|
|
112
|
-
`protocol_version_unsupported` | 406
|
|
113
|
-
`connection_provider_conflict` | 409
|
|
114
|
-
`envelope_correlation_conflict` | 409
|
|
115
|
-
`idempotency_in_flight` | 409
|
|
116
|
-
`idempotency_key_mismatch` | 409
|
|
117
|
-
`interrupt_already_resolved` | 409
|
|
118
|
-
`pack_dependency_conflict` | 409
|
|
119
|
-
`pack_integrity_mismatch` | 409
|
|
120
|
-
`replay_context_summary_unavailable` | 409
|
|
121
|
-
`replay_diverged_at_refusal` | 409
|
|
122
|
-
`replay_memory_snapshot_unavailable` | 409
|
|
123
|
-
`run_already_active` | 409
|
|
124
|
-
`run_state_conflict` | 409
|
|
125
|
-
`run_terminal` | 409
|
|
126
|
-
`version_conflict` | 409
|
|
127
|
-
`workspace_conflict` | 409
|
|
128
|
-
`interrupt_cancelled` | 410
|
|
129
|
-
`interrupt_expired` | 410
|
|
130
|
-
`payload_too_large` | 413
|
|
131
|
-
`workspace_too_large` | 413
|
|
132
|
-
`unsupported_media_type` | 415
|
|
133
|
-
`approval_rejected` | 422
|
|
134
|
-
`capability_not_provided` | 422
|
|
135
|
-
`capability_required` | 422
|
|
136
|
-
`connection_auth_metadata_mismatch` | 422
|
|
137
|
-
`credential_required` | 422
|
|
138
|
-
`envelope_invalid` | 422
|
|
139
|
-
`envelope_refusal` | 422
|
|
140
|
-
`envelope_truncation_unrecoverable` | 422
|
|
141
|
-
`fork_point_invalid` | 422
|
|
142
|
-
`loop_limit_exceeded` | 422
|
|
143
|
-
`mcp_mrtr_rounds_exceeded` | 422
|
|
144
|
-
`node_config_invalid` | 422
|
|
145
|
-
`pack_runtime_requirement_unmet` | 422
|
|
146
|
-
`recursion_limit_exceeded` | 422
|
|
147
|
-
`residency_unavailable` | 422
|
|
148
|
-
`run_timeout` | 422
|
|
149
|
-
`sandbox_invocation_error` | 422
|
|
150
|
-
`sandbox_memory_exceeded` | 422
|
|
151
|
-
`sandbox_timeout` | 422
|
|
152
|
-
`token_budget_exceeded` | 422
|
|
153
|
-
`unknown_envelope_kind` | 422
|
|
154
|
-
`unknown_schema_version` | 422
|
|
155
|
-
`client_version_unsupported` | 426
|
|
156
|
-
`rate_limited` | 429
|
|
157
|
-
`event_type_unmapped` | 500
|
|
158
|
-
`internal_error` | 500
|
|
159
|
-
`pack_load_failure` | 500
|
|
160
|
-
`payload_unprojectable` | 500
|
|
161
|
-
`credential_unavailable` | 501
|
|
162
|
-
`mcp_error` | 502
|
|
163
|
-
`pack_registry_unreachable` | 503
|
|
164
|
-
`runner_unavailable` | 503
|
|
165
|
-
|
|
166
|
-
*Sources: RFCs 0171, 0213.*
|
|
62
|
+
Every registered code, by HTTP status, is listed in [error-codes.md](../generated/error-codes.md), generated from `spec/v2/errors.json` (120 codes).
|
|
63
|
+
|
|
64
|
+
*Sources: RFCs 0171, 0213, 0227, 0228.*
|
package/spec/v2/core/events.md
CHANGED
|
@@ -123,7 +123,7 @@ With `bufferMs` (0..5000) the host accumulates events into one `event: batch` fr
|
|
|
123
123
|
|
|
124
124
|
### Host events
|
|
125
125
|
|
|
126
|
-
`hostEvents` carries the heartbeat messages (`schemas/v2/heartbeat-evaluated.schema.json`, `schemas/v2/heartbeat-state-changed.schema.json`) at `/host/events` (`streamHostEvents`)
|
|
126
|
+
`hostEvents` carries the heartbeat messages (`schemas/v2/heartbeat-evaluated.schema.json`, `schemas/v2/heartbeat-state-changed.schema.json`) at the default address `/host/events` (`streamHostEvents`). A host MAY declare another address under `heartbeat.deliveryChannel` ([capabilities.md](capabilities.md)); every channel has an address. The channel carries no run data.
|
|
127
127
|
|
|
128
128
|
#### `heartbeat`
|
|
129
129
|
|
|
@@ -131,7 +131,7 @@ A heartbeat evaluates a predicate on an interval (at least `minIntervalSec`) and
|
|
|
131
131
|
|
|
132
132
|
- skip, not queue, a tick while the prior evaluation still runs;
|
|
133
133
|
- bound the evaluation by `maxRuntimeMs`, itself capped by `limits.maxRunDurationMs`, terminating an overrun with `status: timeout`;
|
|
134
|
-
- pass the predicate the prior tick's state, and perform no side effect itself
|
|
134
|
+
- pass the predicate the prior tick's state, and perform no side effect itself. The predicate MUST be a pure function of observed and prior state;
|
|
135
135
|
- emit `heartbeat.evaluated`;
|
|
136
136
|
- on a transition only, emit `heartbeat.stateChanged` and, if the predicate asks, call `createRun`; never on an unchanged tick.
|
|
137
137
|
|
|
@@ -196,15 +196,12 @@ An absent `kinds` is not an empty catalog and is not an unrestricted one: a host
|
|
|
196
196
|
|
|
197
197
|
## Envelope, feedback and usage facets
|
|
198
198
|
|
|
199
|
-
- **`envelopes.tierOneSubsetCompliance`** advertises that the host accepts the Tier 1 structured-output subset shared across major providers. A host advertising it MUST accept an envelope restricted to that subset from any provider it advertises, rather than refusing on provider-specific grounds.
|
|
200
|
-
- **`feedback.targets`** names the resources an annotation may be attached to. A host MUST refuse an annotation whose target is outside the advertised set, and MUST NOT write it to the replayable run event log.
|
|
201
|
-
- **`providerUsage.costEstimates`** advertises that the host stamps a derived cost on the `provider.usage` event. That figure is an estimate from the host's own rate table; a consumer MUST NOT treat it as a billed amount.
|
|
202
|
-
|
|
203
199
|
### `feedback`
|
|
204
200
|
|
|
205
|
-
`feedback.signals` lists the signal kinds a host accepts; absent means all four. A host advertising `feedback` MUST:
|
|
201
|
+
`feedback.signals` lists the signal kinds a host accepts; absent means all four. `feedback.targets` names the resources an annotation may be attached to. A host advertising `feedback` MUST:
|
|
206
202
|
|
|
207
203
|
- accept an annotation on a terminal run;
|
|
204
|
+
- refuse an annotation whose target is outside the advertised `targets`, and MUST NOT write it to the replayable run event log;
|
|
208
205
|
- keep annotations visible only within the run's tenant (invariant `annotation-cross-tenant-isolation`);
|
|
209
206
|
- redact secret-shaped material in `signal.correction` and `note` before persistence, listing and export (invariant `annotation-content-redaction`);
|
|
210
207
|
- audit-log each recording with the acting principal.
|
|
@@ -214,11 +211,14 @@ An absent `kinds` is not an empty catalog and is not an unrestricted one: a host
|
|
|
214
211
|
A host advertising `providerUsage` MUST emit exactly one `provider.usage` per LLM provider invocation, before that node's `node.completed`. A host that does not advertise it omits the event.
|
|
215
212
|
|
|
216
213
|
- `inputTokens` and `outputTokens` MUST replay identically; `costEstimateUsd` MAY be omitted on replay.
|
|
214
|
+
- **`providerUsage.costEstimates`** advertises that the host stamps a derived cost on the event. That figure is an estimate from the host's own rate table; a consumer MUST NOT treat it as a billed amount.
|
|
217
215
|
- The payload MUST NOT carry credential refs, hashed credential identifiers, or prompt or response text (invariant `provider-usage-no-credential-leak`).
|
|
218
216
|
- `providerUsage.currency` is the ISO 4217 currency of `costEstimateUsd`; absent means USD.
|
|
219
217
|
|
|
220
218
|
### `envelopes`
|
|
221
219
|
|
|
220
|
+
**`envelopes.tierOneSubsetCompliance`** advertises that the host accepts the Tier 1 structured-output subset shared across major providers. A host advertising it MUST accept an envelope restricted to that subset from any provider it advertises, rather than refusing on provider-specific grounds.
|
|
221
|
+
|
|
222
222
|
`envelopes.reasoning` advertises the host's prompt posture for the optional payload field `reasoning`.
|
|
223
223
|
|
|
224
224
|
- `reasoning` SHOULD be the first property where the schema dialect orders properties. A vendor kind whose payload needs multi-step reasoning SHOULD declare it as optional.
|