@openwop/spec-artifacts 2.42.6 → 2.42.8

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.
Files changed (69) hide show
  1. package/CORPUS-STAMP.json +70 -67
  2. package/api/seams-v2.yaml +238 -322
  3. package/api/v2/asyncapi.yaml +83 -113
  4. package/api/v2/openapi.yaml +408 -724
  5. package/package.json +1 -1
  6. package/schemas/audit-verify-result.schema.json +2 -2
  7. package/schemas/v2/audit-verify-result.schema.json +2 -2
  8. package/schemas/v2/capabilities.schema.json +4 -7
  9. package/schemas/v2/error-envelope.schema.json +144 -69
  10. package/spec/v1/alias-detectors.json +6 -0
  11. package/spec/v1/core-standard-manifest.json +2 -2
  12. package/spec/v1/deprecations.json +22 -0
  13. package/spec/v1/gaps.json +188 -1
  14. package/spec/v1/migrations.json +18 -0
  15. package/spec/v2/README.md +35 -37
  16. package/spec/v2/core/capabilities.md +186 -119
  17. package/spec/v2/core/conformance.md +78 -27
  18. package/spec/v2/core/connection-packs.md +8 -3
  19. package/spec/v2/core/conversation.md +10 -4
  20. package/spec/v2/core/errors.md +30 -9
  21. package/spec/v2/core/events.md +116 -80
  22. package/spec/v2/core/form-content-packs.md +11 -13
  23. package/spec/v2/core/headers.md +23 -14
  24. package/spec/v2/core/host-services.md +22 -5
  25. package/spec/v2/core/i18n.md +64 -0
  26. package/spec/v2/core/idempotency.md +48 -36
  27. package/spec/v2/core/identity.md +110 -42
  28. package/spec/v2/core/interop.md +82 -21
  29. package/spec/v2/core/interrupt.md +79 -40
  30. package/spec/v2/core/node-pack-runtimes.md +18 -4
  31. package/spec/v2/core/oauth.md +40 -6
  32. package/spec/v2/core/overview.md +61 -33
  33. package/spec/v2/core/packs.md +53 -28
  34. package/spec/v2/core/persistence.md +61 -80
  35. package/spec/v2/core/portability.md +44 -0
  36. package/spec/v2/core/replay.md +56 -42
  37. package/spec/v2/core/runs.md +198 -60
  38. package/spec/v2/core/security-defaults.md +57 -33
  39. package/spec/v2/core/tool-catalog.md +29 -6
  40. package/spec/v2/core/versioning.md +115 -50
  41. package/spec/v2/core/webhooks.md +61 -20
  42. package/spec/v2/core/workflow-chain-packs.md +16 -14
  43. package/spec/v2/corrections.json +17 -1
  44. package/spec/v2/corrections.schema.json +1 -1
  45. package/spec/v2/declaration.json +26 -9
  46. package/spec/v2/declaration.schema.json +10 -0
  47. package/spec/v2/errors.json +121 -4
  48. package/spec/v2/errors.schema.json +38 -0
  49. package/spec/v2/ext/README.md +36 -23
  50. package/spec/v2/ext/a2uiSurface/README.md +75 -89
  51. package/spec/v2/ext/brand/README.md +15 -15
  52. package/spec/v2/ext/canvas/README.md +15 -15
  53. package/spec/v2/ext/chat/README.md +15 -15
  54. package/spec/v2/ext/coordination/README.md +15 -15
  55. package/spec/v2/ext/dataIntegration/README.md +17 -20
  56. package/spec/v2/ext/entities/README.md +15 -15
  57. package/spec/v2/ext/grpc-transport/README.md +6 -8
  58. package/spec/v2/ext/kanban/README.md +15 -15
  59. package/spec/v2/ext/knowledge/README.md +15 -15
  60. package/spec/v2/ext/launchStudio/README.md +16 -16
  61. package/spec/v2/ext/messaging/README.md +15 -15
  62. package/spec/v2/ext/portability/README.md +3 -17
  63. package/spec/v2/ext/provider-idempotency/README.md +7 -10
  64. package/spec/v2/ext/restTransport/README.md +30 -19
  65. package/spec/v2/ext/sandbox-runtime-notes/README.md +7 -9
  66. package/spec/v2/ext/webResearch/README.md +16 -16
  67. package/spec/v2/profiles.json +6 -2
  68. package/spec/v2/release.json +2 -2
  69. package/spec/v2/surface-baseline.json +544 -2
@@ -1,17 +1,17 @@
1
1
  # Identity
2
2
 
3
- > **Status: Stable · RFC 0170, 0165, 0176.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `auth`, `authorization`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- v2 makes the Subject the owner of every run, binds every lane to a trust root and a revocation rule, gives the link and every id a grammar, and prefixes tokens so a host can rotate them. Idempotency-key grammar is `idempotency.md`.
8
+ The Subject owns every run. Every lane binds to a trust root and a revocation rule, the link and every id have a grammar, and tokens carry a prefix so a host can rotate them. Idempotency-key grammar is in [idempotency.md](idempotency.md).
9
9
 
10
- ## 1. The Subject is the owner (RFC 0170 §A)
10
+ ## 1. The Subject is the owner
11
11
 
12
12
  ### 1.1 Shape (`schemas/v2/subject.schema.json`)
13
13
 
14
- `RunSnapshot.owner` is `{ tenant, workspace?, subject }` with `subject` REQUIRED; `principal` and `principalKind` are removed (`subject.subjectId` and `subject.kind` carry them). `run.started` MUST echo the same block (`runs.md`, `events.md`). The Subject is closed (`additionalProperties: false`):
14
+ `RunSnapshot.owner` is `{ tenant, workspace?, subject }` with `subject` REQUIRED. `run.started` MUST echo the same block ([runs.md](runs.md), [events.md](events.md)). The Subject is closed (`additionalProperties: false`):
15
15
 
16
16
  | Field | Rule |
17
17
  | --- | --- |
@@ -23,27 +23,37 @@ v2 makes the Subject the owner of every run, binds every lane to a trust root an
23
23
  | `keyClass` | `opaque-idp \| configured-immutable`; MUST be present iff `lane ∈ {saml, scim}` |
24
24
  | `actor` | OPTIONAL; a nested Subject that acts on this subject's behalf; depth bounded at four |
25
25
 
