@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.
- package/CORPUS-STAMP.json +70 -67
- package/api/seams-v2.yaml +238 -322
- package/api/v2/asyncapi.yaml +83 -113
- package/api/v2/openapi.yaml +408 -724
- package/package.json +1 -1
- package/schemas/audit-verify-result.schema.json +2 -2
- package/schemas/v2/audit-verify-result.schema.json +2 -2
- package/schemas/v2/capabilities.schema.json +4 -7
- package/schemas/v2/error-envelope.schema.json +144 -69
- package/spec/v1/alias-detectors.json +6 -0
- package/spec/v1/core-standard-manifest.json +2 -2
- package/spec/v1/deprecations.json +22 -0
- package/spec/v1/gaps.json +188 -1
- package/spec/v1/migrations.json +18 -0
- package/spec/v2/README.md +35 -37
- package/spec/v2/core/capabilities.md +186 -119
- package/spec/v2/core/conformance.md +78 -27
- package/spec/v2/core/connection-packs.md +8 -3
- package/spec/v2/core/conversation.md +10 -4
- package/spec/v2/core/errors.md +30 -9
- package/spec/v2/core/events.md +116 -80
- package/spec/v2/core/form-content-packs.md +11 -13
- package/spec/v2/core/headers.md +23 -14
- package/spec/v2/core/host-services.md +22 -5
- package/spec/v2/core/i18n.md +64 -0
- package/spec/v2/core/idempotency.md +48 -36
- package/spec/v2/core/identity.md +110 -42
- package/spec/v2/core/interop.md +82 -21
- package/spec/v2/core/interrupt.md +79 -40
- package/spec/v2/core/node-pack-runtimes.md +18 -4
- package/spec/v2/core/oauth.md +40 -6
- package/spec/v2/core/overview.md +61 -33
- package/spec/v2/core/packs.md +53 -28
- package/spec/v2/core/persistence.md +61 -80
- package/spec/v2/core/portability.md +44 -0
- package/spec/v2/core/replay.md +56 -42
- package/spec/v2/core/runs.md +198 -60
- package/spec/v2/core/security-defaults.md +57 -33
- package/spec/v2/core/tool-catalog.md +29 -6
- package/spec/v2/core/versioning.md +115 -50
- package/spec/v2/core/webhooks.md +61 -20
- package/spec/v2/core/workflow-chain-packs.md +16 -14
- package/spec/v2/corrections.json +17 -1
- package/spec/v2/corrections.schema.json +1 -1
- package/spec/v2/declaration.json +26 -9
- package/spec/v2/declaration.schema.json +10 -0
- package/spec/v2/errors.json +121 -4
- package/spec/v2/errors.schema.json +38 -0
- package/spec/v2/ext/README.md +36 -23
- package/spec/v2/ext/a2uiSurface/README.md +75 -89
- package/spec/v2/ext/brand/README.md +15 -15
- package/spec/v2/ext/canvas/README.md +15 -15
- package/spec/v2/ext/chat/README.md +15 -15
- package/spec/v2/ext/coordination/README.md +15 -15
- package/spec/v2/ext/dataIntegration/README.md +17 -20
- package/spec/v2/ext/entities/README.md +15 -15
- package/spec/v2/ext/grpc-transport/README.md +6 -8
- package/spec/v2/ext/kanban/README.md +15 -15
- package/spec/v2/ext/knowledge/README.md +15 -15
- package/spec/v2/ext/launchStudio/README.md +16 -16
- package/spec/v2/ext/messaging/README.md +15 -15
- package/spec/v2/ext/portability/README.md +3 -17
- package/spec/v2/ext/provider-idempotency/README.md +7 -10
- package/spec/v2/ext/restTransport/README.md +30 -19
- package/spec/v2/ext/sandbox-runtime-notes/README.md +7 -9
- package/spec/v2/ext/webResearch/README.md +16 -16
- package/spec/v2/profiles.json +6 -2
- package/spec/v2/release.json +2 -2
- package/spec/v2/surface-baseline.json +544 -2
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Form Content Packs
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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.*
|
package/spec/v2/core/headers.md
CHANGED
|
@@ -1,41 +1,50 @@
|
|
|
1
1
|
# Headers
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
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
|
-
|
|
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 (
|
|
15
|
-
| `If-None-Match` | 2 |
|
|
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-
|
|
18
|
-
| `OpenWOP-
|
|
19
|
-
| `OpenWOP-Version` |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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
|
|
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
|
|
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
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
> **Normative home:** `aiEnvelope`, `promptLibrary`, `agentRuntime`, `mcp`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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[] }
|
|
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
|
-
|
|
52
|
-
for this axis and MUST be held constant for the life of an advertisement.
|
|
53
|
-
`multiRegion` is the behavioural claim
|
|
54
|
-
|
|
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.*
|