@openwop/spec-artifacts 2.42.6 → 2.42.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.
Files changed (63) hide show
  1. package/CORPUS-STAMP.json +64 -63
  2. package/api/seams-v2.yaml +187 -322
  3. package/api/v2/asyncapi.yaml +83 -113
  4. package/api/v2/openapi.yaml +339 -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 +2 -5
  9. package/spec/v1/core-standard-manifest.json +2 -2
  10. package/spec/v1/gaps.json +171 -1
  11. package/spec/v2/README.md +34 -36
  12. package/spec/v2/core/capabilities.md +186 -119
  13. package/spec/v2/core/conformance.md +78 -27
  14. package/spec/v2/core/connection-packs.md +8 -3
  15. package/spec/v2/core/conversation.md +10 -4
  16. package/spec/v2/core/errors.md +30 -9
  17. package/spec/v2/core/events.md +116 -80
  18. package/spec/v2/core/form-content-packs.md +11 -13
  19. package/spec/v2/core/headers.md +22 -14
  20. package/spec/v2/core/host-services.md +22 -5
  21. package/spec/v2/core/idempotency.md +48 -36
  22. package/spec/v2/core/identity.md +110 -42
  23. package/spec/v2/core/interop.md +82 -21
  24. package/spec/v2/core/interrupt.md +79 -40
  25. package/spec/v2/core/node-pack-runtimes.md +18 -4
  26. package/spec/v2/core/oauth.md +40 -6
  27. package/spec/v2/core/overview.md +61 -33
  28. package/spec/v2/core/packs.md +53 -28
  29. package/spec/v2/core/persistence.md +61 -80
  30. package/spec/v2/core/replay.md +56 -42
  31. package/spec/v2/core/runs.md +198 -60
  32. package/spec/v2/core/security-defaults.md +57 -33
  33. package/spec/v2/core/tool-catalog.md +29 -6
  34. package/spec/v2/core/versioning.md +95 -49
  35. package/spec/v2/core/webhooks.md +59 -20
  36. package/spec/v2/core/workflow-chain-packs.md +16 -14
  37. package/spec/v2/corrections.json +17 -1
  38. package/spec/v2/corrections.schema.json +1 -1
  39. package/spec/v2/declaration.json +24 -7
  40. package/spec/v2/declaration.schema.json +10 -0
  41. package/spec/v2/errors.json +109 -2
  42. package/spec/v2/errors.schema.json +38 -0
  43. package/spec/v2/ext/README.md +36 -23
  44. package/spec/v2/ext/a2uiSurface/README.md +75 -89
  45. package/spec/v2/ext/brand/README.md +15 -15
  46. package/spec/v2/ext/canvas/README.md +15 -15
  47. package/spec/v2/ext/chat/README.md +15 -15
  48. package/spec/v2/ext/coordination/README.md +15 -15
  49. package/spec/v2/ext/dataIntegration/README.md +17 -20
  50. package/spec/v2/ext/entities/README.md +15 -15
  51. package/spec/v2/ext/grpc-transport/README.md +6 -8
  52. package/spec/v2/ext/kanban/README.md +15 -15
  53. package/spec/v2/ext/knowledge/README.md +15 -15
  54. package/spec/v2/ext/launchStudio/README.md +16 -16
  55. package/spec/v2/ext/messaging/README.md +15 -15
  56. package/spec/v2/ext/portability/README.md +3 -17
  57. package/spec/v2/ext/provider-idempotency/README.md +7 -10
  58. package/spec/v2/ext/restTransport/README.md +30 -19
  59. package/spec/v2/ext/sandbox-runtime-notes/README.md +7 -9
  60. package/spec/v2/ext/webResearch/README.md +16 -16
  61. package/spec/v2/profiles.json +6 -2
  62. package/spec/v2/release.json +2 -2
  63. package/spec/v2/surface-baseline.json +542 -2
@@ -1,27 +1,29 @@
1
1
  # Conformance
2
2
 
3
- > **Status: Stable · RFC 0168.**
3
+ > **Status: Stable.**
4
4
 
5
5
  ## Why this exists
6
6
 
7
- The v2 evidence contract: how a requirement is asserted, how every requirement declares what can witness it, how the seams are mounted, what the suite ships, and what a bundle proves. Profiles are in overview.md; the capability vocabulary the suite gates on is capabilities.md.
7
+ The v2 evidence contract: how a requirement is asserted, how every requirement declares what can witness it, 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
8
 
9
9
  ## Requirement ids
10
10
 
11
- `expect(x, req('openwop.<area>.<slug>', '<doc> §<section>', '<requirement>'))` is the only assertion form. A scenario assertion without a requirement id MUST fail the suite's lint. Ids are minted in `conformance/requirements.json`; every test declares its id explicitly. A title reword without a corresponding `requirement-aliases.json` row MUST fail CI.
11
+ `expect(x, req('openwop.<area>.<slug>', '<doc> §<section>', '<requirement>'))` is the only assertion form. Ids are minted in `conformance/requirements.json`, and every test declares its id explicitly.
12
12
 
13
- The ledger records per `it`, and a bundle's `results.requirements[]` is the per-assertion list. A post-assertion soft-skip MUST record `skipped` for every id not reached and MUST NOT record `pass`.
13
+ - A scenario assertion without a requirement id MUST fail the suite's lint.
14
+ - A title reword without a corresponding `requirement-aliases.json` row MUST fail CI.
15
+
16
+ The ledger records per `it`; a bundle's `results.requirements[]` is the per-assertion list. A post-assertion soft-skip MUST record `skipped` for every id not reached and MUST NOT record `pass`.
14
17
 
15
18
  ### Whose fact is the reason?
16
19
 
17
- A soft-skip carries a disposition and a reason. The reason MUST identify a fact
18
- about the host under test. Use `inapplicable` only when the requirement does not
19
- bind that host. A missing fixture, unreadable corpus file, or other suite-side
20
- failure MUST be `blocked`, never `inapplicable`; a suite with a blocked row MUST
21
- NOT issue a certification (RFC 0168 §E.1).
20
+ A soft-skip carries a disposition and a reason. The reason MUST identify a fact about the host under test.
21
+
22
+ - Use `inapplicable` only when the requirement does not bind that host.
23
+ - A missing fixture, unreadable corpus file, or other suite-side failure MUST be `blocked`, never `inapplicable`.
24
+ - A suite with a blocked row MUST NOT issue a certification.
22
25
 
23
- When more than one gate can skip a test, evaluate host predicates before suite
24
- predicates.
26
+ When more than one gate can skip a test, evaluate host predicates before suite predicates.
25
27
 
26
28
  ## Witness class
27
29
 