26
- `kind: anonymous` REQUIRES `lane: anonymous` and `lane: anonymous` REQUIRES `kind: anonymous`. The `actor` depth bound (4) is a four-level `$ref` chain (`actor1`…`actor4`) rather than a recursive `$ref`; a fifth level MUST fail validation. `session` is a host-native credential the host itself issued (a durable login session, a local password); `anonymous` is an RFC 0132 public surface. The lane enum grows only under `overview.md` §0.
26
+ - `kind: anonymous` REQUIRES `lane: anonymous`, and `lane: anonymous` REQUIRES `kind: anonymous`.
27
+ - The `actor` depth bound (4) is a four-level `$ref` chain (`actor1`…`actor4`), not a recursive `$ref`. A fifth level MUST fail validation.
28
+ - `session` is a host-native credential the host itself issued (a durable login session, a local password). `anonymous` is a public surface.
29
+ - The lane enum grows only under [overview.md](overview.md) §0.
27
30
 
28
- ### 1.2 The legacy subject rule (RFC 0170 §A.3)
31
+ ### 1.2 The legacy subject rule
29
32
 
30
- On every read of a run created before the host began emitting subjects, the host MUST stamp `issuer: "urn:openwop:legacy"`, `lane` as attested else `api-key`, `kind` as recorded else `user`. A host MUST stamp the legacy subject at first read and MUST NOT rewrite it later. A legacy subject MUST NOT participate in a link (§3), an actor chain, or a delegation decision.
33
+ A run created before the host began emitting subjects has a legacy subject:
31
34
 
32
- ### 1.3 Fork (RFC 0170 §A.4)
35
+ - On every read, the host MUST stamp `issuer: "urn:openwop:legacy"`, `lane` as attested else `api-key`, and `kind` as recorded else `user`.
36
+ - A host MUST stamp the legacy subject at first read and MUST NOT rewrite it later.
37
+ - A legacy subject MUST NOT participate in a link (§3), an actor chain, or a delegation decision.
33
38
 
34
- On fork the host MUST copy `owner` verbatim onto the child: `tenant`, `workspace`, and `subject`. There is no `principal` asymmetry.
39
+ ### 1.3 Fork
35
40
 
36
- ### 1.4 A2A anonymous end users (RFC 0170 §A.5)
41
+ On fork the host MUST copy `owner` verbatim onto the child: `tenant`, `workspace` and `subject`.
37
42
 
38
- An end user reaching the host through an A2A peer is `kind: anonymous`, `lane: anonymous`, with the forwarding peer's subject as `actor`; such a subject MUST NOT be linked.
43
+ ### 1.4 A2A anonymous end users
39
44
 
40
- ## 2. One binding pipeline, every lane (RFC 0170 §B)
45
+ An end user reaching the host through an A2A peer is `kind: anonymous`, `lane: anonymous`, with the forwarding peer's subject as `actor`. Such a subject MUST NOT be linked.
41
46
 
42
- ### 2.1 The pipeline (§B.1)
47
+ ## 2. One binding pipeline, every lane
43
48
 
44
- Every lane MUST: verify the credential against the lane's trust root; bind the verified identity to the request, never to an asserted header; check audience; resolve to a Subject before any authorization decision; and fail closed. On the `oidc` lane an ID token MAY be a bearer only when its `aud` equals the host's configured audience; any other `aud` is `audience_mismatch`. The closed reason vocabulary is the family-wide error set in §6. Every lane is advertised as one member of the `auth.lanes[]` facet (`spec/v2/facets/auth.schema.json`):
49
+ ### 2.1 The pipeline
45
50
 
46
- `authorization.failClosed` advertises that rule and MUST be `true` when present; it does not gate it (invariant `authorization-fail-closed`). `authorization.roles` is the host role catalog: a request is authorized when any role-derived scope matches the required scope, under the same scope-match semantics this document applies to a credential.
51
+ Every lane MUST verify the credential against the lane's trust root; bind the verified identity to the request, never to an asserted header; check audience; resolve to a Subject before any authorization decision; and fail closed.
52
+
53
+ - On the `oidc` lane an ID token MAY be a bearer only when its `aud` equals the host's configured audience; any other `aud` is `audience_mismatch`.
54
+ - The closed reason vocabulary is the family-wide error set in §6.
55
+
56
+ Every lane is advertised as one member of the `auth.lanes[]` facet (`spec/v2/facets/auth.schema.json`):
47
57
 
48
58
  ```json
49
59
  { "lane": "oidc", "issuers": ["https://idp.example"], "revocation": "exp-and-recheck",
@@ -51,9 +61,11 @@ Every lane MUST: verify the credential against the lane's trust root; bind the v
51
61
  "delegationProofs": ["dpop"] }
52
62
  ```
53
63
 
54
- `lane`, `issuers[]` (min 1), `revocation`, and `minimumAssurance` are REQUIRED on each member. `auth.lanes[].issuers[]` is the realm; the v1 `auth.profiles` facet is replaced by it (`capabilities.md`).
64
+ `lane`, `issuers[]` (min 1), `revocation` and `minimumAssurance` are REQUIRED on each member. `auth.lanes[].issuers[]` is the realm ([capabilities.md](capabilities.md)).
65
+
66
+ `authorization.failClosed` advertises the fail-closed rule and MUST be `true` when present; it does not gate it (invariant `authorization-fail-closed`). `authorization.roles` is the host role catalog: a request is authorized when any role-derived scope matches the required scope, under the same scope-match semantics this document applies to a credential.
55
67
 
56
- ### 2.2 Trust roots and revocation (§B.2, §B.3)
68
+ ### 2.2 Trust roots and revocation
57
69
 
58
70
  Every lane MUST name its trust root as `subject.issuer` and MUST advertise it in `issuers[]`. Revocation exists for every lane; the `revocation` value names the rule.
59
71
 
@@ -70,27 +82,54 @@ Every lane MUST name its trust root as `subject.issuer` and MUST advertise it in
70
82
  | `session` | `urn:<host>:session` | refuse a revoked session on the next request (`credential_revoked`) | `next-request` |
71
83
  | `anonymous` | `urn:<host>:anon-surface` | — | — |
72
84
 
