@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,15 +1,15 @@
1
1
  # Form Content Packs
2
2
 
3
- > **Status: Stable · RFC 0177, RFC 0137.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `forms`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- A form-content pack ships declarative form templates a host renders in its own chrome. The manifest is `schemas/v2/form-content-pack-manifest.schema.json`; installation and signing follow packs.md.
8
+ A form-content pack ships declarative form templates a host renders in its own chrome. The manifest is `schemas/v2/form-content-pack-manifest.schema.json`; installation and signing follow [packs.md](packs.md).
9
9
 
10
10
  ## Conditional visibility
11
11
 
12
- A field MAY carry `when: <EdgeCondition>`. The grammar is the `WorkflowEdge.condition` object `{ type, left, right }` of `schemas/v2/workflow-definition.schema.json`, with the operator set of workflow-chain-packs.md §"Edge conditions". A host MUST evaluate `when` with its edge-condition semantics and MUST NOT accept any other expression language for visibility.
12
+ A field MAY carry `when: <EdgeCondition>`. The grammar is the `WorkflowEdge.condition` object `{ type, left, right }` of `schemas/v2/workflow-definition.schema.json`, with the operator set of [workflow-chain-packs.md §"Edge conditions"](workflow-chain-packs.md). A host MUST evaluate `when` with its edge-condition semantics and MUST NOT accept any other expression language for visibility.
13
13
 
14
14
  ```jsonc
15
15
  { "id": "region", "type": "select", "label": "Region",
@@ -18,19 +18,15 @@ A field MAY carry `when: <EdgeCondition>`. The grammar is the `WorkflowEdge.cond
18
18
 
19
19
  ## Instantiation
20
20
 
21
- A form instantiated from a pack advertised through `forms.contentPacks` MUST be
22
- created through the same path a hand-authored form uses, and MUST remain
23
- editable afterwards: the pack is a starting point, not a managed object. A host
24
- MUST degrade an unrecognized field type to plain text rather than failing the
25
- instantiation, and MUST NOT execute anything carried by the pack. A pack-authored
26
- string, and any value collected through an instantiated template, is untrusted
27
- input: when one is interpolated into a prompt, the composed envelope MUST carry
28
- `meta.contentTrust: "untrusted"`, so a downstream reader can tell authored text
29
- from pack-supplied text.
21
+ A form instantiated from a pack advertised through `forms.contentPacks` MUST be created through the same path a hand-authored form uses, and MUST remain editable afterwards: the pack is a starting point, not a managed object.
22
+
23
+ - A host MUST degrade an unrecognized field type to plain text rather than failing the instantiation.
24
+ - A host MUST NOT execute anything carried by the pack.
25
+ - A pack-authored string, and any value collected through an instantiated template, is untrusted input. When one is interpolated into a prompt, the composed envelope MUST carry `meta.contentTrust: "untrusted"`, so a downstream reader can tell authored text from pack-supplied text.
30
26
 
31
27
  ## Localized strings
32
28
 
33
- `label`, `title`, and `description` are localized strings. A host MUST select the rendered language by the locale-selection and fallback rules of i18n.md, and MUST treat every rendered string as untrusted: escaped for the target surface, never interpreted as markup, script, or a template directive.
29
+ `label`, `title`, and `description` are localized strings. A host MUST select the rendered language by the locale-selection and fallback rules of [i18n.md](i18n.md), and MUST treat every rendered string as untrusted: escaped for the target surface, never interpreted as markup, script, or a template directive.
34
30
 
35
31
  ## Validation
36
32
 
@@ -43,3 +39,5 @@ from pack-supplied text.
43
39
  | `pattern` | `text`, `longtext` | a host MUST reject a non-matching value |
44
40
 
45
41
  The five spec-reserved `format` values are the core set. A host that recognizes a format SHOULD apply it; one that does not MUST ignore it and accept plain text. A host MUST ignore `format` on any type other than `text` or `longtext`.
42
+
43
+ *Sources: RFC 0137, RFC 0177.*
@@ -1,41 +1,50 @@
1
1
  # Headers