@@ -36,19 +38,27 @@ Every family in `spec/v2/declaration.json`, every requirement in `conformance/re
36
38
  | `negative-existence` | the suite asserts a thing is absent |
37
39
  | `unwitnessable` | no observation path exists; `rationale` REQUIRED |
38
40
 
39
- A protocol-tier invariant marked `unwitnessable` MUST fail the corpus gate. `tests: []` is expressible only as `unwitnessable`. A bundle disposition `blocked` does not exist as a witness class: what v1 called blocked is `seam-gated` or `unwitnessable`.
40
-
41
- A MUST whose only witness is `seam-gated` MUST either mint a normative observation path before the cut or be demoted to SHOULD. The seam count in `docs/witness-baseline.json` is a ratchet and MUST NOT rise.
41
+ - A protocol-tier invariant marked `unwitnessable` MUST fail the corpus gate.
42
+ - `tests: []` is expressible only as `unwitnessable`.
43
+ - `blocked` is a bundle disposition, not a witness class.
44
+ - A MUST whose only witness is `seam-gated` MUST either mint a normative observation path before the cut or be demoted to SHOULD.
45
+ - The seam count in `docs/witness-baseline.json` is a ratchet and MUST NOT rise.
42
46
 
43
47
  ## The seams profile
44
48
 
45
- 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/`, in the path space `/conformance/seams/…`. The seam schemas `$ref` the canonical error and event schemas with no tolerance path. A host that mounts the seams MUST advertise the profile as `conformance.seamsProfile: "openwop-conformance-seams-v2"` at the discovery root (RFC 0168 §C.1, erratum 2026-09-05; the closed root has no `profiles[]`, capabilities.md §3); a `testSeams` capability flag does not exist and MUST NOT be advertised. `api/v2/openapi.yaml` and `spec/v2/path-manifest.json` MUST contain no seam or sample-host operation; an SDK generated from the canonical document has no seam method. The profile is versioned with the suite (`seams-v2` for 2.x).
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/`, in the path space `/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
+ - 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
+ - A host MUST NOT advertise a `testSeams` capability flag.
53
+ - `api/v2/openapi.yaml` and `spec/v2/path-manifest.json` MUST contain no seam or sample-host operation; an SDK generated from the canonical document has no seam method.
46
54
 
47
55
  ## Two products, two ledgers
48
56
 
49
57
  Corpus-coherence checks run in the spec repo's CI (`scripts/check-spec-coherence.mjs`) and MUST NOT appear in a host bundle; the bundle schema forbids their ids. `--offline` is a declared property of a scenario, not a runtime discovery.
50
58
 