73
- `revocationWindowSeconds` (integer ≥ 1) MUST be advertised wherever the rule names a window — `exp-and-recheck`, `exp-only`, `short-lived`, `rebind`. On every lane it is **an upper bound on the interval between a revocation at the trust root and the host's first refusal**, and a host MUST NOT advertise a window it does not enforce.
85
+ - `revocationWindowSeconds` (integer ≥ 1) MUST be advertised wherever the rule names a window: `exp-and-recheck`, `exp-only`, `short-lived`, `rebind`. On every lane it is an upper bound on the interval between a revocation at the trust root and the host's first refusal. A host MUST NOT advertise a window it does not enforce.
86
+ - A host MUST NOT advertise a `revocation` value the row above for its lane does not list.
87
+ - A consumer meeting an unrecognized value MUST NOT act on it: it MUST read the lane as stating no revocation latency, never as `next-request` or any other member ([overview.md](overview.md) §0).
88
+
89
+ #### `exp-only`
90
+
91
+ `exp-only` names a host that honors `exp` and never re-checks revocation: it consults no introspection endpoint, no userinfo endpoint, no revocation list and no host-side epoch or `validAfter` record. A credential revoked at the trust root is accepted until its own `exp`, so the window is the only bound, and it is enforced.
92
+
93
+ - A host advertising `exp-only` on a lane MUST refuse a credential presented on that lane with `401 credential_lifetime_exceeded` when **either** `exp − iat` (total lifetime) **or** `exp − now` (remaining lifetime) exceeds the advertised `revocationWindowSeconds`. Both bounds matter: `exp − iat` alone admits a ten-year token minted ten years ago; `exp − now` alone admits a fresh ten-year token in its ninth year.
94
+ - A credential carrying no `iat` MUST be refused with the same code, because the first bound cannot be evaluated without it (§2.1 fail-closed).
95
+ - A host that cannot enforce both bounds MUST NOT advertise `exp-only`.
96
+ - `exp-only` SHOULD be advertised with a window of one hour or less. No maximum is set.
97
+ - `exp-only` MUST NOT be advertised on the `api-key` or `session` lane, where the host issued the credential and controls revocation itself. `auth.schema.json` refuses that pairing (invariant `lane-exp-only-lifetime-bounded`).
98
+
99
+ ### 2.3 Minimum assurance
100
+
101
+ - Each lane MUST advertise `minimumAssurance: bearer | sender-constrained | key-bound`.
102
+ - A request below the lane's floor MUST be refused with `sender_constraint_missing`.
103
+ - An audit fact MUST record the assurance actually used.
104
+ - A bearer fallback MUST NOT inherit a sender-constrained label (invariant `sender-constraint-no-bearer-downgrade`, `SECURITY/invariants.yaml`).
105
+
106
+ ### 2.4 Delegation proofs
74
107
 
75
- A host MUST NOT advertise a `revocation` value the row above for its lane does not list. A consumer meeting an unrecognized value MUST NOT act on it: it MUST read the lane as stating no revocation latency, never as `next-request` or any other member (`overview.md` §0).
108
+ The proof format is lane-scoped: mTLS key binding or DPoP for the two JWT lanes (`oauth2`, `oidc`), SVID chains for `workload`.
76
109
 
77
- **`exp-only` (RFC 0210).** `exp-only` names a host that honors `exp` and re-checks revocation never: it consults no introspection endpoint, no userinfo endpoint, no revocation list and no host-side epoch or `validAfter` record, so a credential revoked at the trust root is accepted until its own `exp`. Its window is therefore the only bound there is, and it is enforced rather than described. A host advertising `exp-only` on a lane MUST refuse a credential presented on that lane when **either** `exp − iat` (total lifetime) **or** `exp − now` (remaining lifetime) exceeds the advertised `revocationWindowSeconds`, with `401 credential_lifetime_exceeded`; a credential carrying no `iat` MUST be refused with the same code, because the first bound cannot be evaluated without it (§2.1 fail-closed). Both bounds are load-bearing: `exp − iat` alone admits a ten-year token minted ten years ago, `exp − now` alone admits a freshly minted ten-year token in its ninth year. A host that cannot enforce both MUST NOT advertise `exp-only`. `exp-only` SHOULD be advertised with a window of one hour or less; the corpus states no maximum, because no upstream specification does. `exp-only` MUST NOT be advertised on the `api-key` or `session` lane — there the host issued the credential itself, so revocation is in its own hands — and `auth.schema.json` refuses that pairing (invariant `lane-exp-only-lifetime-bounded`).
110
+ - A host MUST advertise the proofs it accepts under `auth.lanes[].delegationProofs[]` (`mtls-key-binding | dpop | svid-chain`).
111
+ - A chain with no acceptable proof MUST be refused as `identity_unverified`.
112
+ - A chain longer than the bound is `delegation_chain_too_long`, a cyclic chain is `delegation_chain_cyclic`, and a link that widens scope is `delegation_scope_amplified` (invariants `delegation-chain-bounded-acyclic`, `delegation-no-scope-amplification`, `delegation-provenance-not-authorization`).
78
113
 
79
- ### 2.3 Minimum assurance (§B.4)
114
+ ### 2.5 Protected-resource metadata and challenges
80
115
 
81
- Each lane MUST advertise `minimumAssurance: bearer | sender-constrained | key-bound`. A request below the lane's floor MUST be refused with `sender_constraint_missing`. An audit fact MUST record the assurance actually used. A bearer fallback MUST NOT inherit a sender-constrained label (invariant `sender-constraint-no-bearer-downgrade`, `SECURITY/invariants.yaml`).
116
+ A host advertising an `oauth2` or `oidc` lane MUST serve RFC 9728 metadata, unauthenticated, at the well-known URI formed from its resource identifier (the base URL it serves this API under) with `/.well-known/oauth-protected-resource` inserted before any path: `https://h.example/api` → `https://h.example/.well-known/oauth-protected-resource/api`.
82
117
 
83
- ### 2.4 Delegation proofs (§B.5)
118
+ - `resource` MUST equal that identifier.
119
+ - `authorization_servers` MUST list exactly the URL-form issuers of those lanes.
120
+ - `scopes_supported` MUST list the scopes the host enforces.
121
+ - `dpop_bound_access_tokens_required` or `tls_client_certificate_bound_access_tokens` MAY be `true` only where every such lane's `minimumAssurance` requires that binding (§2.3).
84
122
 
85
- The proof format is lane-scoped: mTLS key binding or DPoP for the two JWT lanes (`oauth2`, `oidc`), SVID chains for `workload`. A host MUST advertise the proofs it accepts under `auth.lanes[].delegationProofs[]` (`mtls-key-binding | dpop | svid-chain`). A chain with no acceptable proof MUST be refused as `identity_unverified`. The chain rules keep their codes: a chain longer than the bound is `delegation_chain_too_long`, a cyclic chain is `delegation_chain_cyclic`, and a link that widens scope is `delegation_scope_amplified` (invariants `delegation-chain-bounded-acyclic`, `delegation-no-scope-amplification`, `delegation-provenance-not-authorization`).
123
+ Challenges on such a host:
86
124
 
87
- ### 2.5 Protected-resource metadata and challenges (RFC 0200)
125
+ - A `401` MUST carry `WWW-Authenticate: Bearer resource_metadata="<url>"`, adding `error="invalid_token"` when a credential was presented and no error code when none was.
126
+ - A `403` for insufficient scope MUST carry `error="insufficient_scope"` with `scope` listing every scope the operation requires. A `403` for resource binding carries no `insufficient_scope`.
88
127
 