2
2
 
3
- > **Status: Stable · RFC 0171 §C.1, RFC 0172 §A.3–§A.4.** GENERATED by `scripts/derive-v2-api.py` from `api/v2/openapi.yaml`; do not edit.
3
+ > **Status: Stable.** Generated from `api/v2/openapi.yaml` by `scripts/derive-v2-api.py`; do not edit.
4
4
 
5
5
  ## Why this exists
6
6
 
7
- RFC 0171 §C.1: every non-standard header is `OpenWOP-<Name>` and every header is declared in OpenAPI, so this table — generated from the same declaration — enumerates all of them. A header that is not in this table is not part of the protocol. Standard headers keep their standard names (`Idempotency-Key`, `ETag`, `If-None-Match`, `Last-Event-ID`, `Retry-After`, `Authorization`).
7
+ Every non-standard header is named `OpenWOP-<Name>`, and every header is declared in OpenAPI. These tables are generated from that declaration, so they list all of them: a header not listed here is not part of the protocol.
8
+
9
+ Standard headers keep their standard names: `Idempotency-Key`, `ETag`, `If-None-Match`, `Last-Event-ID`, `Retry-After`, `Authorization`.
8
10
 
9
11
  ## Request headers
10
12
 
11
13
  | Header | Operations | Meaning |
12
14
  | --- | --- | --- |
13
- | `Accept-Language` | 1 | BCP-47 preference list; authoritative for locale selection (i18n.md). A malformed value MUST NOT 400. |
14
- | `Idempotency-Key` | 15 | Per-mutation idempotency token (see `idempotency.md` Layer 1). |
15
- | `If-None-Match` | 2 | RFC 0165 §C.2; runs.md §Snapshot. Standard conditional request against any resource that carries an `ETag` — the discovery document (capabilities.md §1) and the run snapshot (runs.md §Snapshot): a matching value MUST yield `304 Not Modified` with no body, and the 304 carries `OpenWOP-Version` like every response (versioning.md §1.4). |
15
+ | `Accept-Language` | 1 | BCP-47 preference list; authoritative for locale selection (i18n.md). A malformed value MUST NOT produce a 400. |
16
+ | `Idempotency-Key` | 15 | Per-mutation idempotency token (`idempotency.md` Layer 1). The server caches `(tenantId, endpoint, key)` → response for ≥24h. A duplicate request returns the cached response with `OpenWOP-Idempotent-Replay: true`. |
17
+ | `If-None-Match` | 2 | Conditional request on the discovery document (capabilities.md §1) and the run snapshot (runs.md §Snapshot). A value matching the `ETag` the host sent MUST yield `304 Not Modified` with no body. The 304 carries `OpenWOP-Version` like every response (versioning.md §1.4). |
16
18
  | `Last-Event-ID` | 1 | Resume from sequence after this ID. |
17
- | `OpenWOP-Dedup` | 1 | When set, server cross-host claim system rejects duplicate `(tenantId, scopeId)` pairs with `409 Conflict`. |
18
- | `OpenWOP-Force-Engine-Version` | 1 | **Test-keys-only.** When set, the server emits events for this run AS IF it |
19
- | `OpenWOP-Version` | 55 | RFC 0172 §A.3 — selects a listed major.minor; absent ⇒ the host's `preferredVersion`; unlisted ⇒ 406 protocol_version_unsupported. |
19
+ | `OpenWOP-Client-Version` | 55 | The protocol version the client implements (versioning.md §1.5). Compared with `minClientVersion` on major.minor. A malformed value is treated as absent and MUST NOT produce a 400. Never selects a contract. |
20
+ | `OpenWOP-Dedup` | 1 | When set, the host's cross-host claim system rejects a duplicate `(tenantId, scopeId)` pair with `409 Conflict`. |
21
+ | `OpenWOP-Force-Engine-Version` | 1 | Test keys only. The server emits this run's events as if it ran the given engine version, which must be within `Capabilities.testing.forceEngineVersionRange`. Servers MUST reject it on production API keys with `403 force_engine_version_forbidden`. |
22
+ | `OpenWOP-Version` | 55 | Selects one of the host's listed major.minor versions. Absent, `/.well-known/openwop` uses `preferredVersion` and every other unversioned path is v2; an unlisted value is 406 protocol_version_unsupported. |
20
23
 