51
- `@openwop/openwop-conformance@2.0.0` ships `dist`, `fixtures`, and `vectors` only. The corpus — `api/`, `schemas/`, the `spec/v2/*.json` registries, `CORPUS-STAMP.json` — is `@openwop/spec-artifacts@2.0.0`, an exact-pinned peer dependency the suite MUST digest-check at start and MUST refuse to run against on a mismatch. The suite is one package: `--target-major 1|2` selects the target (default: the host's `preferredVersion`), scenario ids share one namespace across majors, and the 1.x target is removed at v1 end-of-support.
59
+ - `@openwop/openwop-conformance@2.0.0` ships `dist`, `fixtures` and `vectors` only.
60
+ - The corpus — `api/`, `schemas/`, the `spec/v2/*.json` registries, `CORPUS-STAMP.json` — is `@openwop/spec-artifacts@2.0.0`, an exact-pinned peer dependency. The suite MUST digest-check it at start and MUST refuse to run against it on a mismatch.
61
+ - The suite is one package: `--target-major 1|2` selects the target (default: the host's `preferredVersion`), and scenario ids share one namespace across majors. The 1.x target is removed at v1 end-of-support.
52
62
 
53
63
  ## Bundle v3
54
64
 
@@ -57,7 +67,7 @@ A certification bundle validates against `schemas/v2/certification-bundle.schema
57
67
  | Field | Rule |
58
68
  | --- | --- |
59
69
  | `suite` | `name`, `version`, `targetMajor`, `specArtifactsVersion` REQUIRED |
60
- | `host` | `name`, `version`, `build.{kind, id}` REQUIRED; `kind` is `image-digest`, `commit`, or `artifact-sha256` |
70
+ | `host` | `name`, `version`, `build.{kind, id}` REQUIRED; `kind` is `image-digest`, `commit` or `artifact-sha256` |
61
71
  | `host.deployment` | OPTIONAL; `colocated-companion` only (below). Absent: the bundle measures the served host |
62
72
  | `discovery` | `url`, `sha256`, `protocolVersions`, `preferredVersion` REQUIRED |
63
73
  | `claimedProfiles[]` | `id`, `evidenceTier` (`self` \| `steward` \| `independent`), `witnessCount`, `certified` REQUIRED |
@@ -69,29 +79,70 @@ A certification bundle validates against `schemas/v2/certification-bundle.schema
69
79
  | `durability.rung` | OPTIONAL; a claim the verifier re-derives (below) |
70
80
  | `signature` | REQUIRED |
71
81
 
72
- `signature` is an Ed25519 attestation over the JCS bytes (§"Canonical JSON") of `{ witnessSha256, host.build, suite.version, discovery.sha256 }`; `over` MUST list exactly those four members. A host that signs bundles MUST publish the corresponding public keys as `signingKeys[]` in its discovery document, and `signature.keyId` MUST name one of them. 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.
82
+ ### Signature and attribution
83
+
84
+ `signature` is an Ed25519 attestation over the JCS bytes (§"Canonical JSON") of `{ witnessSha256, host.build, suite.version, discovery.sha256 }`; `over` MUST list exactly those four members.
85
+
86
+ - A host that signs bundles MUST publish the corresponding public keys as `signingKeys[]` in its discovery document, and `signature.keyId` MUST name one of them.
87
+ - 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
+ - A retired key MUST stay listed.
89
+
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 that a presence check collapses into one: *no discovery document was read*, *read and the key is not published*, and *the attestation does not verify*.
91
+
92
+ `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
+ ### Certification
73
95
 
74
- A signature that cannot be resolved to a published key attests **integrity only**. Such a bundle MUST NOT be read as attributable evidence, and a gate MUST distinguish three outcomes that a presence check collapses into one — *no discovery document was read*, *read and the key is not published*, and *the attestation does not verify*. A retired key MUST stay listed. `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. A bundle with `totals.blocked > 0` does not certify. At major 2 a requirement a test did not observe records `blocked` even when the test asserted setup facts first; an `executed-pass` carrying a `partial-witness:` detail is reserved for a leg that observed its requirement and skipped an optional extra. A verifier MUST derive the operator's opt-outs from the signed `skipped` rows and MUST reject a bundle whose captured discovery document advertises one of them (`opted-out-but-advertised`). v1 and v2 bundles are never upgraded to v3; a bundle is evidence at its own version.
96
+ - A bundle with `totals.blocked > 0` does not certify.
97
+ - At major 2 a requirement a test did not observe records `blocked`, even when the test asserted setup facts first.
98
+ - An `executed-pass` carrying a `partial-witness:` detail is reserved for a leg that observed its requirement and skipped an optional extra.
99
+ - A verifier MUST derive the operator's opt-outs from the signed `skipped` rows and MUST reject a bundle whose captured discovery document advertises one of them (`opted-out-but-advertised`).
100
+ - v1 and v2 bundles are never upgraded to v3; a bundle is evidence at its own version.
75
101
 
76
- A bundle cut from a *colocated companion* — the served host's image, run beside the suite so it can trust a suite-held trust anchor — MUST carry `host.deployment: "colocated-companion"`, which the preimage covers (§"Canonical JSON"). A host serving production traffic MUST NOT list a suite-held trust anchor among the trust roots it advertises. A companion is evidence only for the requirements in `spec/v2/harness-trust-anchors.json`, and only when it pairs with a certified served-host bundle of the same `image-digest` build, signed under a key that bundle's discovery publishes, whose discovery document is equal once each document's origin and the `oidc` lane's `issuers` are set aside (RFC 0216).
102
+ ### Colocated companion
103
+
104
+ A *colocated companion* is the served host's image run beside the suite, so it can trust a suite-held trust anchor.
105
+
106
+ - A bundle cut from one MUST carry `host.deployment: "colocated-companion"`, which the preimage covers (§"Canonical JSON").
107
+ - A host serving production traffic MUST NOT list a suite-held trust anchor among the trust roots it advertises.
108
+ - A companion is evidence only for the requirements in `spec/v2/harness-trust-anchors.json`, and only when it pairs with a certified served-host bundle of the same `image-digest` build, signed under a key that bundle's discovery publishes, whose discovery document is equal once each document's origin and the `oidc` lane's `issuers` are set aside.
77
109
 
78
110
  ### Canonical JSON
79
111
 
80
- Every signature and digest in this corpus is over the RFC 8785 (JCS) serialization, UTF-8 encoded (RFC 0212). The value MUST be I-JSON (RFC 7493): a signer or hasher MUST refuse, not coerce, a value with duplicate member names, a lone surrogate, a non-finite number, an integer literal whose magnitude exceeds 2^53 − 1, or a non-JSON value, and a verifier that meets one in a document it must re-canonicalize MUST fail verification. `conformance/vectors/jcs-v1.json` is normative.
112
+ Every signature and digest in this corpus is over the RFC 8785 (JCS) serialization, UTF-8 encoded. `conformance/vectors/jcs-v1.json` is normative.
113
+
114
+ The value MUST be I-JSON (RFC 7493):
115
+
116
+ - A signer or hasher MUST refuse, not coerce, a value with duplicate member names, a lone surrogate, a non-finite number, an integer literal whose magnitude exceeds 2^53 − 1, or a non-JSON value.
117
+ - A verifier that meets one in a document it must re-canonicalize MUST fail verification.
118
+
119
+ `witnessSha256` is SHA-256 over the JCS bytes of the rows:
81
120
 
82
- `witnessSha256` is SHA-256 over the JCS bytes of the rows — one object per `results.requirements[]` entry with exactly `id`, `scenario`, `result` and, each only when present, `assertions`, `detail`, `evidence`, sorted by `id` in UTF-16 code-unit order — or, only when `host.relaxations[]` is non-empty or `host.deployment` is present, of `{ "rows": …, "relaxations": …, "deployment": … }` carrying each of the last two only when so, with relaxations in the order carried. A locale-sensitive comparator MUST NOT be used. `discovery.sha256` is SHA-256 over the JCS bytes of the captured document.
121
+ - One object per `results.requirements[]` entry, with exactly `id`, `scenario`, `result` and, each only when present, `assertions`, `detail`, `evidence`.
122
+ - Rows sorted by `id` in UTF-16 code-unit order. A locale-sensitive comparator MUST NOT be used.
123
+ - Only when `host.relaxations[]` is non-empty or `host.deployment` is present, the preimage is instead `{ "rows": …, "relaxations": …, "deployment": … }`, carrying each of the last two only when so, with relaxations in the order carried.
124
+
125
+ `discovery.sha256` is SHA-256 over the JCS bytes of the captured document.
83
126
 
84
127
  ### Recovery evidence
85
128
 
86
- Recovery evidence (RFC 0158 §E) rides on rows, which `witnessSha256` digests: a row's `evidence` enters the digest only when present, and a bundle without it digests as it always did.
129
+ Recovery evidence rides on rows: a row's `evidence` enters `witnessSha256` only when present.
87
130
 
88
131
  | Row | `evidence` member |
89
132
  | --- | --- |
90
- | `0158.bound-is-derived` | `recoveryBounds[]` of `{ class, bound, terms[] }`, each term `{ name, ms }`; `bound` MUST equal the sum of its terms. One entry per recovery class; there is no aggregate bound. |
91
- | `0158.kill-after-accept`, `0158.kill-during-execution` | `recovery: { class, boundMs, observedMs }` — the class exercised, the bound it was judged against, and the observed kill-to-resumption interval. |
133
+ | `0158.bound-is-derived` | `recoveryBounds[]` of `{ class, bound, terms[] }`, each term `{ name, ms }` |
134
+ | `0158.kill-after-accept`, `0158.kill-during-execution` | `recovery: { class, boundMs, observedMs }` |
135
+
136
+ - In `recoveryBounds[]`, `bound` MUST equal the sum of its terms. There is one entry per recovery class and no aggregate bound.
137
+ - In `recovery`, `class` is the class exercised, `boundMs` the bound applied, `observedMs` the kill-to-resumption interval.
138
+ - `class` and `name` are opaque host-chosen identifiers, never a closed vocabulary.
92
139
 
93
- `class` and `name` are opaque host-chosen identifiers, never a closed vocabulary. `durability.rung` is outside the attestation, so a verifier MUST re-derive it and MUST reject (`rung-not-derivable`) a claim it cannot derive: every row of the rung `executed-pass`, each kill row's `class` naming 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. This proves arithmetic and that recovery ran once inside the bound. It does not prove the kill landed in the class it names; killing only once the execution claim is held is the host's obligation.
140
+ `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
+ This proves the arithmetic and that recovery ran once inside the bound. It does not prove the kill landed in the class it names; killing only once the execution claim is held is the host's obligation.
94
143
 
95
144
  ## Corpus-gate evidence
96
145
 
97
- 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; the accepted-predicate check reads `(corpus)` rows from `evidence/corpus-ledger.json` for such an RFC and MUST NOT require a host bundle for it.
146
+ 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
+
148
+ *Sources: RFC 0158, RFC 0168, RFC 0212, RFC 0216.*
@@ -1,11 +1,11 @@
1
1
  # Connection Packs
2
2
 
3
- > **Status: Stable · RFC 0177, RFC 0095.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `connections`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- A connection pack is a signed provider definition — the endpoints, scope catalog, and reach a connector's `auth.provider` string resolves against. The manifest is `schemas/v2/connection-pack-manifest.schema.json`; installation and signing follow packs.md.
8
+ A connection pack is a signed provider definition — the endpoints, scope catalog, and reach a connector's `auth.provider` string resolves against. The manifest is `schemas/v2/connection-pack-manifest.schema.json`; installation and signing follow [packs.md](packs.md).
9
9
 
10
10
  ## Provider identity
11
11
 
@@ -26,8 +26,13 @@ A connector MAY name a provider by its qualified form `<packName>#<id>`. A quali
26
26
 
27
27
  ## Resolution
28
28
 
29
- A host advertising `connections.packsSupported` MUST resolve a connector's `auth: { type: "oauth2", provider: P }` (and any `host.oauth` invocation for `P`) against the definition selected above to obtain its endpoints and scope catalog. An unresolvable provider MUST be refused when the dependent connector or pack is registered. On a publish-path host, resolution MUST run after the idempotency short-circuit: a byte-identical re-publish of an installed pack MUST succeed even when resolution inputs have since changed.
29
+ A host advertising `connections.packsSupported` MUST resolve a connector's `auth: { type: "oauth2", provider: P }` (and any `host.oauth` invocation for `P`) against the definition selected above, to obtain its endpoints and scope catalog.
30
+
31
+ - An unresolvable provider MUST be refused when the dependent connector or pack is registered.
32
+ - On a publish-path host, resolution MUST run after the idempotency short-circuit: a byte-identical re-publish of an installed pack MUST succeed even when resolution inputs have since changed.
30
33
 
31
34
  ## Errors
32
35
 
33
36
  Both codes are in `spec/v2/errors.json`: `connection_provider_conflict` (two claimants for one bare id) and `connection_provider_unresolved` (no definition for the referenced id).
37
+
38
+ *Sources: RFC 0095, RFC 0177.*
@@ -1,15 +1,19 @@
1
1
  # Conversation surfaces
2
2
 
3
- > **Status: Stable · v2.0 · RFC 0101, 0109, 0110.** Normative contract for the multi-party conversation families.
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `multiPartyConversation`, `channelPresence`, `conversationTurnModelProvenance`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- RFC 0101 and RFC 0110 mint no client route for opening a conversation; the obligation an advertising host takes on is stated here.
8
+ No client route opens a conversation. This document states the obligations a host takes on by advertising a multi-party conversation family.
9
9
 
10
10
  ## `multiPartyConversation`
11
11
 
12
- A host advertising `multiPartyConversation` MUST accept an optional `participants` array of agent references on conversation creation, and MUST refuse a turn from a principal absent from that roster rather than silently accepting it. It MUST refuse a roster that exceeds `multiPartyConversation.maxParticipants` at creation rather than truncating it.
12
+ A host advertising `multiPartyConversation`:
13
+
14
+ - MUST accept an optional `participants` array of agent references on conversation creation;
15
+ - MUST refuse a turn from a principal absent from that roster, rather than silently accepting it;
16
+ - MUST refuse, at creation, a roster that exceeds `multiPartyConversation.maxParticipants`, rather than truncating it.
13
17
 
14
18
  ## `channelPresence`
15
19
 
@@ -17,4 +21,6 @@ A host advertising `channelPresence` MUST report present members as a subset of
17
21
 
18
22
  ## `conversationTurnModelProvenance`
19
23
 
20
- A host that stamps model provenance on an agent turn MUST advertise `conversationTurnModelProvenance`. The stamp is non-secret and non-PII — provider and model identifiers only — and a host MUST NOT place prompt or completion content in it.
24
+ A host that stamps model provenance on an agent turn MUST advertise `conversationTurnModelProvenance`. The stamp is non-secret and non-PII (provider and model identifiers only), and a host MUST NOT place prompt or completion content in it.
25
+
26
+ *Sources: RFC 0101, RFC 0109, RFC 0110.*
@@ -1,30 +1,49 @@
1
1
  # Errors
2
2
 
3
- > **Status: Stable · RFC 0171 §B.**
3
+ > **Status: Stable.**
4
4
 
5
5
  ## Why this exists
6
6
 
7
- Every error a v2 host returns is a row in one registry. A client routes on `error`, never on `message`, and a code that is not registered is not a protocol error. The registry is the single source for the envelope schema, the HTTP status, and retriability.
7
+ Every error a v2 host returns is a row in one registry. A client routes on `error`, never on `message`. A code that is not registered is not a protocol error. The registry is the single source for the envelope schema, the HTTP status and retriability.
8
8
 
9
9
  ## The registry
10
10
 
11
- `spec/v2/errors.json` holds one row per code: `{ code, httpStatus, retriable, details, since, deprecated? }` plus the provenance fields `statusSource` and `source`. It registers **107** codes. `schemas/v2/error-envelope.schema.json` is GENERATED from it and MUST NOT be edited by hand.
11
+ `spec/v2/errors.json` holds one row per code: `{ code, httpStatus, retriable, details, since, deprecated? }` plus the provenance fields `statusSource` and `source`, and an optional one-line `meaning`. `spec/v2/errors.schema.json` defines the row. It registers **107** codes. `schemas/v2/error-envelope.schema.json` is GENERATED from it and MUST NOT be edited by hand.
12
12
 
13
- A host MUST return a registered code, or a vendor code, in every error response. 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. The registry grows by overview.md §0.
13
+ - A host MUST return a registered code, or a vendor code, in every error response.
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.
15
+ - The registry grows by [overview.md](overview.md) §0.
14
16
 
15
17
  ## The envelope
16
18
 
17
- Every error response body MUST be `{ error, message, details? }` and nothing else (`additionalProperties: false`). `error` is the registered code or a vendor code; `message` is a non-empty string; `details` is an object whose shape is the row's `details` schema. A row whose `details` is `null` accepts any object; when a row registers a schema, the generated envelope becomes a `oneOf` discriminated on `error`. Contextual data (conflict refs, trace ids, validation paths) MUST live under `details`, never at a new top level. The same envelope is the per-id `error` of `bulkCancelRuns` (runs.md). When present, `details.correlationId` MUST be a non-empty string.
19
+ Every error response body MUST be `{ error, message, details? }` and nothing else (`additionalProperties: false`).
18
20
 
19
- `x-openwop-http-status` and `x-openwop-retriable` in the generated schema mirror the registry; a host MUST answer with the registered status.
21
+ - `error` is the registered code or a vendor code. `message` is a non-empty string.
22
+ - `details` is an object shaped by the row's `details` schema. A row whose `details` is `null` accepts any object. When a row registers a schema, the generated envelope becomes a `oneOf` discriminated on `error`.
23
+ - Contextual data (conflict refs, trace ids, validation paths) MUST live under `details`, never at a new top level.
24
+ - When present, `details.correlationId` MUST be a non-empty string.
25
+
26
+ The same envelope is the per-id `error` of `bulkCancelRuns` ([runs.md](runs.md)). `x-openwop-http-status` and `x-openwop-retriable` in the generated schema mirror the registry; a host MUST answer with the registered status.
20
27
 
21
28
  ## Retry timing
22
29
 
23
- Retry timing lives in the `Retry-After` header only. `details.retryAfter`, `details.retryAfterMs` and `details.retryAfterSeconds` are not part of v2 and a host MUST NOT emit them. A `429 rate_limited` response MUST set `Retry-After`. The retriable rows are `residency_unavailable`, `rate_limited`, `internal_error`, `pack_registry_unreachable`, `runner_unavailable`.
30
+ Retry timing lives in the `Retry-After` header only.
31
+
32
+ - A host MUST NOT emit `details.retryAfter`, `details.retryAfterMs` or `details.retryAfterSeconds`.
33
+ - A `429 rate_limited` response MUST set `Retry-After`.
34
+
35
+ The retriable rows are `residency_unavailable`, `rate_limited`, `internal_error`, `pack_registry_unreachable`, `runner_unavailable`.
24
36
 
25
37
  ## One code per state
26
38
 
27
- An interrupt has one code per state: a token or run-scoped resolve against an interrupt that is already resolved, or whose run is cancelled or completed, MUST return `409 interrupt_already_resolved`; a signed token past its `expiresAt` MUST return `410 interrupt_expired`; a token whose `alg` or `kid` the host does not accept MUST return `401 interrupt_token_invalid` (see interrupt.md, identity.md). The idempotency mismatch code is `idempotency_key_mismatch` only (idempotency.md). `interrupt_cancelled` is registered and names no state of the core resolve surfaces; a host MUST NOT emit it from `resolveInterruptByRun`, `inspectInterruptByToken` or `resolveInterruptByToken` (RFC 0213 §C).
39
+ An interrupt has one code per state ([interrupt.md](interrupt.md), [identity.md](identity.md)):
40
+
41
+ - A token or run-scoped resolve against an interrupt that is already resolved, or whose run is cancelled or completed, MUST return `409 interrupt_already_resolved`.
42
+ - A signed token past its `expiresAt` MUST return `410 interrupt_expired`.
43
+ - A token whose `alg` or `kid` the host does not accept MUST return `401 interrupt_token_invalid`.
44
+ - `interrupt_cancelled` is registered but names no state of the core resolve surfaces. A host MUST NOT emit it from `resolveInterruptByRun`, `inspectInterruptByToken` or `resolveInterruptByToken`.
45
+
46
+ The idempotency mismatch code is `idempotency_key_mismatch` only ([idempotency.md](idempotency.md)).
28
47
 
29
48
  ## Codes by HTTP status
30
49
 
@@ -138,4 +157,6 @@ Code | Status
138
157
  `payload_unprojectable` | 500
139
158
  `credential_unavailable` | 501
140
159
  `pack_registry_unreachable` | 503
141
- `runner_unavailable` | 503
160
+ `runner_unavailable` | 503
161
+
162
+ *Sources: RFC 0171, RFC 0213.*
@@ -1,155 +1,191 @@
1
1
  # Events
2
2
 
3
- > **Status: Stable · RFC 0171 §A, §E; RFC 0176 §A.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `heartbeat`, `envelopeContracts`, `envelopes`, `feedback`, `providerUsage`, `supportedEnvelopes`, `schemaVersions`, `envelopeStrictness`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- A run is its append-only event log; every snapshot, stream, poll, fork and diff is a projection of it. v2 has one closed envelope, type registry, payload registry, ordering field, events channel and poll cursor.
8
+ A run is its append-only event log. Every snapshot, stream, poll, fork and diff is a projection of it. There is one closed envelope, type registry, payload registry, ordering field, events channel and poll cursor.
9
9
 
10
10
  ## The envelope
11
11
 
12
- `schemas/v2/run-event.schema.json` (`RunEventDoc`) is closed. `eventId`, `runId`, `type`, `payload`, `timestamp`, `sequence` and `schemaVersion` are REQUIRED; `nodeId`, `engineVersion` and `causationId` are OPTIONAL. Every id field `$ref`s its grammar in `schemas/v2/ids.schema.json` (identity.md).
12
+ `schemas/v2/run-event.schema.json` (`RunEventDoc`) is closed. `eventId`, `runId`, `type`, `payload`, `timestamp`, `sequence` and `schemaVersion` are REQUIRED; `nodeId`, `engineVersion` and `causationId` are OPTIONAL. Every id field `$ref`s its grammar in `schemas/v2/ids.schema.json` ([identity.md](identity.md)).
13
13
 
14
- | Field | Rule |
14
+ | Field | Meaning |
15
15
  | --- | --- |
16
- | `sequence` | The one ordering field: integer ≥ 0, first event `0`, strictly increasing per run. Persisted logs are never renumbered. |
17
- | `schemaVersion` | Per-event schema version, integer ≥ 1, first-class (RFC 0172 §B axis 5). |
18
- | `engineVersion` | Integer ≥ 0 everywhere (RFC 0172 §B axis 3). |
19
- | `eventId` | Host-minted, opaque; consumers MUST treat it as a string. |
20
- | `causationId` | The `eventId`, or AI-envelope `correlationId`, that caused this event. |
21
- | `timestamp` | ISO 8601. |
16
+ | `sequence` | The one ordering field: integer ≥ 0, first event `0`, strictly increasing per run |
17
+ | `schemaVersion` | Per-event schema version, integer ≥ 1, first-class |
18
+ | `engineVersion` | Integer ≥ 0 everywhere |
19
+ | `eventId` | Host-minted, opaque |
20
+ | `causationId` | The `eventId`, or AI-envelope `correlationId`, that caused this event |
21
+ | `timestamp` | ISO 8601 |
22
22
 
23
- A consumer MUST NOT throw on an event whose `type` it does not know; it folds what it understands and ignores the rest.
23
+ - Persisted logs are never renumbered.
24
+ - A consumer MUST treat `eventId` as a string.
25
+ - A consumer MUST NOT throw on an event whose `type` it does not know; it folds what it understands and ignores the rest.
24
26
 
25
27
  ## Types
26
28
 
27
- `type` is `oneOf` a closed enum of registered protocol types and a vendor pattern. The enum is GENERATED from `spec/v2/event-codemap.json` (118 rows, every row `decided`) and MUST NOT be edited by hand. The vendor branch is exactly:
29
+ `type` is `oneOf` a closed enum of registered protocol types and a vendor pattern. The enum is generated from `spec/v2/event-codemap.json` and MUST NOT be edited by hand. The vendor branch is exactly:
28
30
 
29
31
  ```text
30
32
  ^(?!openwop\.)[a-z][a-z0-9]*(-[a-z0-9]+)*\.[a-z][a-z0-9]*(-[a-z0-9]+)*(\.[a-z][a-z0-9]*(-[a-z0-9]+)*)?$
31
33
  ```
32
34
 
33
- | Rule | Requirement |
34
- | --- | --- |
35
- | Naming | A protocol type is `domain.verb-ed`: kebab-case, exactly two segments, past tense for a transition (`run.started`, `node.suspend-failed`, `run.resume-started`). `domain.noun` is permitted only for an emitted artifact (`output.chunk`, `provider.usage`, `channel.presence`, `agent.handoff`, `envelope.refusal`, `agent.reasoning-delta`, `voice.synthesis-chunk`, `voice.endpoint-candidate`); each exception is recorded in the codemap and checked by the corpus gate. |
36
- | Reserved prefix | `openwop.` is the only reserved prefix. `core.`, `community.`, `vendor.`, `private.` and `local.` are pack namespaces, not event namespaces, and a type under them is invalid. |
37
- | Vendor events | A vendor type's first segment MUST be an org registered in the `extensions` object of `spec/v2/declaration.json` — the ORG REGISTRY, not the `extensions` metadata key a host publishes in its own discovery payload; an unregistered org fails validation. An org named in `reservedOrgs` is forbidden, never registered, and `extensionsKeyPattern` is the shape a vendor type must have, not a permission to use it. The registry is normally small: `example` is held by the protocol for documentation and conformance and is never assignable to a vendor. |
38
- | Growth | The registry grows by the closed-enum rule in overview.md §0: a producer MUST NOT emit an unregistered protocol type; a consumer MUST accept an unknown registered member and MUST NOT act on it. |
35
+ - **Naming.** A protocol type is `domain.verb-ed`: kebab-case, exactly two segments, past tense for a transition (`run.started`, `node.suspend-failed`, `run.resume-started`). `domain.noun` is permitted only for an emitted artifact (`output.chunk`, `provider.usage`, `channel.presence`, `agent.handoff`, `envelope.refusal`, `agent.reasoning-delta`, `voice.synthesis-chunk`, `voice.endpoint-candidate`); each exception is recorded in the codemap.
36
+ - **Reserved prefix.** `openwop.` is the only reserved prefix. `core.`, `community.`, `vendor.`, `private.` and `local.` are pack namespaces, not event namespaces; a type under them is invalid.
37
+ - **Vendor events.** A vendor type's first segment MUST be an org registered in the `extensions` object of `spec/v2/declaration.json` (the org registry, not the `extensions` metadata key a host publishes in discovery). An unregistered org fails validation. An org in `reservedOrgs` is forbidden and never registered. `extensionsKeyPattern` is the shape a vendor type must have, not a permission to use it. `example` is held by the protocol for documentation and conformance and is never assignable to a vendor.
38
+ - **Growth.** The registry grows by the closed-enum rule in [overview.md](overview.md) §0. A producer MUST NOT emit an unregistered protocol type. A consumer MUST accept an unknown registered member and MUST NOT act on it.
39
39
 
40
40
  ## Payloads
41
41
 
42
- `schemas/v2/run-event-payloads.schema.json` holds one `$defs` entry per payload, every entry `additionalProperties: false`, and `_typeIndex`: the NORMATIVE map from v2 type to `$defs` key, GENERATED from `spec/v2/event-codemap.json`. A host MUST emit a payload that validates against the entry `_typeIndex` names for its `type`. Sub-typing is `$ref` composition, never duplication: `approval.*` and `clarification.*` resolve to `interruptRequested` / `interruptResolved` (interrupt.md); `lease.acquired`, `lease.renewed` and `lease.lost` share `leaseLifecycle`.
42
+ `schemas/v2/run-event-payloads.schema.json` holds one `$defs` entry per payload, each `additionalProperties: false`, plus `_typeIndex`: the normative map from v2 type to `$defs` key, generated from `spec/v2/event-codemap.json`.
43
+
44
+ - A host MUST emit a payload that validates against the entry `_typeIndex` names for its `type`.
45
+ - Sub-typing is `$ref` composition, never duplication. `approval.*` and `clarification.*` resolve to `interruptRequested` / `interruptResolved` ([interrupt.md](interrupt.md)); `lease.acquired`, `lease.renewed` and `lease.lost` share `leaseLifecycle`.
46
+ - The CloudEvents mapping and the webhook delivery envelope are generated from the same definition. An event's `type`, `eventId`, `sequence` and `payload` are byte-identical across the run stream, a CloudEvents rendering and a webhook delivery.
43
47
 
44
- The CloudEvents mapping and the webhook delivery envelope are GENERATED from the same definition (one source, three renderings): the event's `type`, `eventId`, `sequence` and `payload` are byte-identical across the run stream, a CloudEvents rendering and a webhook delivery.
48
+ Specific payloads:
45
49
 
46
- `run.started` carries the `owner` block (identity.md §1.1).
47
- `run.cancelled` carries `reason`, `cancelledBy`, `durationMs`, and `parentRunId`.
48
- `run.completed` MUST carry `outputs` as an object; an empty object is valid, but
49
- an absent key is not. This distinguishes “no outputs” from “outputs not
50
- rendered” and is witnessed by `v2-run-completed-outputs`.
50
+ - `run.started` carries the `owner` block ([identity.md](identity.md) §1.1).
51
+ - `run.cancelled` carries `reason`, `cancelledBy`, `durationMs`, and `parentRunId`.
52
+ - `run.completed` MUST carry `outputs` as an object. An empty object is valid; an absent key is not. This separates "no outputs" from "outputs not rendered".
51
53
 
52
54
  ## AI envelopes: E1–E5
53
55
 
54
- `schemas/v2/ai-envelope.schema.json` is the shape an LLM emits; the engine records its acceptance as one or more `RunEventDoc`s. In v2 `correlationId` and `meta.source` are REQUIRED on every envelope, and an engine MUST reject an envelope that omits either; nothing is synthesized. An envelope kind MUST be namespaced under the same `<org>.` rule as events, universal kinds and the core content-primitive families `ui.*` and `media.*` excepted.
56
+ `schemas/v2/ai-envelope.schema.json` is the shape an LLM emits; the engine records its acceptance as one or more `RunEventDoc`s.
55
57
 
56
- | Gap | Contract |
57
- | --- | --- |
58
- | E1 partial reassembly | Every chunk of one partial emission carries the same `correlationId`; the events that record them are ordered by `sequence`; the emission is complete at the first recorded chunk with `partial: false`. A consumer MAY render progressively but MUST NOT enable any action before that event. |
59
- | E2 multi-turn correlation | Each turn is an envelope with its own `correlationId`; every event it produces carries `causationId = correlationId`. A re-emission with a `correlationId` already recorded in the run MUST return the cached outcome and MUST NOT emit new events. |
60
- | E3 vendor kinds | The registry of vendor kinds is `spec/v2/declaration.json`; a kind whose org is not registered is invalid. |
61
- | E4 sub-typing | `$ref` composition, as in the payload registry above. |
62
- | E5 refusal × retry | `configurable.ai.maxRefusals` (runs.md) is the ceiling on `envelope.refusal` events a run records. A host MUST NOT retry the emission that produced a refusal. |
58
+ - `correlationId` and `meta.source` are REQUIRED on every envelope. An engine MUST reject an envelope that omits either; nothing is synthesized.
59
+ - An envelope kind MUST be namespaced under the same `<org>.` rule as events, universal kinds and the core content-primitive families `ui.*` and `media.*` excepted.
60
+
61
+ The five contracts:
62
+
63
+ - **E1 partial reassembly.** Every chunk of one partial emission carries the same `correlationId`, and the events that record them are ordered by `sequence`. The emission is complete at the first recorded chunk with `partial: false`. A consumer MAY render progressively but MUST NOT enable any action before that event.
64
+ - **E2 multi-turn correlation.** Each turn is an envelope with its own `correlationId`; every event it produces carries `causationId = correlationId`. A re-emission with a `correlationId` already recorded in the run MUST return the cached outcome and MUST NOT emit new events.
65
+ - **E3 vendor kinds.** The registry of vendor kinds is `spec/v2/declaration.json`. A kind whose org is not registered is invalid.
66
+ - **E4 sub-typing.** `$ref` composition, as in the payload registry above.
67
+ - **E5 refusal × retry.** `configurable.ai.maxRefusals` ([runs.md](runs.md)) is the ceiling on `envelope.refusal` events a run records. A host MUST NOT retry the emission that produced a refusal.
63
68
 
64
69
  ## The events channel
65
70
 
66
- `api/v2/asyncapi.yaml` declares one channel, `runEvents`, at `/runs/{runId}/events`, and `api/v2/openapi.yaml` declares the same path (`streamRunEvents`); the two MUST resolve to the same absolute path (RFC 0172 §C.2). The `streamMode` query parameter is one pattern, not four enums:
71
+ `api/v2/asyncapi.yaml` declares one channel, `runEvents`, at `/runs/{runId}/events`, and `api/v2/openapi.yaml` declares the same path (`streamRunEvents`). The two MUST resolve to the same absolute path.
72
+
73
+ ### Stream modes
74
+
75
+ The `streamMode` query parameter is one pattern:
67
76
 
68
77
  ```text
69
78
  ^(values|(updates|messages|debug)(,(updates|messages|debug))*)$
70
79
  ```
71
80
 
72
- The default is `updates`. A host MUST implement `updates` and SHOULD implement all four. A value outside the pattern, or a mode the host does not implement, MUST return `400 unsupported_stream_mode` with `details.supported` listing each individual mode the host serves; combinations are not listed. Validation MUST run before any content negotiation.
73
-
74
81
  | Mode | Emits | Combines |
75
82
  | --- | --- | --- |
76
- | `updates` | Run transitions, terminal node transitions, suspensions, `node.dispatched`, interrupt events, `artifact.created`, `eval.*`, `deployment.*`, `workspace.updated`; each payload is a delta | yes |
83
+ | `updates` (default) | Deltas for run transitions, terminal node transitions, suspensions, `node.dispatched`, interrupt events, `artifact.created`, `eval.*`, `deployment.*`, `workspace.updated` | yes |
77
84
  | `values` | One synthesized `state.snapshot` (`schemas/v2/run-snapshot.schema.json`) after each `updates`-tier transition | never |
78
- | `messages` | `ai.message.chunk` (`outputChunk` payload) from streaming AI nodes only; a host MUST populate a Tier 1 `meta` slot whenever it has the data | yes |
85
+ | `messages` | `ai.message.chunk` (`outputChunk` payload) from streaming AI nodes only | yes |
79
86
  | `debug` | Every event in the log, including `log.appended`, `variable.changed`, `version.pinned`, `lease.*`, `node.retried` and every vendor event | yes |
80
87
 
81
- Vendor events appear in `debug` only. In a mixed mode the host emits the union of the filters in log order and MUST NOT reorder; each frame SHOULD carry `event:` naming the mode that admitted it, and a consumer MUST tolerate an event admitted by more than one mode.
88
+ - A host MUST implement `updates` and SHOULD implement all four.
89
+ - A value outside the pattern, or a mode the host does not implement, MUST return `400 unsupported_stream_mode` with `details.supported` listing each individual mode the host serves; combinations are not listed.
90
+ - Validation MUST run before any content negotiation.
91
+ - In `messages`, a host MUST populate a Tier 1 `meta` slot whenever it has the data.
92
+ - Vendor events appear in `debug` only.
93
+ - In a mixed mode the host emits the union of the filters in log order and MUST NOT reorder. Each frame SHOULD carry `event:` naming the mode that admitted it. A consumer MUST tolerate an event admitted by more than one mode.
82
94
 
83
95
  ### SSE frames
84
96
 
85
- Each frame carries `id:`, `event:` and `data:`: `id:` is the `sequence`, `event:` is the v2 `type` (single mode), `data:` is the `RunEventDoc`. Three frame names are not types and are absent from the `type` enum: `state.snapshot` (`values`; `data:` a `RunSnapshot`), `batch` (`bufferMs`; `data:` an array of `RunEventDoc`) and `ai.message.chunk` (`messages`; `data:` the `outputChunk` payload, persisted type `output.chunk`). A host MUST set `Content-Type: text/event-stream`, MUST emit a keep-alive comment at least every 30 seconds, and MUST close the connection after the run's terminal event (`run.completed`, `run.failed`, `run.cancelled`).
97
+ Each frame carries `id:` (the `sequence`), `event:` (the v2 `type`, in a single mode) and `data:` (the `RunEventDoc`). Three frame names are not types and are absent from the `type` enum:
98
+
99
+ - `state.snapshot` — `values` mode; `data:` is a `RunSnapshot`.
100
+ - `batch` — `bufferMs`; `data:` is an array of `RunEventDoc`.
101
+ - `ai.message.chunk` — `messages` mode; `data:` is the `outputChunk` payload, persisted as type `output.chunk`.
102
+
103
+ A host MUST set `Content-Type: text/event-stream`, MUST emit a keep-alive comment at least every 30 seconds, and MUST close the connection after the run's terminal event (`run.completed`, `run.failed`, `run.cancelled`).
104
+
105
+ #### Resuming with `Last-Event-ID`
106
+
107
+ `Last-Event-ID: N` resumes every mode as an exclusive cursor, with the semantics of poll's `afterSequence`.
108
+
109
+ - The host MUST stream the events with `sequence > N` in log order and MUST NOT re-emit `N`.
110
+ - When `N` is at or beyond the last persisted sequence there is no backlog. On a live run the host MUST hold the stream open for later events; on a terminal run it MUST close the stream without a frame.
111
+ - A host MUST NOT refuse a well-formed non-negative integer `Last-Event-ID` because no event carries that sequence. Any other value SHOULD be refused with `400 validation_error`.
112
+ - The header is evaluated only after the caller is authorized to read the run. For a run the caller cannot read, the response MUST be the one the host gives without the header.
113
+ - In `values` mode, resumption MUST emit a `state.snapshot` first.
86
114
 
87
- `Last-Event-ID: N` resumes every mode as an exclusive cursor with the semantics of poll's `afterSequence`: the host MUST stream the events with `sequence > N` in log order and MUST NOT re-emit `N`. When `N` is at or beyond the last persisted sequence there is no backlog: on a live run the host MUST hold the stream open for later events, and on a terminal run it MUST close the stream without a frame. A host MUST NOT refuse a well-formed non-negative integer `Last-Event-ID` because no event carries that sequence; any other value SHOULD be refused with `400 validation_error`. The header is evaluated only after the caller is authorized to read the run: for a run the caller cannot read, the response MUST be the one the host gives without the header (RFC 0213 §A). In `values` mode resumption MUST emit a `state.snapshot` first. With `bufferMs` (0..5000) the host accumulates events into one `event: batch` frame whose `data:` is an array of `RunEventDoc`; it MUST flush on a terminal event, on `node.suspended`, and on close; the batch's `id:` SHOULD be its highest `sequence` and `Last-Event-ID` MUST honor that id. A consumer MUST tolerate both a one-element batch and an unbatched frame. A host MUST NOT limit subscribers per run except for resource protection, and then MUST answer `429 rate_limited` with `Retry-After` rather than drop silently.
115
+ #### Batching and subscribers
116
+
117
+ With `bufferMs` (0..5000) the host accumulates events into one `event: batch` frame whose `data:` is an array of `RunEventDoc`.
118
+
119
+ - It MUST flush on a terminal event, on `node.suspended`, and on close.
120
+ - The batch's `id:` SHOULD be its highest `sequence`, and `Last-Event-ID` MUST honor that id.
121
+ - A consumer MUST tolerate both a one-element batch and an unbatched frame.
122
+ - A host MUST NOT limit subscribers per run except for resource protection, and then MUST answer `429 rate_limited` with `Retry-After` rather than drop silently.
88
123
 
89
124
  ### Host events
90
125
 
91
- `hostEvents` carries the heartbeat messages (`schemas/v2/heartbeat-evaluated.schema.json`, `schemas/v2/heartbeat-state-changed.schema.json`) at `/host/events` (`streamHostEvents`), the documented default; a host MAY declare another address under `heartbeat.deliveryChannel` (capabilities.md). The channel is content-free of run data. There is no channel without an address.
126
+ `hostEvents` carries the heartbeat messages (`schemas/v2/heartbeat-evaluated.schema.json`, `schemas/v2/heartbeat-state-changed.schema.json`) at `/host/events` (`streamHostEvents`), the documented default. A host MAY declare another address under `heartbeat.deliveryChannel` ([capabilities.md](capabilities.md)). The channel is content-free of run data. There is no channel without an address.
92
127
 
93
128
  ## Poll
94
129
 
95
130
  `GET /runs/{runId}/events/poll` (`pollRunEvents`) is the long-poll fallback.
96
131
 
97
- | Parameter | Rule |
132
+ | Parameter | Meaning |
98
133
  | --- | --- |
99
- | `afterSequence` | Integer ≥ 0; the response carries events with `sequence > afterSequence`. Omission means "from the first event" (sequence 0). `lastSequence` and `since` are not parameters. |
100
- | `timeout` | Seconds to wait for new events, 1..60, default 30. |
134
+ | `afterSequence` | Integer ≥ 0; return events with `sequence > afterSequence`. Omitted means from sequence 0 |
135
+ | `timeout` | Seconds to wait for new events, 1..60, default 30 |
136
+
137
+ `lastSequence` and `since` are not parameters.
138
+
139
+ The response is `{ runId, events, lastSequence, status, isTerminal }` (closed):
101
140
 
102
- The response is `{ runId, events, lastSequence, status, isTerminal }` (closed): `lastSequence` is the highest sequence in the log at the time of the response, `-1` when the log is empty; `status` is the snapshot status; `isTerminal` is whether the run is terminal. A cursor past the end of the log MUST return `200` with an empty `events` array. The shape is declared here and generated into `api/v2/openapi.yaml` from one definition.
141
+ - `lastSequence` is the highest sequence in the log at the time of the response, `-1` when the log is empty.
142
+ - `status` is the snapshot status; `isTerminal` is whether the run is terminal.
143
+ - A cursor past the end of the log MUST return `200` with an empty `events` array.
103
144
 
104
145
  ## The terminal event
105
146
 
106
- A run's log MUST contain exactly one terminal run event — `run.completed`, `run.failed` or `run.cancelled` — and after it MUST NOT contain another terminal run event, `run.started`, `run.resumed`, `run.resume-started`, `run.paused`, `run.restored-from-snapshot`, any `node.*` event or any `interrupt.*` event. `compensation.*` events and `run.dead-lettered` MAY follow it (a compensating host unwinds after a cancelled parent); vendor-prefixed types are unconstrained. A host that receives work for a run whose terminal event is recorded — a duplicate delivery, a late worker — MUST NOT append forward-execution events for it, SHOULD record the refusal in its operational log, and MUST NOT surface the refusal as a run event (RFC 0194). No stream-closure rule changes.
147
+ A run's log MUST contain exactly one terminal run event: `run.completed`, `run.failed` or `run.cancelled`.
148
+
149
+ - After it, the log MUST NOT contain another terminal run event, `run.started`, `run.resumed`, `run.resume-started`, `run.paused`, `run.restored-from-snapshot`, any `node.*` event or any `interrupt.*` event.
150
+ - `compensation.*` events and `run.dead-lettered` MAY follow it (a compensating host unwinds after a cancelled parent). Vendor-prefixed types are unconstrained.
151
+ - A host that receives work for a run whose terminal event is recorded (a duplicate delivery, a late worker) MUST NOT append forward-execution events for it, SHOULD record the refusal in its operational log, and MUST NOT surface the refusal as a run event.
107
152
 
108
153
  ## Era-2 logs
109
154
 
110
- An `eventLogSchemaVersion` of `2` means v1-written; every reader, diff included, translates it per persistence.md §"The reader rule". **A projection MUST NOT silently drop a property**: carry or fail `500 payload_unprojectable` (hatch `^(openwop-|x-|vendor\.)`; RFC 0185). Fork and replay over an era-2 parent: replay.md.
155
+ An `eventLogSchemaVersion` of `2` means v1-written. Every reader, diff included, translates it per [persistence.md](persistence.md) §"The reader rule".
156
+
157
+ - A projection MUST NOT silently drop a property: it carries it or fails with `500 payload_unprojectable` (hatch `^(openwop-|x-|vendor\.)`).
158
+ - Fork and replay over an era-2 parent are in [replay.md](replay.md).
111
159
 
112
160
  ## The envelope-kind catalog
113
161
 
114
- `supportedEnvelopes`, `schemaVersions` and `envelopeStrictness` are one flow, read in
115
- that order on every inbound envelope.
162
+ `supportedEnvelopes`, `schemaVersions` and `envelopeStrictness` are one flow, read in that order on every inbound envelope.
116
163
 
117
- `supportedEnvelopes.kinds` is the catalog. A host advertising it MUST refuse an
118
- emitted `type` that is neither universal nor a member, with `unknown_envelope_kind`.
119
- **An absent `kinds` is not an empty catalog and is not an unrestricted one**: a host
120
- advertising `supportedEnvelopes` without it has made no catalog claim, and an engine
121
- MUST refuse every non-universal kind rather than admit it unchecked.
164
+ ### `supportedEnvelopes`
122
165
 
123
- `schemaVersions.kinds` maps a kind to its advertised floor; a kind absent from the map
124
- has a floor of `0`. An emitted `schemaVersion` ABOVE the floor MUST be refused with
125
- `unknown_schema_version` whatever the strictness.
126
- `ui.a2ui-surface` at schema version 2 is specified by `ext/a2uiSurface/README.md`.
166
+ `supportedEnvelopes.kinds` is the catalog. A host advertising it MUST refuse an emitted `type` that is neither universal nor a member, with `unknown_envelope_kind`.
127
167
 
128
- `envelopeStrictness.mode` governs drift BELOW the floor only. Under `warn` — the value
129
- when the seat is absent — an engine MUST validate against the advertised version and
130
- log `envelope_schema_version_drift`. Under `strict` the same condition MUST refuse with
131
- `unknown_schema_version`. A host MUST NOT read an absent `mode` as "no checking".
168
+ An absent `kinds` is not an empty catalog and is not an unrestricted one: a host advertising `supportedEnvelopes` without it has made no catalog claim, and an engine MUST refuse every non-universal kind rather than admit it unchecked.
132
169
 
133
- ## Envelope contracts
170
+ ### `schemaVersions`
134
171
 
135
- `envelopeContracts` advertises that a host enforces per-node envelope permission
136
- sets. A host advertising `envelopeContracts.advertised` MUST refuse a node whose
137
- emitted envelope `type` is neither universal nor listed in that node's accepted
138
- set, and MUST refuse it distinctly from the capability-gated `typeId` refusal —
139
- the two stack rather than substitute.
172
+ `schemaVersions.kinds` maps a kind to its advertised floor; a kind absent from the map has a floor of `0`. An emitted `schemaVersion` above the floor MUST be refused with `unknown_schema_version`, whatever the strictness. `ui.a2ui-surface` at schema version 2 is specified by `ext/a2uiSurface/README.md`.
140
173
 
141
- ## Envelope, feedback and usage facets
174
+ ### `envelopeStrictness`
175
+
176
+ `envelopeStrictness.mode` governs drift below the floor only. A host MUST NOT read an absent `mode` as "no checking".
177
+
178
+ - Under `warn` (the value when the seat is absent), an engine MUST validate against the advertised version and log `envelope_schema_version_drift`.
179
+ - Under `strict`, the same condition MUST refuse with `unknown_schema_version`.
142
180
 
143
- `envelopes.tierOneSubsetCompliance` advertises that the host accepts the Tier 1
144
- structured-output subset shared across major providers; a host advertising it MUST
145
- accept an envelope restricted to that subset from any provider it advertises, rather
146
- than refusing on provider-specific grounds.
181
+ ## Envelope contracts
182
+
183
+ `envelopeContracts` advertises that a host enforces per-node envelope permission sets. A host advertising `envelopeContracts.advertised` MUST refuse a node whose emitted envelope `type` is neither universal nor listed in that node's accepted set. It MUST refuse it distinctly from the capability-gated `typeId` refusal: the two stack rather than substitute.
147
184
 
148
- `feedback.targets` names the resources an annotation may be attached to. A host MUST
149
- refuse an annotation whose target is outside the advertised set, and MUST NOT write it
150
- to the replayable run event log.
185
+ ## Envelope, feedback and usage facets
151
186
 
152
- `providerUsage.costEstimates` advertises that the host stamps a derived cost on the
153
- `provider.usage` event. That figure is an estimate from the host's own rate table, and
154
- a consumer MUST NOT treat it as a billed amount.
187
+ - **`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.
188
+ - **`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.
189
+ - **`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.
155
190
 
191
+ *Sources: RFC 0171, RFC 0172, RFC 0176, RFC 0185, RFC 0194, RFC 0213.*