89
- A host advertising an `oauth2` or `oidc` lane MUST serve RFC 9728 metadata at the well-known URI formed from its resource identifier, the base URL it serves this API under, with `/.well-known/oauth-protected-resource` inserted before any path (`https://h.example/api` → `https://h.example/.well-known/oauth-protected-resource/api`), unauthenticated. `resource` MUST equal that identifier; `authorization_servers` MUST list exactly the URL-form issuers of those lanes; `scopes_supported` MUST list the scopes the host enforces; `dpop_bound_access_tokens_required` or `tls_client_certificate_bound_access_tokens` MAY be `true` only where every such lane's `minimumAssurance` requires that binding (§2.3).
128
+ Other hosts SHOULD send `WWW-Authenticate: Bearer` on a `401`. A challenge attaches only to a response already `401` or `403` and MUST NOT change a status: where a rule requires `404` for an unknown or unauthorized resource, the `404` stands and carries none (invariant `auth-challenge-no-oracle`).
90
129
 
91
- On such a host a `401` MUST carry `WWW-Authenticate: Bearer resource_metadata="<url>"`, adding `error="invalid_token"` when a credential was presented and no error code when none was, and a `403` for insufficient scope MUST carry `error="insufficient_scope"` with `scope` listing every scope the operation requires; a `403` for resource binding carries no `insufficient_scope`. Other hosts SHOULD send `WWW-Authenticate: Bearer` on a `401`. A challenge attaches only to a response already `401` or `403` and MUST NOT change a status: where a rule requires `404` for an unknown or unauthorized resource, the `404` stands and carries none (invariant `auth-challenge-no-oracle`).
130
+ ## 3. The link is a record
92
131
 
93
- ## 3. The link is a record (RFC 0170 §C; `schemas/v2/subject-link.schema.json`)
132
+ A subject link is a `schemas/v2/subject-link.schema.json` record:
94
133
 
95
134
  ```json
96
135
  { "a": { "issuer": "…", "subjectId": "…" }, "b": { "issuer": "…", "subjectId": "…" },
@@ -98,19 +137,31 @@ On such a host a `401` MUST carry `WWW-Authenticate: Bearer resource_metadata="<
98
137
  "tenant": "<tenantId>", "formedAt": "<date-time>", "deniedAt"?: "<date-time>" }