21
24
  ## Response headers
22
25
 
23
26
  | Header | Operations | Meaning |
24
27
  | --- | --- | --- |
25
- | `Cache-Control` | 2 | |
26
- | `Content-Encoding` | 1 | RFC 0115. Present only when the host negotiated compression |
28
+ | `Cache-Control` | 2 | Standard HTTP caching directive (RFC 9111), set per operation: public content-page delivery, and prompt templates (immutable semantics when the version was pinned); see each operation. |
29
+ | `Content-Encoding` | 1 | Present only when the host negotiated compression from `Accept-Encoding`; pairs with `Vary: Accept-Encoding`. The decoded body is byte-identical to the identity body. |
27
30
  | `Content-Language` | 1 | The BCP-47 locale actually used (equals the response `locale`). |
28
31
  | `ETag` | 3 | Standard HTTP validator (RFC 9110 §8.8.3). The obligation is per operation: MUST on the discovery document (capabilities.md §1), SHOULD on the run snapshot (runs.md §Snapshot), a content hash on a prompt template (getPromptTemplate); see each operation. |
29
32
  | `Location` | 1 | Canonical URI of the new template. |
30
33
  | `OpenWOP-Idempotent-Replay` | 1 | Set when the response was served from the idempotency cache. |
31
- | `OpenWOP-Version` | 55 | RFC 0172 §A.4 — the contract that produced this response; MUST equal the one used. |
34
+ | `OpenWOP-Version` | 55 | The contract that produced this response. It MUST equal the one used. |
32
35
  | `Retry-After` | 1 | Seconds until the active claim is stale-eligible. |
33
- | `WWW-Authenticate` | 48 | RFC 0200 §B — `Bearer` challenge; `resource_metadata` and `error="invalid_token"` on a host with an oauth2/oidc lane. Never on a non-disclosure 404. |
36
+ | `WWW-Authenticate` | 48 | A `Bearer` challenge. On a host with an oauth2 or oidc lane it carries `resource_metadata` and `error="invalid_token"`. Never sent on a non-disclosure 404. |
34
37
 
35
38
  ## Webhook delivery headers
36
39
 
37
- Declared in `webhooks.md`, not in OpenAPI (the host is the client): `OpenWOP-Webhook-Id`, `OpenWOP-Event-Type`, `OpenWOP-Timestamp`, `OpenWOP-Signature`, `OpenWOP-Signature-Algorithm` (RFC 0165 §C.1). The `X-openwop-*` family is emitted beside them through the overlap and removed at v1 end-of-support (`spec/v1/deprecations.json` `webhook-x-header-family`). On a subscription that opted into Standard Webhooks (webhooks.md), that standard's `webhook-id`, `webhook-timestamp` and `webhook-signature`, which keep their standard names (RFC 0201 §F).
40
+ Declared in `webhooks.md`, not in OpenAPI, because the host is the client:
41
+
42
+ - `OpenWOP-Webhook-Id`, `OpenWOP-Event-Type`, `OpenWOP-Timestamp`, `OpenWOP-Signature`, `OpenWOP-Signature-Algorithm`.
43
+ - On a subscription that opted into Standard Webhooks (webhooks.md): that standard's `webhook-id`, `webhook-timestamp` and `webhook-signature`, under their standard names.
44
+ - During the v1 overlap only: the `X-openwop-*` family, emitted beside them and removed at v1 end-of-support.
38
45
 
39
46
  ## Removed in v2
40
47
 