99
138
  ```
100
139
 
101
- The record and both `SubjectRef`s are closed; `a`, `b`, `keyClass`, `issuer`, `tenant`, `formedAt` are REQUIRED. A link MUST be tenant-scoped, MUST join exactly two subjects whose `issuer` values are bound to one IdP entityID (`issuer` on the record), and MUST NOT include a legacy (`urn:openwop:legacy` is schema-rejected) or anonymous subject. Deactivation sets `deniedAt`; the SAML decision path MUST consult it (the leaver contract). The link is a reference, not a merge: nothing rewrites a subject already stamped on a run.
140
+ The record and both `SubjectRef`s are closed; `a`, `b`, `keyClass`, `issuer`, `tenant`, `formedAt` are REQUIRED. A link:
102
141
 
103
- `auth.subjectLinking` is removed (migration row `C2.5`): advertising both `saml` and `scim` lanes implies the contract. Lanes stay separate facets; there is no single "enterprise identity" profile. The `auth.subjectLinkKey` facet (`opaque-idp | configured-immutable`) names the key class the host forms links under.
142
+ - MUST be tenant-scoped;
143
+ - MUST join exactly two subjects whose `issuer` values are bound to one IdP entityID (`issuer` on the record);
144
+ - MUST NOT include a legacy (`urn:openwop:legacy` is schema-rejected) or anonymous subject.
104
145
 
105
- ## 4. Resume tokens (RFC 0170 §E.1; RFC 0176 §B.2)
146
+ Deactivation sets `deniedAt`; the SAML decision path MUST consult it (the leaver contract). The link is a reference, not a merge: nothing rewrites a subject already stamped on a run.
106
147
 
107
- An interrupt resume token is `ow2.<alg>.<kid>.<payload>.<mac>`: `alg ∈ {hs256}` at the cut (`interrupt.tokenAlgs[]` advertises it), `kid` (`keyId` grammar) selects the verification secret, `payload` and `mac` as in v1. A host MUST refuse a token whose `alg` it does not advertise or whose `kid` it does not hold with `401` `interrupt_token_invalid`. The `{token}` path parameter carries the grammar (`api/v2/openapi.yaml`).
148
+ Advertising both `saml` and `scim` lanes implies the link contract; there is no `auth.subjectLinking` flag. Lanes stay separate facets, with no single "enterprise identity" profile. The `auth.subjectLinkKey` facet (`opaque-idp | configured-immutable`) names the key class the host forms links under.
108
149
 
109
- A token the host issued under v1 (any token not `ow2.`-prefixed; the rule is the prefix, never a segment count, persistence.md) MUST remain resolvable under `kid: legacy` until its `expiresAt`; a run suspended on an interrupt at the cut continues under `persistence.md` and its outstanding token resolves the same way. Interrupt semantics are `interrupt.md`.
150
+ ## 4. Resume tokens
151
+
152
+ An interrupt resume token is `ow2.<alg>.<kid>.<payload>.<mac>`:
153
+
154
+ - `alg ∈ {hs256}`, advertised in `interrupt.tokenAlgs[]`;
155
+ - `kid` (`keyId` grammar) selects the verification secret;
156
+ - `payload` and `mac` are as in v1.
157
+
158
+ A host MUST refuse a token whose `alg` it does not advertise or whose `kid` it does not hold with `401` `interrupt_token_invalid`. The `{token}` path parameter carries the grammar (`api/v2/openapi.yaml`). Interrupt semantics are in [interrupt.md](interrupt.md).
159
+
160
+ A token not `ow2.`-prefixed was issued under v1; the rule is the prefix, never a segment count. Such a token MUST remain resolvable under `kid: legacy` until its `expiresAt`. A run suspended on an interrupt at the cut continues under [persistence.md](persistence.md), and its outstanding token resolves the same way.
110
161
 
111
162
  ## 5. Identifier grammars (`schemas/v2/ids.schema.json`)
112
163
 
113
- Every id field in every v2 schema and every `api/v2/openapi.yaml` parameter and response body MUST `$ref` its kind. `x-openwop-minted` records who mints the id: `host` (opaque and checkable), `author` (chosen in a workflow or pack; the v1 grammar stands), or `registry`.
164
+ Every id field in every v2 schema and every `api/v2/openapi.yaml` parameter and response body MUST `$ref` its kind. `x-openwop-minted` records who mints the id: `host` (opaque and checkable), `author` (chosen in a workflow or pack), or `registry`.
114
165
 
115
166
  | Kinds | Grammar | Minted |
116
167
  | --- | --- | --- |
@@ -123,17 +174,32 @@ Every id field in every v2 schema and every `api/v2/openapi.yaml` parameter and
123
174
  | `nodeId`, `workflowId`, `agentId`, `chainId`, `pluginId`, `templateId`, `libraryId` | `^[A-Za-z0-9._~:-]{1,128}$` | author |
124
175
  | `typeId` | `^[a-z][a-z0-9_-]*(\.[a-z][a-zA-Z0-9_-]*)+$`, maxLength 256 | author |
125
176
 
126
- `scripts/check-id-kinds-bound.mjs` enforces this against `spec/v2/id-field-bindings.json`, which sorts every `*Id` property in a v2 schema into two sets: it **is** a kind above (and MUST `$ref` it), or nothing here governs it (reason recorded); a field in neither set fails. *Every id field*, not every matching name.
177
+ `spec/v2/id-field-bindings.json` sorts every `*Id` property in a v2 schema into two sets: it **is** a kind above (and MUST `$ref` it), or nothing here governs it (reason recorded).
178
+
179
+ - A host MUST reject a tenant-bound id whose tenant segment is not the caller's with `403` `id_tenant_mismatch`.
180
+ - A host-minted opaque segment MUST match `^[A-Za-z0-9._~-]{16,128}$`.
181
+ - Ids in documents and bodies are bound, always. A client MAY bind at its request seam.
182
+ - Handle grammars (`memoryRef`, workspace `path`/`etag`, the plugin version token) and their `resolvability` class are specified where each handle is used. An importer MUST re-mint every `host`-scoped handle ([portability.md](portability.md)).
127
183
 
128
- A host MUST reject a tenant-bound id whose tenant segment is not the caller's with `403` `id_tenant_mismatch`. A host-minted opaque segment MUST match `^[A-Za-z0-9._~-]{16,128}$`.
184
+ ### Wire form
129
185
 
130
- **On the wire a tenant-bound id is one path segment, projected** (RFC 0184): every UTF-8 byte outside `[A-Za-z0-9._-]` becomes `~` plus two uppercase hex digits, so `acme/r-9f3c…` travels as `acme~2Fr-9f3c…`. A host MUST emit it in every link and MUST accept it on every tenant-bound parameter; it MUST still accept `tenant%2Fopaque`, and MUST decode either before matching the grammar. A host MUST NOT mint a tenant-bound id containing `~`; ids already minted MUST still resolve. A host MUST project exactly once, where an id leaves it, and MUST NOT re-encode its own output.
186
+ On the wire a tenant-bound id is one path segment, projected: every UTF-8 byte outside `[A-Za-z0-9._-]` becomes `~` plus two uppercase hex digits, so `acme/r-9f3c…` travels as `acme~2Fr-9f3c…`.
131
187
 
132
- **Through the overlap the bare form is admitted on a major-2 path parameter**. A parameter carrying only the opaque segment (what a `/v1/` create hands out) MUST resolve under the caller's tenant and never another's, and the response MUST name the resource bound (`versioning.md` §5); the credential supplies the segment the `403` check would read. Once a host advertises no `1.x` member it MUST refuse the bare form `400 validation_error` (not `id_tenant_mismatch`, not `not_found`). Ids in documents and bodies are bound, always. A client MAY bind at its request seam. Handle grammars (`memoryRef`, workspace `path`/`etag`, the plugin version token) and their `resolvability` class are specified where each handle is used; an importer MUST re-mint every `host`-scoped handle (`spec/v2/ext/portability/`).
188
+ - A host MUST emit the projected form in every link and MUST accept it on every tenant-bound parameter.
189
+ - It MUST still accept `tenant%2Fopaque`, and MUST decode either form before matching the grammar.
190
+ - A host MUST NOT mint a tenant-bound id containing `~`; ids already minted MUST still resolve.
191
+ - A host MUST project exactly once, where an id leaves it, and MUST NOT re-encode its own output.
192
+
193
+ ### Bare ids during the v1 overlap
194
+
195
+ Through the overlap the bare form is admitted on a major-2 path parameter.
196
+
197
+ - A parameter carrying only the opaque segment (what a `/v1/` create hands out) MUST resolve under the caller's tenant and never another's, and the response MUST name the resource bound ([versioning.md](versioning.md) §5). The credential supplies the segment the `403` check would read.
198
+ - Once a host advertises no `1.x` member it MUST refuse the bare form `400 validation_error` (not `id_tenant_mismatch`, not `not_found`).
133
199
 
134
200
  ## 6. Identity error codes (`spec/v2/errors.json`)
135
201
 
136
- Every code below is a row with `retriable: false` and no `details` contract; the envelope is `errors.md`. All are `since: "2.0"` except `credential_lifetime_exceeded` (`since: "2.36"`, RFC 0210).
202
+ Every code below is a row with `retriable: false` and no `details` contract; the envelope is in [errors.md](errors.md).
137
203
 
138
204
  | Code | HTTP | Raised when |
139
205
  | --- | --- | --- |
@@ -150,8 +216,10 @@ Every code below is a row with `retriable: false` and no `details` contract; the
150
216
  | `id_tenant_mismatch` | 403 | a tenant-bound id's tenant segment is not the caller's (§5) |
151
217
  | `interrupt_token_invalid` | 401 | an unadvertised `alg` or an unheld `kid` (§4) |
152
218
 
153
- `unauthenticated` and `run_forbidden` keep their v1 rows. Every code is a registry member under `overview.md` §0.
219
+ `unauthenticated` and `run_forbidden` also apply. Every code is a registry member under [overview.md](overview.md) §0.
220
+
221
+ ## 7. Invariants
154
222
 
155
- ## 7. Invariants (RFC 0170 §E.2)
223
+ The identity invariants are registered in `SECURITY/invariants.yaml` with their scenarios. An invariant without a witness is demoted from `protocol` tier.
156
224
 
157
- The RFC 0170 §E.2 invariants are registered in `SECURITY/invariants.yaml` with their scenarios; an invariant that reaches the cut without a witness is demoted from `protocol` tier and recorded.
225
+ *Sources: RFC 0132, RFC 0165, RFC 0170, RFC 0176, RFC 0184, RFC 0200, RFC 0210.*
@@ -1,15 +1,18 @@
1
1
  # Interop
2
2
 
3
- > **Status: Stable · RFC 0175.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `a2a`, `mcp`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- The v2 contract for the two embedded protocols (A2A and MCP): how a host advertises them, how a version is negotiated, and what every negotiation leaves behind. Capability shapes are in capabilities.md; the peer identity is the Subject of identity.md.
8
+ This document covers the two embedded protocols, A2A and MCP: how a host advertises them, how a version is negotiated, and what every negotiation leaves behind. Capability shapes are in [capabilities.md](capabilities.md); the peer identity is the Subject of [identity.md](identity.md).
9
9
 
10
10
  ## REST is the wire
11
11
 
12
- REST and SSE are the wire. A host MUST NOT advertise a transport list; `supportedTransports` does not exist in `schemas/v2/capabilities.schema.json`, and a discovery document carrying it MUST fail validation. A2A and MCP are **compositions** over the wire, advertised by their own facets and nothing else.
12
+ REST and SSE are the wire. A2A and MCP are compositions over it, advertised by their own facets and nothing else.
13
+
14
+ - A host MUST NOT advertise a transport list.
15
+ - A discovery document carrying `supportedTransports` MUST fail validation against `schemas/v2/capabilities.schema.json`.
13
16
 
14
17
  ## The facets
15
18
 
@@ -21,14 +24,16 @@ A host that speaks either protocol MUST advertise the corresponding facet — `a
21
24
  | Default | `preferredVersion` | `preferredVersion` | REQUIRED; served when the peer names none |