41
- `Capabilities-Etag` (the standard `ETag`/`If-None-Match` pair applies to the discovery document), `X-Dedup`, `X-Force-Engine-Version`, `X-Pack-Sha256`, `X-Pack-Signing-Method` (renamed under the one scheme), `X-openwop-*` (webhooks), `openwop-Webhook-Signature` (SDK-only). Each has a `spec/v1/deprecations.json` row with a removal trigger.
48
+ These header names are not part of v2: `Capabilities-Etag` (use the standard `ETag`/`If-None-Match` pair), `X-Dedup`, `X-Force-Engine-Version`, `X-Pack-Sha256`, `X-Pack-Signing-Method`, `X-openwop-*` (webhooks), `openwop-Webhook-Signature`.
49
+
50
+ *Sources: RFC 0165, RFC 0171, RFC 0172, RFC 0201.*
@@ -1,19 +1,27 @@
1
1
  # Host services
2
2
 
3
- > **Status: Stable · v2.0 · RFC 0144.** Normative contract for the advertised `host.*` service surfaces a node pack invokes through `ctx`.
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `aiEnvelope`, `promptLibrary`, `agentRuntime`, `mcp`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- `spec/v1/host-capabilities.md` describes these three surfaces but states no RFC 2119 obligation; the contract a host takes on by advertising them is written here.
8
+ A node pack invokes advertised `host.*` services through `ctx`. This document states the contract a host takes on by advertising them.
9
9
 
10
10
  ## `aiEnvelope`
11
11
 
12
- A host advertising `aiEnvelope` MUST expose `ctx.aiEnvelope.generate` to pack code, and MUST refuse a node whose `typeId` requires the family when it does not advertise it. It MUST expose `ctx.aiEnvelope.await` when, and only when, it advertises `aiEnvelope.await`.
12
+ A host advertising `aiEnvelope`:
13
+
14
+ - MUST expose `ctx.aiEnvelope.generate` to pack code;
15
+ - MUST refuse a node whose `typeId` requires the family when it does not advertise it;
16
+ - MUST expose `ctx.aiEnvelope.await` when, and only when, it advertises `aiEnvelope.await`.
13
17
 
14
18
  ## `promptLibrary`
15
19
 
16
- A host advertising `promptLibrary` MUST expose `ctx.promptLibrary.get`, MUST return a pinned version verbatim so that a replayed run resolves the same template, and MUST fail the calling node rather than substitute an unpinned one.
20
+ A host advertising `promptLibrary`:
21
+
22
+ - MUST expose `ctx.promptLibrary.get`;
23
+ - MUST return a pinned version verbatim, so that a replayed run resolves the same template;
24
+ - MUST fail the calling node rather than substitute an unpinned one.
17
25
 
18
26
  ## `agentRuntime`
19
27
 
@@ -21,4 +29,13 @@ A host advertising `agentRuntime` MUST expose `spawn`, `delegate`, `consensus` a
21
29
 
22
30
  ## `mcp`
23
31
 
24
- A host advertising `mcp.client` MUST expose to pack code `ctx.mcp.callTool`, `listTools`, `readResource` and `serverHealth`, each against a host-configured `serverId` at the revision `mcp` negotiates. Each rejects only for an unknown `serverId` (`not_found`), an MCP error response (carried unaltered), or a transport failure. `callTool` MUST resolve to the server's `CallToolResult` unaltered (`content[]`, `structuredContent`, `isError`, `_meta`), including when `isError` is true; the host handles an `InputRequiredResult` itself and never returns one. `listTools` MUST resolve to one `ListToolsResult` page unaltered, `outputSchema`, `annotations`, `nextCursor`, `ttlMs` and `cacheScope` included, and MUST forward a pack's `cursor`; `readResource` resolves to the `ReadResourceResult` unaltered. `serverHealth` MUST report `reachable`, `unreachable` or `incompatible` from a `server/discover` probe no older than its `ttlMs`, with the `DiscoverResult` when one was received; it MUST NOT report a connection or session state, which MCP 2026-07-28 does not have.
32
+ A host advertising `mcp.client` MUST expose to pack code `ctx.mcp.callTool`, `listTools`, `readResource` and `serverHealth`, each against a host-configured `serverId` at the revision `mcp` negotiates.
33
+
34
+ Each rejects only for an unknown `serverId` (`not_found`), an MCP error response (carried unaltered), or a transport failure.
35
+
36
+ - `callTool` MUST resolve to the server's `CallToolResult` unaltered (`content[]`, `structuredContent`, `isError`, `_meta`), including when `isError` is true. The host handles an `InputRequiredResult` itself and never returns one.
37
+ - `listTools` MUST resolve to one `ListToolsResult` page unaltered, `outputSchema`, `annotations`, `nextCursor`, `ttlMs` and `cacheScope` included, and MUST forward a pack's `cursor`.
38
+ - `readResource` resolves to the `ReadResourceResult` unaltered.
39
+ - `serverHealth` MUST report `reachable`, `unreachable` or `incompatible` from a `server/discover` probe no older than its `ttlMs`, with the `DiscoverResult` when one was received. It MUST NOT report a connection or session state, which MCP 2026-07-28 does not have.
40
+
41
+ *Sources: RFC 0144.*
@@ -0,0 +1,64 @@
1
+ # Internationalization
2
+
3
+ > **Status: Stable.**
4
+ > **Normative home:** `i18n`.
5
+
6
+ ## Why this exists
7
+
8
+ A host renders some text for humans: interrupt prompts, error messages, extension UI strings. This document states how a client asks for a locale and how a host answers. Machine-readable identifiers stay ASCII, dates and times stay ISO 8601, and number, currency and text-direction formatting are out of scope.
9
+
10
+ ## Language tags
11
+
12
+ Every locale identifier — `Accept-Language`, `Content-Language` and every `locale` field — is a [BCP 47](https://www.rfc-editor.org/rfc/rfc5646) tag.
13
+
14
+ - Tags compare case-insensitively (RFC 5646 §2.1.1, RFC 4647 §2).
15
+ - A host SHOULD advertise and emit tags in the case RFC 5646 §2.1.1 recommends: lowercase language, titlecase script, uppercase region (`zh-Hant-TW`, `es-419`).
16
+
17
+ ## `Accept-Language`
18
+
19
+ A client MAY send `Accept-Language` ([RFC 9110 §12.5.4](https://www.rfc-editor.org/rfc/rfc9110#section-12.5.4)) on any request. A host MAY honor it to localize human-facing text in the response.
20
+
21
+ A host:
22
+
23
+ - MUST parse the header without failing the request. A malformed value MUST NOT cause `400`; the host uses its default locale and proceeds.
24
+ - MUST NOT reject a request because it does not support the requested locale.
25
+ - MUST honor q-values: the highest-q locale it supports wins, and ties go to request order.
26
+ - MUST NOT infer a locale from the request body to override the header, which is authoritative.
27
+ - SHOULD set `Content-Language` to the canonical tag of the locale used when it localized the response. When it localized nothing, it SHOULD omit `Content-Language` or set it to its default locale.
28
+ - MAY cache localized text. When localization is expensive, caching by `(localeTag, sourceText)` is RECOMMENDED.
29
+ - MAY return default-locale text at once and emit `node.completed` when the localized version is ready. This is discouraged for an interrupt prompt, which a human needs localized now.
30
+
31
+ ## Fallback
32
+
33
+ When a host cannot localize to the requested locale, it:
34
+
35
+ 1. walks the list in q-value order and uses the first locale it supports;
36
+ 2. otherwise tries the language family: asked for `ja-JP` and holding only `ja`, it uses `ja` and sets `Content-Language: ja`;
37
+ 3. otherwise uses its default locale, which it SHOULD advertise as `i18n.defaultLocale`;
38
+ 4. sets `Content-Language` to the locale it actually used, never another.
39
+
40
+ ## Error envelopes
41
+
42
+ A host MAY localize an error `message`, and MAY then add `details.locale`.
43
+
44
+ - The `error` code MUST be the same registered identifier in every locale ([errors.md](errors.md)); it is never translated.
45
+ - `details` keys are schema field names and are not localized.
46
+ - A human-facing `details` value MAY be localized, and SHOULD carry a `locale` sibling on the same object.
47
+
48
+ ## The `i18n` record
49
+
50
+ A host that negotiates locale SHOULD advertise `i18n`. A host advertising it honors `Accept-Language` on every protected route; a host that omits it serves one locale.
51
+
52
+ - **`defaultLocale`** — the locale returned when no `Accept-Language` entry matches `supportedLocales`; `"en"` when omitted.
53
+ - **`supportedLocales`** — the locales the host can return for human-facing text. It MUST contain `defaultLocale`, and its order carries no meaning.
54
+ - A host that translates by machine SHOULD list only the locales it has validated end to end.
55
+
56
+ ## Replay and fork
57
+
58
+ Locale is chosen at request time. `Content-Language` is request-scoped and not recorded in the event log, so a replay re-projects the recorded text and does not re-render it. A fork localizes by its own request's `Accept-Language`, not the parent run's.
59
+
60
+ ## Clients
61
+
62
+ A client that wants localized content MUST check discovery for `i18n` before sending `Accept-Language`. A host that does not advertise it ignores the header and returns its default locale.
63
+
64
+ *Sources: RFC 0206.*
@@ -1,59 +1,71 @@
1
1
  # Idempotency
2
2
 
3
- > **Status: Stable · RFC 0170 §D.3, RFC 0171 §B.2, RFC 0173 §B.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `idempotency`.
5
5
 
6
6
  ## Why this exists
7
7
 
8
- A retried request MUST NOT create a second run, and a retried node MUST NOT issue a second external effect. A host MUST implement Layer 1 for every mutating endpoint; a host that advertises `idempotency` MUST implement Layer 2 for every node executor that performs an external side effect (security-defaults.md).
8
+ A retried request MUST NOT create a second run, and a retried node MUST NOT issue a second external effect. A host MUST implement Layer 1 for every mutating endpoint; a host that advertises `idempotency` MUST implement Layer 2 for every node executor that performs an external side effect ([security-defaults.md](security-defaults.md)).
9
9
 
10
10
  ## Layer 1: `Idempotency-Key`
11
11
 
12
- The header keeps its standard name (RFC 0171 §C.1) and applies to every mutating operation in `api/v2/openapi.yaml`; `GET` operations MUST NOT honor it.
13
-
14
- | Rule | Requirement |
15
- | --- | --- |
16
- | Grammar | The value MUST match `^[A-Za-z0-9._~-]{22,128}$` and MUST carry at least 128 bits of entropy (a UUIDv4 in canonical or base64url form satisfies it). A host MUST reject a value outside the grammar with `400 idempotency_key_invalid`. |
17
- | Record key | A record MUST be keyed by `(authenticatedTenantId, canonicalEndpointId, callerIdempotencyKey)`; the tenant MUST come from the credential, never the body. |
18
- | Final outcomes | A host MUST cache `2xx` and non-retryable `4xx` responses (status, headers, body) and MUST return the cached response to a same-key duplicate. |
19
- | Retryable outcomes | `429` and `5xx` MUST NOT be replayed from cache; a same-key retry MUST re-execute, and a later final outcome replaces the record. |
20
- | Not cached | `400 idempotency_key_invalid`, `400 validation_error`, `401` and `403` MUST NOT be cached. |
21
- | Digest mismatch | A different request digest under the same record key MUST fail with `409 idempotency_key_mismatch` and MUST NOT return the cached body. This is the only mismatch code. |
22
- | Concurrency | Of two concurrent same-key requests a host MUST process exactly one to completion and MUST NOT process both. The other MAY wait, bounded by the host's request timeout, and receive the winner's response only if it is a final outcome (one this record caches), marked `OpenWOP-Idempotent-Replay: true`. Otherwise the host MUST answer `409 idempotency_in_flight` with no retry timing in `details`, and SHOULD set `Retry-After`. The code's registry `retriable: false` means not retryable without waiting (RFC 0213 §B). |
23
- | Replay marker | A response served from cache MUST carry `OpenWOP-Idempotent-Replay: true`. |
24
- | Retention | A record MUST be retained for at least 24 hours. |
25
- | Keyspace | Host-minted identifiers MUST NOT share the caller idempotency store. Logs and spans MUST NOT expose keys. |
12
+ The header keeps its standard name and applies to every mutating operation in `api/v2/openapi.yaml`; `GET` operations MUST NOT honor it.
13
+
14
+ ### Key and record
15
+
16
+ - **Grammar.** The value MUST match `^[A-Za-z0-9._~-]{22,128}$` and MUST carry at least 128 bits of entropy (a UUIDv4 in canonical or base64url form satisfies it). A host MUST reject a value outside the grammar with `400 idempotency_key_invalid`.
17
+ - **Record key.** A record MUST be keyed by `(authenticatedTenantId, canonicalEndpointId, callerIdempotencyKey)`; the tenant MUST come from the credential, never the body.
18
+ - **Retention.** A record MUST be retained for at least 24 hours.
19
+ - **Keyspace.** Host-minted identifiers MUST NOT share the caller idempotency store. Logs and spans MUST NOT expose keys.
20
+
21
+ ### Outcomes
22
+
23
+ - **Final.** A host MUST cache `2xx` and non-retryable `4xx` responses (status, headers, body) and MUST return the cached response to a same-key duplicate.
24
+ - **Retryable.** `429` and `5xx` MUST NOT be replayed from cache; a same-key retry MUST re-execute, and a later final outcome replaces the record.
25
+ - **Not cached.** `400 idempotency_key_invalid`, `400 validation_error`, `401` and `403` MUST NOT be cached.
26
+ - **Digest mismatch.** A different request digest under the same record key MUST fail with `409 idempotency_key_mismatch` and MUST NOT return the cached body. This is the only mismatch code.
27
+ - **Replay marker.** A response served from cache MUST carry `OpenWOP-Idempotent-Replay: true`.
28
+
29
+ ### Concurrency
30
+
31
+ Of two concurrent same-key requests a host MUST process exactly one to completion and MUST NOT process both.
32
+
33
+ - The other MAY wait, bounded by the host's request timeout, and receive the winner's response only if it is a final outcome (one this record caches), marked `OpenWOP-Idempotent-Replay: true`.
34
+ - Otherwise the host MUST answer `409 idempotency_in_flight` with no retry timing in `details`, and SHOULD set `Retry-After`.
35
+
36
+ The code's registry `retriable: false` means not retryable without waiting.
26
37
 
27
38
  ## Layer 2: effect identity
28
39
 
29
- Layer 2 is bound by advertising `idempotency` (security-defaults.md). Its unit is the **effect**, identified once and stable across every transport or provider retry.
40
+ Layer 2 is bound by advertising `idempotency` ([security-defaults.md](security-defaults.md)). Its unit is the **effect**, identified once and stable across every transport or provider retry.
30
41
 
31
- | Rule | Requirement |
32
- | --- | --- |
33
- | Keying | An effect MUST be keyed on its business identity (`keying: business-identity`): derived from the business operation, stable across every entry point, containing no `runId`, `nodeId` or ordinal. The activity recipe (`keying: activity-recipe`: tenant, run, node, ordinal, `providerKey`) is the fallback for a provider with no business key. |
34
- | Attempts | The retry counter MUST NOT participate in the identity. Two distinct logical invocations MUST receive different identities. |
35
- | Claim | The persist that guards the effect MUST be an atomic claim (compare-and-set or insert-if-absent) that at most one executor can win, so at most one concurrent duplicate performs the effect. |
36
- | Provider key | When the provider accepts an idempotency key, the host MUST inject the effect identity (or a documented deterministic derivative), stable across retries. A host that cannot use the provider's convention MUST still persist the outcome. |
37
- | Streaming | A streamed body MUST NOT be cached in the ledger; the host SHOULD record the request and its final outcome. |
38
- | Retention | An effect record MUST be retained for at least 14 days. |
42
+ - **Keying.** An effect MUST be keyed on its business identity (`keying: business-identity`): derived from the business operation, stable across every entry point, containing no `runId`, `nodeId` or ordinal. The activity recipe (`keying: activity-recipe`: tenant, run, node, ordinal, `providerKey`) is the fallback for a provider with no business key.
43
+ - **Attempts.** The retry counter MUST NOT participate in the identity. Two distinct logical invocations MUST receive different identities.
44
+ - **Claim.** The persist that guards the effect MUST be an atomic claim (compare-and-set or insert-if-absent) that at most one executor can win, so at most one concurrent duplicate performs the effect.
45
+ - **Provider key.** When the provider accepts an idempotency key, the host MUST inject the effect identity (or a documented deterministic derivative), stable across retries. A host that cannot use the provider's convention MUST still persist the outcome.
46
+ - **Streaming.** A streamed body MUST NOT be cached in the ledger; the host SHOULD record the request and its final outcome.
47
+ - **Retention.** An effect record MUST be retained for at least 14 days.
39
48
 
40
49
  ### Witness: `GET /runs/{runId}/effects`
41
50
 
42
- A host that advertises `idempotency` MUST serve `schemas/v2/effect-ledger-projection.schema.json` at `GET /runs/{runId}/effects` (`getRunEffects`): `{ runId, effects[] }`, each record carrying `effectId` (tenant-bound, `schemas/v2/ids.schema.json`), `nodeId`, `attempt`, `keying`, `state` (`claimed` | `completed` | `released` | `escaped`), `at`, optional `invocationId` and a redaction-safe `providerKey`. The projection MUST be content-free of provider payloads and credential material.
51
+ A host that advertises `idempotency` MUST serve `schemas/v2/effect-ledger-projection.schema.json` at `GET /runs/{runId}/effects` (`getRunEffects`): `{ runId, effects[] }`. Each record carries:
52
+
53
+ - `effectId` (tenant-bound, `schemas/v2/ids.schema.json`), `nodeId`, `attempt`, `keying`, `at`;
54
+ - `state`: `claimed` | `completed` | `released` | `escaped`;
55
+ - optional `invocationId` and a redaction-safe `providerKey`.
56
+
57
+ The projection MUST be content-free of provider payloads and credential material.
43
58
 
44
59
  ## Composition
45
60
 
46
- Layer 1 deduplicates the caller's request; Layer 2 deduplicates the run's effects. A retried provider call inside a run MUST resolve to the same effect record. Effects under replay and fork are in replay.md; identifier grammars are in identity.md.
61
+ Layer 1 deduplicates the caller's request; Layer 2 deduplicates the run's effects. A retried provider call inside a run MUST resolve to the same effect record. Effects under replay and fork are in [replay.md](replay.md); identifier grammars are in [identity.md](identity.md).
47
62
 
48
63
  ## Multi-region
49
64
 
50
- `multiRegion` and `crossRegion` are the two region facets of `idempotency`, and
51
- they claim different things. `crossRegion` names the host's deployment posture
52
- for this axis and MUST be held constant for the life of an advertisement.
53
- `multiRegion` is the behavioural claim: when a host advertises it, both layers
54
- above MUST hold across regions — an `Idempotency-Key` replayed into a second
55
- region MUST resolve to the first region's response rather than starting new
56
- work, and effect identity MUST collapse a duplicate effect wherever it is
57
- observed. A host that does not advertise `multiRegion` makes no cross-region
58
- promise, and a client MUST NOT infer one from `crossRegion` alone.
65
+ `multiRegion` and `crossRegion` are the two region facets of `idempotency`, and they claim different things.
66
+
67
+ - `crossRegion` names the host's deployment posture for this axis and MUST be held constant for the life of an advertisement.
68
+ - `multiRegion` is the behavioural claim. When a host advertises it, both layers MUST hold across regions: an `Idempotency-Key` replayed into a second region MUST resolve to the first region's response rather than starting new work, and effect identity MUST collapse a duplicate effect wherever it is observed.
69
+ - A host that does not advertise `multiRegion` makes no cross-region promise, and a client MUST NOT infer one from `crossRegion` alone.
59
70
 
71
+ *Sources: RFC 0170, RFC 0171, RFC 0173, RFC 0213.*