22
25
  | Floor | `minimumVersion` | `minimumRevision` | REQUIRED; below it negotiation fails closed |
23
26
  | Freshness | `refreshedAt` | `refreshedAt` | REQUIRED; see the refresh SLA |
24
- | Profiles | `profiles[]` `a2a-<major.minor>` | `profiles[]` `mcp-<date>` | no `-legacy` alternative exists |
27
+ | Profiles | `profiles[]` `a2a-<major.minor>` | `profiles[]` `mcp-<date>` | no `-legacy` suffix |
25
28
  | Protocol-specific | `agentCardUrl`, `streaming`, `pushNotifications`, `durableTasks` | `features[]`, `serverUrls[]`, `serverMount.transports[]` (`stdio` \| `streamable-http`), `mrtr.maxRounds` | optional |
26
29
 
27
30
  `mcp.serverMount.transports[]` is the MCP server's own transport enum; it is not a host transport advertisement.
28
31
 
29
32
  ## Legacy profiles are absent
30
33
 
31
- The profile ids `a2a-0.3-legacy` and `mcp-2025-06-18-legacy` do not exist in v2. The `profiles[]` item patterns admit no `-legacy` suffix, and the legacy code paths (the A2A 0.3 mapping and the MCP live-callback bridges) are not part of this corpus. A host that still speaks a legacy version does so as a private, non-advertised behavior. When no `A2A-Version` header is present, a host MUST serve the agent card of `preferredVersion`.
34
+ The `profiles[]` item patterns admit no `-legacy` suffix, so `a2a-0.3-legacy` and `mcp-2025-06-18-legacy` are not valid profile ids. A host that still speaks a legacy version does so as a private, non-advertised behavior.
35
+
36
+ When no `A2A-Version` header is present, a host MUST serve the agent card of `preferredVersion`.
32
37
 
33
38
  ## Negotiation is a protocol
34
39
 
@@ -43,23 +48,45 @@ The profile ids `a2a-0.3-legacy` and `mcp-2025-06-18-legacy` do not exist in v2.
43
48
  "negotiated": "…" | null, "outcome": "accepted" | "downgraded" | "refused", "reason": "…" }
44
49
  ```
45
50
 
46
- The event is content-free: `peer` MUST be a digest of the peer origin, never the origin in clear. The event on the host's own log is the normative witness of the two silent-downgrade invariants (`a2a-version-no-silent-downgrade`, `mcp-version-no-silent-downgrade`); the conformance seams profile (conformance.md) drives the exchange and captures the wire leg.
51
+ The event is content-free: `peer` MUST be a digest of the peer origin, never the origin in clear. The event is the normative witness of the invariants `a2a-version-no-silent-downgrade` and `mcp-version-no-silent-downgrade`; the seams profile ([conformance.md](conformance.md)) drives the exchange and captures the wire leg.
47
52
 
48
53
  **The refresh SLA.** A host MUST re-evaluate its advertised `versions[]` / `revisions[]` against the upstream registry within the window its `refreshedAt` declares, and that window MUST NOT exceed 90 days. An advertisement older than its window is non-conformant.
49
54
 
50
55
  **Downgrade above the floor.** A host MAY accept an authenticated request for a version between the floor and `preferredVersion`; the event then reports `outcome: downgraded`.
51
56
 
52
- ## The operation mappings (RFC 0208)
57
+ ## The operation mappings
58
+
59
+ `spec/v2/interop-map.json` (schema `interop-map.schema.json`) maps each profile's upstream operations, states, fields and errors to the v2 wire, pinned to an upstream release. A host advertising a profile:
60
+
61
+ - MUST serve every row it implements as the row states, under the caller's Subject with the authorization, tenant scoping and state of the v2 operation the row names;
62
+ - MUST refuse a row whose `requires` facet it does not advertise with the row's error;
63
+ - MUST list every feature the map requires for that profile.
64
+
65
+ What the map does not name is opaque: it MUST round-trip where upstream requires it and MUST NOT become authority, a prompt segment, a tool call or a workflow variable. A patch release that re-maps a row is a map edit; patch numbers are never negotiated.
53
66
 
54
- `spec/v2/interop-map.json` (schema `interop-map.schema.json`) maps each profile's upstream operations, states, fields and errors to the v2 wire, pinned to an upstream release. A host advertising a profile MUST serve every row it implements as the row states, under the caller's Subject with the authorization, tenant scoping and state of the v2 operation the row names; MUST refuse a row whose `requires` facet it does not advertise with the row's error; and MUST list every feature the map requires for that profile. What the map does not name is opaque: it MUST round-trip where upstream requires it and MUST NOT become authority, a prompt segment, a tool call or a workflow variable. A patch release that re-maps a row is a map edit; patch numbers are never negotiated.
67
+ **Isolation.** On either interface:
55
68
 
56
- **Isolation.** On either interface, a task the caller could not read through `getRun` MUST be answered exactly as a nonexistent one, including a tenant mismatch REST refuses `403`. `ListTasks` MUST return only runs `listRuns` would return to the same Subject, whether or not `runList` is advertised. `contextId`, `tenant` and `_meta` never select a tenant, workspace or principal.
69
+ - A task the caller could not read through `getRun` MUST be answered exactly as a nonexistent one, including a tenant mismatch REST refuses `403`.
70
+ - `ListTasks` MUST return only runs `listRuns` would return to the same Subject, whether or not `runList` is advertised.
71
+ - `contextId`, `tenant` and `_meta` never select a tenant, workspace or principal.
57
72
 
58
- **A2A multi-turn (A2A §3.4.3).** A message carrying `taskId` without `contextId` MUST be answered with the task's `contextId`. A message whose `contextId` is not its task's MUST be refused with its binding's invalid-parameters error and MUST NOT change the run. A message to a retained terminal task MUST be refused `UnsupportedOperationError`; `TaskNotFoundError` is for unknown, purged and unreadable tasks.
73
+ **A2A multi-turn (A2A §3.4.3).**
59
74
 
60
- **A2A error details (RFC 0211).** On a 1.0 `JSONRPC` interface, an A2A error's `error.data` MUST be an array of objects each carrying `@type`, including exactly one `type.googleapis.com/google.rpc.ErrorInfo` whose `reason` is the map row's `reason` and whose `domain` is `a2a-protocol.org` (a tightening of A2A §9.5's SHOULD). An `HTTP+JSON` interface MUST answer A2A §11.6's `google.rpc.Status`. A response on an interface URL the card lists, including a refusal before dispatch, MUST NOT be the OpenWOP error envelope. `TaskNotFoundError` details MUST NOT differ between an unknown task and an unreadable one, apart from an echo of the requested id. On `VersionNotSupportedError` a host SHOULD set `metadata.supportedVersions` to a comma-joined list; a client falls back to the card's `supportedInterfaces[].protocolVersion`. A client MUST identify an error by code or by the ErrorInfo `reason`, MUST accept `data` as an array, and SHOULD accept a `data` object carrying `reason` through 2.x.
75
+ - A message carrying `taskId` without `contextId` MUST be answered with the task's `contextId`.
76
+ - A message whose `contextId` is not its task's MUST be refused with its binding's invalid-parameters error and MUST NOT change the run.
77
+ - A message to a retained terminal task MUST be refused `UnsupportedOperationError`; `TaskNotFoundError` is for unknown, purged and unreadable tasks.
61
78
 
62
- ## MCP tasks and cancellation (RFC 0198)
79
+ **A2A error details.** A host:
80
+
81
+ - on a 1.0 `JSONRPC` interface, MUST make an A2A error's `error.data` an array of objects each carrying `@type`, including exactly one `type.googleapis.com/google.rpc.ErrorInfo` whose `reason` is the map row's `reason` and whose `domain` is `a2a-protocol.org` (a tightening of A2A §9.5's SHOULD);
82
+ - on an `HTTP+JSON` interface, MUST answer A2A §11.6's `google.rpc.Status`;
83
+ - MUST NOT answer with the OpenWOP error envelope on an interface URL the card lists, including a refusal before dispatch;
84
+ - MUST NOT vary `TaskNotFoundError` details between an unknown task and an unreadable one, apart from an echo of the requested id;
85
+ - on `VersionNotSupportedError`, SHOULD set `metadata.supportedVersions` to a comma-joined list; a client falls back to the card's `supportedInterfaces[].protocolVersion`.
86
+
87
+ A client MUST identify an error by code or by the ErrorInfo `reason`, MUST accept `data` as an array, and SHOULD accept a `data` object carrying `reason` through 2.x.
88
+
89
+ ## MCP tasks and cancellation
63
90
 
64
91
  A host MAY serve the MCP Tasks extension `io.modelcontextprotocol/tasks` (revision `2026-07-28`) on its server mount. It advertises it in its `server/discover` `capabilities.extensions` and by listing `extensions` in `mcp.features[]`, and nowhere else. A host that advertises it MUST implement the extension as published and the map's `mcp.tasks` rows, and:
65
92
 
@@ -67,7 +94,11 @@ A host MAY serve the MCP Tasks extension `io.modelcontextprotocol/tasks` (revisi
67
94
  - MUST use the run's projected `runId` (identity.md §5) as `taskId`, with an opaque segment of at least 128 bits of entropy. A `taskId` is never a credential;
68
95
  - MUST NOT append to a run's log to answer `tasks/get`.
69
96
 
70
- **Cancellation.** Until the host has sent its whole response to a request that starts or continues a run, the run belongs to that request: a client disconnect on streamable HTTP, or a stdio `notifications/cancelled` naming the request, MUST cancel the run as `cancelRun` would, with `run.cancelled.reason` `mcp-request-cancelled`. Once the response is sent, a disconnect MUST NOT affect the run; a task ends through `tasks/cancel`, `cancelRun`, or its own terminal state. A host MUST NOT send `notifications/cancelled` except to end a `subscriptions/listen` stream.
97
+ **Cancellation.** Until the host has sent its whole response to a request that starts or continues a run, the run belongs to that request.
98
+
99
+ - Before the response is sent, a client disconnect on streamable HTTP, or a stdio `notifications/cancelled` naming the request, MUST cancel the run as `cancelRun` would, with `run.cancelled.reason` `mcp-request-cancelled`.
100
+ - Once the response is sent, a disconnect MUST NOT affect the run; a task ends through `tasks/cancel`, `cancelRun`, or its own terminal state.
101
+ - A host MUST NOT send `notifications/cancelled` except to end a `subscriptions/listen` stream.
71
102
 
72
103
  ## The MCP round ceiling
73
104
 
@@ -77,28 +108,58 @@ A host MAY serve the MCP Tasks extension `io.modelcontextprotocol/tasks` (revisi
77
108
 
78
109
  `auth-required` remains a member of the persisted A2A task state enum (`schemas/v2/a2a-task-state.schema.json`) for the reverse direction (consuming an external A2A agent). The forward projection MUST emit it, with `interruptKind: credential` and a status message carrying `connectUrl`, for a run suspended on a `credential` interrupt (interrupt.md), and MUST NOT emit it otherwise.
79
110
 
80
- ## A2A push delivery (RFC 0214)
111
+ ## A2A push delivery
81
112
 
82
- A host advertising `a2a.pushNotifications` treats each push as a webhook egress: webhooks.md §Egress binds at delivery time as well as registration, a `3xx` is a failed delivery, and the push credential is bound as security-defaults.md §"Onward hops" states. It MUST attempt each push at least once; a host that retries follows `webhooks.md` `retryPolicy` semantics. The body is an A2A 1.0 `StreamResponse` sent as `application/a2a+json`, carrying `Authorization: {scheme} {credentials}` from the config; when only `token` is set a host SHOULD send it as `Authorization: Bearer <token>` (A2A v1.0.1 leaves its carriage undefined). A host MUST NOT add an OpenWOP signature. Push dead-letters are not visible to A2A clients; a client recovers with `GetTask`.
113
+ A host advertising `a2a.pushNotifications` treats each push as a webhook egress: webhooks.md §Egress binds at delivery time as well as registration, a `3xx` is a failed delivery, and the push credential is bound as security-defaults.md §"Onward hops" states.
83
114
 
84
- A `replay` fork MUST NOT push re-emitted history, and no fork inherits a source run's push configs. A push-config read or delete on a task the caller cannot read, or naming a `configId` that is not that task's, MUST answer exactly as for an unknown id, apart from the JSON-RPC `id`; a `configId` MUST NOT encode a tenant, workspace or principal, and delete is idempotent.
115
+ - A host MUST attempt each push at least once; a host that retries follows `webhooks.md` `retryPolicy` semantics.
116
+ - The body is an A2A 1.0 `StreamResponse` sent as `application/a2a+json`, carrying `Authorization: {scheme} {credentials}` from the config. When only `token` is set, a host SHOULD send it as `Authorization: Bearer <token>` (A2A v1.0.1 leaves its carriage undefined).
117
+ - A host MUST NOT add an OpenWOP signature.
118
+ - Push dead-letters are not visible to A2A clients; a client recovers with `GetTask`.
119
+ - A `replay` fork MUST NOT push re-emitted history, and no fork inherits a source run's push configs.
120
+ - A push-config read or delete on a task the caller cannot read, or naming a `configId` that is not that task's, MUST answer exactly as for an unknown id, apart from the JSON-RPC `id`.
121
+ - A `configId` MUST NOT encode a tenant, workspace or principal. Delete is idempotent.
85
122
 
86
123
  ## Per-agent cards
87
124
 
88
125
  A host advertising `a2a.agentCards` MUST also offer the `a2a-1.0` profile and `agents.manifestRuntime`, and MUST declare `capabilities.extendedAgentCard: true` on its public card. It publishes each entry of a caller's agent inventory (`GET /agents`) as an A2A `AgentCard`, reached through the entry's `a2aTenant`: an opaque routing value `R` the host mints, stable for the agent and host version, that MUST NOT encode a tenant, workspace, or principal.
89
126
 
90
- `GetExtendedAgentCard` with `tenant: R` MUST return that agent's card: `name` is the entry's `persona`, `version` its `packVersion`, `description` its `description` or else `label`; `supportedInterfaces[]` are the host card's interfaces, each carrying `tenant: R`; `capabilities` and `securitySchemes` equal the host card's; `skills[]` holds one skill per workflow the host routes to the agent for this caller. The card MUST NOT carry anything the inventory entry may not, and does not replace it: `degraded[]` and `memoryDegraded` stay on the entry.
127
+ `GetExtendedAgentCard` with `tenant: R` MUST return that agent's card:
91
128
 
92
- **Non-disclosure.** A request carrying `R` MUST be authenticated and authorized as `GET /agents/{agentId}` is, before `R` is resolved. For an `R` naming an agent outside the caller's inventory, every A2A operation MUST return what it returns for an `R` the host never minted, apart from the JSON-RPC `id`. The public card at `agentCardUrl` MUST NOT list any `R`. `R` is a `tenant` value under §"The operation mappings" **Isolation**.
129
+ - `name` is the entry's `persona`, `version` its `packVersion`, `description` its `description` or else `label`;
130
+ - `supportedInterfaces[]` are the host card's interfaces, each carrying `tenant: R`;
131
+ - `capabilities` and `securitySchemes` equal the host card's;
132
+ - `skills[]` holds one skill per workflow the host routes to the agent for this caller.
133
+
134
+ The card MUST NOT carry anything the inventory entry may not, and does not replace it: `degraded[]` and `memoryDegraded` stay on the entry.
135
+
136
+ **Non-disclosure.**
137
+
138
+ - A request carrying `R` MUST be authenticated and authorized as `GET /agents/{agentId}` is, before `R` is resolved.
139
+ - For an `R` naming an agent outside the caller's inventory, every A2A operation MUST return what it returns for an `R` the host never minted, apart from the JSON-RPC `id`.
140
+ - The public card at `agentCardUrl` MUST NOT list any `R`.
141
+ - `R` is a `tenant` value under §"The operation mappings" **Isolation**.
93
142
 
94
143
  ## gRPC
95
144
 
96
- gRPC is not part of the core wire. Its document lives at `spec/v2/ext/grpc-transport/` with `witness: unwitnessable` and `adoption: none`; its requirements are SHOULDs of that extension. A host MUST NOT advertise a `grpc` capability block — an unwitnessable family is not advertisable — and `api/v2/openapi.yaml` and the AsyncAPI document are the only canonical API descriptions. The extension re-enters core only by a v2.x additive RFC that generates the proto from `spec/v2/declaration.json` and lands a suite client.
145
+ gRPC is not part of the core wire. Its document lives at `spec/v2/ext/grpc-transport/` with `witness: unwitnessable` and `adoption: none`; its requirements are SHOULDs of that extension.
146
+
147
+ - A host MUST NOT advertise a `grpc` capability block — an unwitnessable family is not advertisable.
148
+ - `api/v2/openapi.yaml` and the AsyncAPI document are the only canonical API descriptions.
97
149
 
98
- ## Trace context (RFC 0207)
150
+ The extension re-enters core only by an additive RFC that generates the proto from `spec/v2/declaration.json` and lands a suite client.
99
151
 
100
- A host that propagates W3C Trace Context into an MCP request MUST carry it in that request's `params._meta` (unprefixed `traceparent`, and `tracestate` when present; MCP 2026-07-28 `_meta`, SEP-414) or in the HTTP `traceparent` header, and SHOULD use `_meta`, the only carrier on stdio. Into an A2A message it MUST carry it in `Message.metadata.openwop.traceparent` and `.tracestate` or in the HTTP header, and SHOULD use the metadata. A receiver prefers the in-message value, ignores a malformed one, and MUST NOT derive tenant, principal or scope from either.
152
+ ## Trace context
153
+
154
+ A host that propagates W3C Trace Context:
155
+
156
+ - into an MCP request MUST carry it in that request's `params._meta` (unprefixed `traceparent`, and `tracestate` when present; MCP 2026-07-28 `_meta`, SEP-414) or in the HTTP `traceparent` header, and SHOULD use `_meta`, the only carrier on stdio;
157
+ - into an A2A message MUST carry it in `Message.metadata.openwop.traceparent` and `.tracestate` or in the HTTP header, and SHOULD use the metadata.
158
+
159
+ A receiver prefers the in-message value, ignores a malformed one, and MUST NOT derive tenant, principal or scope from either.
101
160
 
102
161
  ## Threat model
103
162
 
104
163
  `SECURITY/threat-model-interop.md` is the threat model for this document; its invariants are rows of `SECURITY/invariants.yaml`. Peer identity and authorization at the boundary are governed by security-defaults.md; a peer MUST NOT gain authority the caller's Subject does not hold.
164
+
165
+ *Sources: RFC 0175, RFC 0198, RFC 0207, RFC 0208, RFC 0211, RFC 0214.*