@openwop/spec-artifacts 2.35.1 → 2.36.0
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 +103 -89
- package/api/openapi.yaml +325 -4
- package/api/seams-v2.yaml +342 -1
- package/api/v2/asyncapi.yaml +1 -1
- package/api/v2/openapi.yaml +564 -8
- package/package.json +1 -1
- package/schemas/a2a-task-state.schema.json +3 -3
- package/schemas/artifact-type-pack-manifest.schema.json +3 -3
- package/schemas/capabilities.schema.json +34 -1
- package/schemas/connection-pack-manifest.schema.json +14 -0
- package/schemas/debug-bundle.schema.json +20 -0
- package/schemas/dispatch-config.schema.json +1 -1
- package/schemas/localized-content-language-settings.schema.json +3 -3
- package/schemas/localized-content-page.schema.json +2 -2
- package/schemas/localized-content-section.schema.json +3 -3
- package/schemas/run-event-payloads.schema.json +4 -2
- package/schemas/suspend-request.schema.json +61 -4
- package/schemas/v2/a2a-task-state.schema.json +10 -9
- package/schemas/v2/agent-deployment-transition.schema.json +4 -4
- package/schemas/v2/agent-deployment.schema.json +7 -7
- package/schemas/v2/agent-eval-suite.schema.json +10 -10
- package/schemas/v2/agent-inventory-response.schema.json +8 -2
- package/schemas/v2/artifact-type-pack-manifest.schema.json +3 -3
- package/schemas/v2/artifact.schema.json +84 -0
- package/schemas/v2/capabilities.schema.json +82 -8
- package/schemas/v2/certification-bundle.schema.json +1 -1
- package/schemas/v2/channel-presence-payload.schema.json +3 -3
- package/schemas/v2/connection-pack-manifest.schema.json +14 -0
- package/schemas/v2/conversation-event.schema.json +9 -2
- package/schemas/v2/conversation-turn.schema.json +19 -12
- package/schemas/v2/dispatch-config.schema.json +1 -1
- package/schemas/v2/envelopes/ui.a2ui-surface.schema.json +920 -22
- package/schemas/v2/error-envelope.schema.json +11 -1
- package/schemas/v2/eval-summary.schema.json +1 -2
- package/schemas/v2/goal.schema.json +4 -4
- package/schemas/v2/localized-content-language-settings.schema.json +3 -3
- package/schemas/v2/localized-content-page.schema.json +2 -2
- package/schemas/v2/localized-content-section.schema.json +3 -3
- package/schemas/v2/node-pack-manifest.schema.json +105 -0
- package/schemas/v2/part.schema.json +81 -0
- package/schemas/v2/proposal.schema.json +6 -6
- package/schemas/v2/run-event-payloads.schema.json +5 -3
- package/schemas/v2/self-hosted-runner-dispatch-frame.schema.json +2 -2
- package/schemas/v2/self-hosted-runner-result-frame.schema.json +1 -1
- package/schemas/v2/suspend-request.schema.json +251 -6
- package/schemas/v2/tool-descriptor.schema.json +246 -2
- package/schemas/v2/trigger-event.schema.json +1 -2
- package/schemas/v2/trigger-subscription.schema.json +1 -2
- package/schemas/v2/webhook-verification.schema.json +22 -0
- package/schemas/v2/workflow-definition.schema.json +25 -25
- package/spec/v1/alias-detectors.json +13 -1
- package/spec/v1/core-standard-manifest.json +2 -2
- package/spec/v1/deprecations.json +45 -1
- package/spec/v1/deprecations.schema.json +119 -4
- package/spec/v1/gaps.json +1925 -4
- package/spec/v2/README.md +5 -2
- package/spec/v2/core/capabilities.md +13 -26
- package/spec/v2/core/conformance.md +5 -8
- package/spec/v2/core/connection-packs.md +1 -1
- package/spec/v2/core/conversation.md +1 -1
- package/spec/v2/core/errors.md +8 -3
- package/spec/v2/core/events.md +8 -20
- package/spec/v2/core/form-content-packs.md +2 -2
- package/spec/v2/core/headers.md +5 -4
- package/spec/v2/core/host-services.md +6 -2
- package/spec/v2/core/idempotency.md +1 -1
- package/spec/v2/core/identity.md +23 -12
- package/spec/v2/core/interop.md +34 -4
- package/spec/v2/core/interrupt.md +6 -3
- package/spec/v2/core/node-pack-runtimes.md +20 -0
- package/spec/v2/core/oauth.md +32 -0
- package/spec/v2/core/overview.md +19 -6
- package/spec/v2/core/packs.md +4 -6
- package/spec/v2/core/persistence.md +20 -55
- package/spec/v2/core/replay.md +5 -11
- package/spec/v2/core/runs.md +9 -8
- package/spec/v2/core/security-defaults.md +18 -20
- package/spec/v2/core/tool-catalog.md +22 -0
- package/spec/v2/core/versioning.md +10 -28
- package/spec/v2/core/webhooks.md +16 -4
- package/spec/v2/core/workflow-chain-packs.md +1 -1
- package/spec/v2/corrections.json +32 -0
- package/spec/v2/corrections.schema.json +49 -0
- package/spec/v2/declaration.json +18 -13
- package/spec/v2/declaration.schema.json +3 -1
- package/spec/v2/errors.json +46 -1
- package/spec/v2/ext/a2uiSurface/README.md +131 -17
- package/spec/v2/ext/dataIntegration/README.md +5 -0
- package/spec/v2/facets/a2a.schema.json +4 -0
- package/spec/v2/facets/auth.schema.json +24 -0
- package/spec/v2/facets/mcp.schema.json +4 -0
- package/spec/v2/facets/oauth.schema.json +70 -0
- package/spec/v2/facets/webhooks.schema.json +23 -2
- package/spec/v2/id-field-bindings.json +2 -0
- package/spec/v2/interop-map.json +639 -0
- package/spec/v2/interop-map.schema.json +458 -0
- package/spec/v2/migrations.json +7 -0
- package/spec/v2/migrations.schema.json +58 -0
- package/spec/v2/path-manifest.json +6 -1
- package/spec/v2/release.json +3 -3
- package/spec/v2/surface-baseline.json +7981 -0
package/spec/v2/README.md
CHANGED
|
@@ -42,6 +42,9 @@ and [`core/overview.md`](./core/overview.md).
|
|
|
42
42
|
| [`profiles.json`](./profiles.json) | Generated profile predicates. |
|
|
43
43
|
| [`peer-dependency-aliases.json`](./peer-dependency-aliases.json) | Generated v1 alias mapping. |
|
|
44
44
|
| [`release.json`](./release.json) | Authoritative corpus release identity. |
|
|
45
|
+
| [`migrations.json`](./migrations.json) | RFC 0197 — the v2→v2 migration register: what a 2.x minor replaced and which deprecation row schedules the old surface out. Empty is a legal state. |
|
|
46
|
+
| [`corrections.json`](./corrections.json) | RFC 0197 §A.4 — W3C Class-3 corrections that changed a v2 shape, each with the bundle and registry census that showed nothing conforming stopped conforming. |
|
|
47
|
+
| [`surface-baseline.json`](./surface-baseline.json) | RFC 0197 §A.1 — GENERATED census of every v2 surface, enumerated by instance path; `check-v2-surface-monotone.mjs` diffs the tree against it. Regenerated by the release PR. |
|
|
45
48
|
|
|
46
49
|
Generated files identify their generator in `$comment` or their header. Do not
|
|
47
50
|
edit generated outputs directly. Machine artifacts are published in
|
|
@@ -52,7 +55,7 @@ exact-version peer dependency.
|
|
|
52
55
|
|
|
53
56
|
| Area | Documents |
|
|
54
57
|
| --- | --- |
|
|
55
|
-
| Foundation | [`overview`](./core/overview.md), [`versioning`](./core/versioning.md), [`headers`](./core/headers.md), [`identity`](./core/identity.md), [`capabilities`](./core/capabilities.md) |
|
|
58
|
+
| Foundation | [`overview`](./core/overview.md), [`versioning`](./core/versioning.md), [`headers`](./core/headers.md), [`identity`](./core/identity.md), [`oauth`](./core/oauth.md), [`capabilities`](./core/capabilities.md) |
|
|
56
59
|
| Execution | [`runs`](./core/runs.md), [`events`](./core/events.md), [`interrupt`](./core/interrupt.md), [`persistence`](./core/persistence.md), [`idempotency`](./core/idempotency.md), [`replay`](./core/replay.md) |
|
|
57
|
-
| Integration | [`webhooks`](./core/webhooks.md), [`interop`](./core/interop.md), [`packs`](./core/packs.md), [`connection packs`](./core/connection-packs.md), [`form-content packs`](./core/form-content-packs.md), [`workflow-chain packs`](./core/workflow-chain-packs.md) |
|
|
60
|
+
| Integration | [`webhooks`](./core/webhooks.md), [`interop`](./core/interop.md), [`packs`](./core/packs.md), [`node-pack runtimes`](./core/node-pack-runtimes.md), [`connection packs`](./core/connection-packs.md), [`form-content packs`](./core/form-content-packs.md), [`workflow-chain packs`](./core/workflow-chain-packs.md) |
|
|
58
61
|
| Reliability | [`errors`](./core/errors.md), [`security defaults`](./core/security-defaults.md), [`conformance`](./core/conformance.md) |
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## Why this exists
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
v2 discovery has one capability record type on a closed root, generated from one declaration file that also mints pack peer-dependency identifiers and the `§` anchors below. Profiles are derived predicates, never a wire field.
|
|
8
8
|
|
|
9
9
|
## 1. One well-known resource (RFC 0176 §C.1)
|
|
10
10
|
|
|
@@ -47,19 +47,19 @@ The root of `schemas/v2/capabilities.schema.json` is `additionalProperties: fals
|
|
|
47
47
|
|
|
48
48
|
### 3.1 Metadata keys (17)
|
|
49
49
|
|
|
50
|
-
`protocolVersion`, `protocolVersions`, `preferredVersion`, `extensions`, `implementation`, `engineVersion`, `eventLogSchemaVersion`, `configurable`, `observability`, `minClientVersion`, `runtimeCapabilities`, `testing`, `conformance`, `fixtures`, `compliance`, `discovery`, and `supportedTransports`. Each is declared as metadata with its own schema in `spec/v2/declaration.json`; a metadata key is not a record and carries no `status` or `witness`. `supportedTransports` is declared only to record its deletion (§4). Version-axis metadata are specified in `versioning.md`; `configurable` in `runs.md`.
|
|
50
|
+
`protocolVersion`, `protocolVersions`, `preferredVersion`, `extensions`, `implementation`, `engineVersion`, `eventLogSchemaVersion`, `configurable`, `observability`, `minClientVersion`, `runtimeCapabilities`, `testing`, `conformance`, `fixtures`, `compliance`, `discovery`, and `supportedTransports`. Each is declared as metadata with its own schema in `spec/v2/declaration.json`; a metadata key is not a record and carries no `status` or `witness`. `supportedTransports` is declared only to record its deletion (§4). Version-axis metadata are specified in `versioning.md`; `configurable` in `runs.md`. `implementation` (`name`, `version`, `vendor`, `url`) is self-reported: a client SHOULD NOT change behaviour because of it or authorize from it.
|
|
51
51
|
|
|
52
52
|
### 3.2 `extensions.<org>.<name>`
|
|
53
53
|
|
|
54
|
-
Vendor and host extensions live under one key, `extensions`, whose members MUST match `^[a-z][a-z0-9]*(-[a-z0-9]+)*\.[a-z][a-z0-9]*(-[a-z0-9]+)*$` (short-form `<org>.<name>`). The orgs `openwop` and `
|
|
54
|
+
Vendor and host extensions live under one key, `extensions`, whose members MUST match `^[a-z][a-z0-9]*(-[a-z0-9]+)*\.[a-z][a-z0-9]*(-[a-z0-9]+)*$` (short-form `<org>.<name>`). The orgs in `spec/v2/declaration.json` `reservedOrgs` — `openwop`, `vendor`, `effect-seams` and `events` — are reserved: a host MUST NOT use any of them. An extension record's shape is the org's own (`additionalProperties: true` inside the record). The 11 v1 extension-class `host.*` families (§6) are advertised as `extensions.openwop-app.*` by the host that serves them. A change to an extension that would break an existing reader MUST ship under a new key; a host MUST NOT read one key's record as another's (A2A §4.6.3).
|
|
55
55
|
|
|
56
56
|
## 4. Deleted keys (RFC 0169 §A.5, §B.3; RFC 0175)
|
|
57
57
|
|
|
58
58
|
| Key | Why |
|
|
59
59
|
| --- | --- |
|
|
60
60
|
| `contractProvenance` | an advisory self-declaration the wire cannot falsify (RFC 0169 §A.5) |
|
|
61
|
-
| `supportedTransports` | REST is the wire;
|
|
62
|
-
| `grpc` |
|
|
61
|
+
| `supportedTransports` | REST is the wire (interop.md; RFC 0175 §B.1) |
|
|
62
|
+
| `grpc` | unwitnessable, so not advertisable (interop.md §gRPC; RFC 0175 §A.1) |
|
|
63
63
|
| `Capabilities-Etag`, `auth.subjectLinking`, `replay.fork`, bare `a2a.supported` / `mcp.supported`, the `openwop-core` alias | absent from the v2 root; rows `C2.1`–`C2.10` in `spec/v1/migrations.json` carry each with its codemod |
|
|
64
64
|
| `host.media`, `host.collaboration` | reserved slots are unrepresentable under a closed root; `host.workspace` is the declared family `workspace` |
|
|
65
65
|
|
|
@@ -75,11 +75,11 @@ Each heading is a `spec/v2/declaration.json` row with `anchor: core`; `scripts/c
|
|
|
75
75
|
|
|
76
76
|
### § supportedEnvelopes
|
|
77
77
|
|
|
78
|
-
Witness `witnessable-gated`; owner
|
|
78
|
+
Witness `witnessable-gated`; owner RFC 0193; facets `kinds`.
|
|
79
79
|
|
|
80
80
|
### § schemaVersions
|
|
81
81
|
|
|
82
|
-
Witness `witnessable-gated`; owner
|
|
82
|
+
Witness `witnessable-gated`; owner RFC 0193; facets `kinds`.
|
|
83
83
|
|
|
84
84
|
### § limits
|
|
85
85
|
|
|
@@ -87,7 +87,7 @@ Witness `witnessable-gated`; owner no owning RFC (declaration row).
|
|
|
87
87
|
|
|
88
88
|
### § envelopeStrictness
|
|
89
89
|
|
|
90
|
-
Witness `claims-check`; owner
|
|
90
|
+
Witness `claims-check`; owner RFC 0193; facets `mode`.
|
|
91
91
|
|
|
92
92
|
### § envelopeContracts
|
|
93
93
|
|
|
@@ -255,7 +255,7 @@ Witness `witnessable-gated`; owner RFC 0053.
|
|
|
255
255
|
|
|
256
256
|
### § webhooks
|
|
257
257
|
|
|
258
|
-
Witness `witnessable-gated`; owner RFC 0188; facets `deadLetter`, `retryPolicy`, `signatureAlgorithms`.
|
|
258
|
+
Witness `witnessable-gated`; owner RFC 0188; facets `deadLetter`, `retryPolicy`, `secretRotation`, `signatureAlgorithms`.
|
|
259
259
|
|
|
260
260
|
### § triggerBridge
|
|
261
261
|
|
|
@@ -309,11 +309,11 @@ Witness `witnessable-gated`; owner RFC 0019.
|
|
|
309
309
|
|
|
310
310
|
### § workflowChainPacks
|
|
311
311
|
|
|
312
|
-
Witness `witnessable-gated`; owner RFC 0013
|
|
312
|
+
Witness `witnessable-gated`; owner RFC 0013; facets `subChains`.
|
|
313
313
|
|
|
314
314
|
### § packs
|
|
315
315
|
|
|
316
|
-
Witness `claims-check`; owner RFC 0025
|
|
316
|
+
Witness `claims-check`; owner RFC 0025; facets `testMode`.
|
|
317
317
|
|
|
318
318
|
### § mcp
|
|
319
319
|
|
|
@@ -385,7 +385,7 @@ The `openwop-core` alias is deleted (row `C2.3`); the canonical discovery-only i
|
|
|
385
385
|
|
|
386
386
|
| Axis | Values | Source |
|
|
387
387
|
| --- | --- | --- |
|
|
388
|
-
| `technical` | `experimental \| stable \| deprecated` | the record's `status
|
|
388
|
+
| `technical` | `experimental \| stable \| deprecated` | the record's `status`, which MUST NOT exceed the declaration row (RFC 0197) |
|
|
389
389
|
| `adoption` | `none \| single-witness \| multi-witness \| independent` | derived from INTEROP-MATRIX bundle evidence |
|
|
390
390
|
|
|
391
391
|
`stable` does not require a tier-3 host; `independent` records whether one exists. The RFC 0155 numeric extension budget is repealed: a family MAY exist at any count if it declares its witness class. At the cut, `memory.injectionBudget`, `toolCatalog.compactView`, and `aiProviders.promptPrefixCache` keep their families with `adoption: single-witness` (RFC 0169 §C.5).
|
|
@@ -396,17 +396,4 @@ The `openwop-core` alias is deleted (row `C2.3`); the canonical discovery-only i
|
|
|
396
396
|
|
|
397
397
|
## 10. Migration rows (RFC 0169)
|
|
398
398
|
|
|
399
|
-
|
|
400
|
-
| --- | --- | --- |
|
|
401
|
-
| `C2.1` | `capabilities` wrapper | none (`openwop.codemod.capabilities-wrapper-removal`) |
|
|
402
|
-
| `C2.2` | `host.<family>` dotted mirrors | none |
|
|
403
|
-
| `C2.3` | `openwop-core` | `openwop-discovery-core` |
|
|
404
|
-
| `C2.4` | `contractProvenance` | none |
|
|
405
|
-
| `C2.5` | `auth.subjectLinking` | none; advertising `saml` and `scim` lanes implies the contract (`identity.md`) |
|
|
406
|
-
| `C2.6` | bare `a2a.supported` / `mcp.supported` | `versions[]` / `revisions[]` facets (the codemod refuses when no array is present) |
|
|
407
|
-
| `C2.7` | `replay.fork` boolean | `replay.modes[]` |
|
|
408
|
-
| `C2.8` | the 11 extension-class `host.*` families | `extensions.<org>.<name>` |
|
|
409
|
-
| `C2.9` | `Capabilities-Etag` | standard `ETag` / `If-None-Match` (dual emission through the overlap) |
|
|
410
|
-
| `C2.10` | root `profiles[]` | none — schema-invalid |
|
|
411
|
-
|
|
412
|
-
Rows `C2.2`–`C2.8` are transformed by `openwop.codemod.discovery-document-v2`; a family with `supported: false` is dropped, and a dotted-only declared family is promoted to its plain key. Certification bundles naming `openwop-core` are never-upgraded and remain valid v1 evidence at their version.
|
|
399
|
+
Rows `C2.1`–`C2.10` are `spec/v1/migrations.json` entries. `C2.2`–`C2.8` are transformed by `openwop.codemod.discovery-document-v2`; a family with `supported: false` is dropped, and a dotted-only declared family is promoted to its plain key. Certification bundles naming `openwop-core` are never-upgraded and remain valid v1 evidence at their version.
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## Why this exists
|
|
6
6
|
|
|
7
|
-
|
|
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.
|
|
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. 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.
|
|
12
12
|
|
|
13
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`.
|
|
14
14
|
|
|
@@ -21,8 +21,7 @@ failure MUST be `blocked`, never `inapplicable`; a suite with a blocked row MUST
|
|
|
21
21
|
NOT issue a certification (RFC 0168 §E.1).
|
|
22
22
|
|
|
23
23
|
When more than one gate can skip a test, evaluate host predicates before suite
|
|
24
|
-
predicates.
|
|
25
|
-
suite failure cannot be hidden as an inapplicable host requirement.
|
|
24
|
+
predicates.
|
|
26
25
|
|
|
27
26
|
## Witness class
|
|
28
27
|
|
|
@@ -39,8 +38,6 @@ Every family in `spec/v2/declaration.json`, every requirement in `conformance/re
|
|
|
39
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`.
|
|
41
40
|
|
|
42
|
-
The six v1 certification admissions map to one class each: the shape-versus-behavior dual grade is `witnessable-gated` on the behavioral leg, never two grades; "blocked as unobservable" is `seam-gated` or `unwitnessable`; install-time-only extension opacity is `claims-check`; corpus-structural legs run in the spec repo's CI and have no host class; gRPC end-to-end is `unwitnessable` (interop.md); a negative-existence claim is `negative-existence`.
|
|
43
|
-
|
|
44
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.
|
|
45
42
|
|
|
46
43
|
## The seams profile
|
|
@@ -73,11 +70,11 @@ A certification bundle validates against `schemas/v2/certification-bundle.schema
|
|
|
73
70
|
|
|
74
71
|
`signature` is an Ed25519 attestation over the 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.
|
|
75
72
|
|
|
76
|
-
A signature that cannot be resolved to a published key attests **integrity only
|
|
73
|
+
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.
|
|
77
74
|
|
|
78
75
|
### Recovery evidence
|
|
79
76
|
|
|
80
|
-
RFC 0158 §E
|
|
77
|
+
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.
|
|
81
78
|
|
|
82
79
|
| Row | `evidence` member |
|
|
83
80
|
| --- | --- |
|
|
@@ -5,7 +5,7 @@
|
|
|
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.
|
|
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.
|
|
9
9
|
|
|
10
10
|
## Provider identity
|
|
11
11
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
RFC 0101 and RFC 0110
|
|
8
|
+
RFC 0101 and RFC 0110 mint no client route for opening a conversation; the obligation an advertising host takes on is stated here.
|
|
9
9
|
|
|
10
10
|
## `multiPartyConversation`
|
|
11
11
|
|
package/spec/v2/core/errors.md
CHANGED
|
@@ -8,9 +8,9 @@ Every error a v2 host returns is a row in one registry. A client routes on `erro
|
|
|
8
8
|
|
|
9
9
|
## The registry
|
|
10
10
|
|
|
11
|
-
`spec/v2/errors.json` holds one row per code: `{ code, httpStatus, retriable, details, since, deprecated? }` plus the provenance fields `statusSource` and `source`. It registers **
|
|
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 **106** 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
|
|
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.
|
|
14
14
|
|
|
15
15
|
## The envelope
|
|
16
16
|
|
|
@@ -28,7 +28,7 @@ An interrupt has one code per state: a token or run-scoped resolve against an in
|
|
|
28
28
|
|
|
29
29
|
## Codes by HTTP status
|
|
30
30
|
|
|
31
|
-
Generated from `spec/v2/errors.json` (
|
|
31
|
+
Generated from `spec/v2/errors.json` (106 codes; `retriable` and `statusSource` are in the registry).
|
|
32
32
|
|
|
33
33
|
Code | Status
|
|
34
34
|
--- | ---
|
|
@@ -54,12 +54,16 @@ Code | Status
|
|
|
54
54
|
`schedule_horizon_exceeded` | 400
|
|
55
55
|
`sub_chain_cycle` | 400
|
|
56
56
|
`sub_chain_depth_exceeded` | 400
|
|
57
|
+
`unsupported_runtime` | 400
|
|
57
58
|
`unsupported_stream_mode` | 400
|
|
58
59
|
`until_in_past` | 400
|
|
59
60
|
`validation_error` | 400
|
|
61
|
+
`webhook_endpoint_unverified` | 400
|
|
60
62
|
`webhook_url_rejected` | 400
|
|
61
63
|
`audience_mismatch` | 401
|
|
64
|
+
`connector_auth_declined` | 401
|
|
62
65
|
`connector_auth_expired` | 401
|
|
66
|
+
`credential_lifetime_exceeded` | 401
|
|
63
67
|
`credential_revoked` | 401
|
|
64
68
|
`delegation_expired` | 401
|
|
65
69
|
`identity_unresolvable` | 401
|
|
@@ -108,6 +112,7 @@ Code | Status
|
|
|
108
112
|
`unsupported_media_type` | 415
|
|
109
113
|
`capability_not_provided` | 422
|
|
110
114
|
`capability_required` | 422
|
|
115
|
+
`connection_auth_metadata_mismatch` | 422
|
|
111
116
|
`credential_required` | 422
|
|
112
117
|
`envelope_invalid` | 422
|
|
113
118
|
`envelope_refusal` | 422
|
package/spec/v2/core/events.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
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
|
|
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.
|
|
9
9
|
|
|
10
10
|
## The envelope
|
|
11
11
|
|
|
@@ -24,7 +24,7 @@ A consumer MUST NOT throw on an event whose `type` it does not know; it folds wh
|
|
|
24
24
|
|
|
25
25
|
## Types
|
|
26
26
|
|
|
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` (
|
|
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:
|
|
28
28
|
|
|
29
29
|
```text
|
|
30
30
|
^(?!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]+)*)?$
|
|
@@ -43,8 +43,7 @@ A consumer MUST NOT throw on an event whose `type` it does not know; it folds wh
|
|
|
43
43
|
|
|
44
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.
|
|
45
45
|
|
|
46
|
-
`run.started` carries `owner
|
|
47
|
-
block as `RunSnapshot.owner` with `subject` REQUIRED (runs.md, identity.md).
|
|
46
|
+
`run.started` carries the `owner` block (identity.md §1.1).
|
|
48
47
|
`run.cancelled` carries `reason`, `cancelledBy`, `durationMs`, and `parentRunId`.
|
|
49
48
|
`run.completed` MUST carry `outputs` as an object; an empty object is valid, but
|
|
50
49
|
an absent key is not. This distinguishes “no outputs” from “outputs not
|
|
@@ -52,7 +51,7 @@ rendered” and is witnessed by `v2-run-completed-outputs`.
|
|
|
52
51
|
|
|
53
52
|
## AI envelopes: E1–E5
|
|
54
53
|
|
|
55
|
-
`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 excepted.
|
|
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
55
|
|
|
57
56
|
| Gap | Contract |
|
|
58
57
|
| --- | --- |
|
|
@@ -62,14 +61,6 @@ rendered” and is witnessed by `v2-run-completed-outputs`.
|
|
|
62
61
|
| E4 sub-typing | `$ref` composition, as in the payload registry above. |
|
|
63
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. |
|
|
64
63
|
|
|
65
|
-
Worked example (E5), `maxRefusals: 2`:
|
|
66
|
-
|
|
67
|
-
```text
|
|
68
|
-
seq 7 envelope.refusal nodeId n1 (refusal 1; the run's retry policy re-dispatches n1)
|
|
69
|
-
seq 9 envelope.refusal nodeId n1 (refusal 2 = ceiling)
|
|
70
|
-
seq 10 node.failed error.code envelope_refusal; n1 is not re-dispatched
|
|
71
|
-
```
|
|
72
|
-
|
|
73
64
|
## The events channel
|
|
74
65
|
|
|
75
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:
|
|
@@ -116,26 +107,23 @@ A run's log MUST contain exactly one terminal run event — `run.completed`, `ru
|
|
|
116
107
|
|
|
117
108
|
## Era-2 logs
|
|
118
109
|
|
|
119
|
-
An `eventLogSchemaVersion` of `2` means v1-written
|
|
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.
|
|
120
111
|
|
|
121
112
|
## The envelope-kind catalog
|
|
122
113
|
|
|
123
114
|
`supportedEnvelopes`, `schemaVersions` and `envelopeStrictness` are one flow, read in
|
|
124
|
-
that order on every inbound envelope.
|
|
125
|
-
a v2 capability record is an object, so each holds its value in a named seat.
|
|
115
|
+
that order on every inbound envelope.
|
|
126
116
|
|
|
127
117
|
`supportedEnvelopes.kinds` is the catalog. A host advertising it MUST refuse an
|
|
128
118
|
emitted `type` that is neither universal nor a member, with `unknown_envelope_kind`.
|
|
129
119
|
**An absent `kinds` is not an empty catalog and is not an unrestricted one**: a host
|
|
130
120
|
advertising `supportedEnvelopes` without it has made no catalog claim, and an engine
|
|
131
|
-
MUST refuse every non-universal kind rather than admit it unchecked.
|
|
132
|
-
reads as "unrestricted" would let a record claiming `stable` admit any kind an
|
|
133
|
-
attacker can name, which is precisely what `prompt-injection-envelope-typecheck`
|
|
134
|
-
forbids.
|
|
121
|
+
MUST refuse every non-universal kind rather than admit it unchecked.
|
|
135
122
|
|
|
136
123
|
`schemaVersions.kinds` maps a kind to its advertised floor; a kind absent from the map
|
|
137
124
|
has a floor of `0`. An emitted `schemaVersion` ABOVE the floor MUST be refused with
|
|
138
125
|
`unknown_schema_version` whatever the strictness.
|
|
126
|
+
`ui.a2ui-surface` at schema version 2 is specified by `ext/a2uiSurface/README.md`.
|
|
139
127
|
|
|
140
128
|
`envelopeStrictness.mode` governs drift BELOW the floor only. Under `warn` — the value
|
|
141
129
|
when the seat is absent — an engine MUST validate against the advertised version and
|
|
@@ -5,11 +5,11 @@
|
|
|
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.
|
|
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.
|
|
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"
|
|
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.
|
|
13
13
|
|
|
14
14
|
```jsonc
|
|
15
15
|
{ "id": "region", "type": "select", "label": "Region",
|
package/spec/v2/core/headers.md
CHANGED
|
@@ -11,12 +11,12 @@ RFC 0171 §C.1: every non-standard header is `OpenWOP-<Name>` and every header i
|
|
|
11
11
|
| Header | Operations | Meaning |
|
|
12
12
|
| --- | --- | --- |
|
|
13
13
|
| `Accept-Language` | 1 | BCP-47 preference list; authoritative for locale selection (i18n.md). A malformed value MUST NOT 400. |
|
|
14
|
-
| `Idempotency-Key` |
|
|
14
|
+
| `Idempotency-Key` | 15 | Per-mutation idempotency token (see `idempotency.md` Layer 1). |
|
|
15
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). |
|
|
16
16
|
| `Last-Event-ID` | 1 | Resume from sequence after this ID. |
|
|
17
17
|
| `OpenWOP-Dedup` | 1 | When set, server cross-host claim system rejects duplicate `(tenantId, scopeId)` pairs with `409 Conflict`. |
|
|
18
18
|
| `OpenWOP-Force-Engine-Version` | 1 | **Test-keys-only.** When set, the server emits events for this run AS IF it |
|
|
19
|
-
| `OpenWOP-Version` |
|
|
19
|
+
| `OpenWOP-Version` | 54 | RFC 0172 §A.3 — selects a listed major.minor; absent ⇒ the host's `preferredVersion`; unlisted ⇒ 406 protocol_version_unsupported. |
|
|
20
20
|
|
|
21
21
|
## Response headers
|
|
22
22
|
|
|
@@ -28,12 +28,13 @@ RFC 0171 §C.1: every non-standard header is `OpenWOP-<Name>` and every header i
|
|
|
28
28
|
| `ETag` | 3 | Optional probe handle for mid-session capability change detection. Deprecated toward v2 (RFC 0165 §C.2). |
|
|
29
29
|
| `Location` | 1 | Canonical URI of the new template. |
|
|
30
30
|
| `OpenWOP-Idempotent-Replay` | 1 | Set when the response was served from the idempotency cache. |
|
|
31
|
-
| `OpenWOP-Version` |
|
|
31
|
+
| `OpenWOP-Version` | 54 | RFC 0172 §A.4 — the contract that produced this response; MUST equal the one used. |
|
|
32
32
|
| `Retry-After` | 1 | Seconds until the active claim is stale-eligible. |
|
|
33
|
+
| `WWW-Authenticate` | 47 | 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. |
|
|
33
34
|
|
|
34
35
|
## Webhook delivery headers
|
|
35
36
|
|
|
36
|
-
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`).
|
|
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).
|
|
37
38
|
|
|
38
39
|
## Removed in v2
|
|
39
40
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Host services
|
|
2
2
|
|
|
3
3
|
> **Status: Stable · v2.0 · RFC 0144.** Normative contract for the advertised `host.*` service surfaces a node pack invokes through `ctx`.
|
|
4
|
-
> **Normative home:** `aiEnvelope`, `promptLibrary`, `agentRuntime`.
|
|
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
|
|
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.
|
|
9
9
|
|
|
10
10
|
## `aiEnvelope`
|
|
11
11
|
|
|
@@ -18,3 +18,7 @@ A host advertising `promptLibrary` MUST expose `ctx.promptLibrary.get`, MUST ret
|
|
|
18
18
|
## `agentRuntime`
|
|
19
19
|
|
|
20
20
|
A host advertising `agentRuntime` MUST expose `spawn`, `delegate`, `consensus` and `messageSend`, and MUST satisfy `agents.manifestRuntime`, which advertising it implies.
|
|
21
|
+
|
|
22
|
+
## `mcp`
|
|
23
|
+
|
|
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.
|
|
@@ -5,7 +5,7 @@
|
|
|
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
|
|
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).
|
|
9
9
|
|
|
10
10
|
## Layer 1: `Idempotency-Key`
|
|
11
11
|
|
package/spec/v2/core/identity.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
v2 makes the Subject the owner of every run, binds every lane to a trust root and a revocation rule, gives the link and every id a grammar, and prefixes tokens so a host can rotate them. Idempotency-key grammar is `idempotency.md`.
|
|
9
9
|
|
|
10
10
|
## 1. The Subject is the owner (RFC 0170 §A)
|
|
11
11
|
|
|
@@ -41,7 +41,7 @@ An end user reaching the host through an A2A peer is `kind: anonymous`, `lane: a
|
|
|
41
41
|
|
|
42
42
|
### 2.1 The pipeline (§B.1)
|
|
43
43
|
|
|
44
|
-
Every lane MUST: verify the credential against the lane's trust root; bind the verified identity to the request, never to an asserted header; check audience; resolve to a Subject before any authorization decision; and fail closed. The closed reason vocabulary is the family-wide error set in §6. Every lane is advertised as one member of the `auth.lanes[]` facet (`spec/v2/facets/auth.schema.json`):
|
|
44
|
+
Every lane MUST: verify the credential against the lane's trust root; bind the verified identity to the request, never to an asserted header; check audience; resolve to a Subject before any authorization decision; and fail closed. On the `oidc` lane an ID token MAY be a bearer only when its `aud` equals the host's configured audience; any other `aud` is `audience_mismatch`. The closed reason vocabulary is the family-wide error set in §6. Every lane is advertised as one member of the `auth.lanes[]` facet (`spec/v2/facets/auth.schema.json`):
|
|
45
45
|
|
|
46
46
|
`authorization.failClosed` advertises that rule and MUST be `true` when present; it does not gate it (invariant `authorization-fail-closed`). `authorization.roles` is the host role catalog: a request is authorized when any role-derived scope matches the required scope, under the same scope-match semantics this document applies to a credential.
|
|
47
47
|
|
|
@@ -60,8 +60,8 @@ Every lane MUST name its trust root as `subject.issuer` and MUST advertise it in
|
|
|
60
60
|
| Lane | `subject.issuer` (trust root) | Revocation MUST | `revocation` |
|
|
61
61
|
| --- | --- | --- | --- |
|
|
62
62
|
| `api-key` | the key realm (`urn:<host>:api-key` or a host-chosen URI) | refuse a revoked key on the next request (`credential_revoked`) | `next-request` |
|
|
63
|
-
| `oauth2` | the token issuer | honor `exp
|
|
64
|
-
| `oidc` | `iss` | as `oauth2` | `exp-and-recheck` |
|
|
63
|
+
| `oauth2` | the token issuer | honor `exp` and re-check the issuer within the advertised `revocationWindowSeconds`; or honor `exp` alone under an enforced lifetime bound (`exp-only`, below) | `exp-and-recheck \| exp-only` |
|
|
64
|
+
| `oidc` | `iss` | as `oauth2` | `exp-and-recheck \| exp-only` |
|
|
65
65
|
| `mtls` | the CA subject | check CRL or OCSP, or issue certificates whose lifetime is at most the advertised window | `crl \| ocsp \| short-lived` |
|
|
66
66
|
| `saml` | the IdP entityID (`<saml:Issuer>`) | honor `NotOnOrAfter`; consult the SCIM link deny-set when both lanes are advertised (§3) | `not-on-or-after` |
|
|
67
67
|
| `scim` | the SCIM connection id bound at configuration to one IdP entityID | bind each client credential to one IdP entityID; refuse an unbound request | `bound-connection` |
|
|
@@ -70,7 +70,11 @@ Every lane MUST name its trust root as `subject.issuer` and MUST advertise it in
|
|
|
70
70
|
| `session` | `urn:<host>:session` | refuse a revoked session on the next request (`credential_revoked`) | `next-request` |
|
|
71
71
|
| `anonymous` | `urn:<host>:anon-surface` | — | — |
|
|
72
72
|
|
|
73
|
-
`revocationWindowSeconds` (integer ≥ 1) MUST be advertised wherever the rule names a window
|
|
73
|
+
`revocationWindowSeconds` (integer ≥ 1) MUST be advertised wherever the rule names a window — `exp-and-recheck`, `exp-only`, `short-lived`, `rebind`. On every lane it is **an upper bound on the interval between a revocation at the trust root and the host's first refusal**, and a host MUST NOT advertise a window it does not enforce.
|
|
74
|
+
|
|
75
|
+
A host MUST NOT advertise a `revocation` value the row above for its lane does not list. A consumer meeting an unrecognized value MUST NOT act on it: it MUST read the lane as stating no revocation latency, never as `next-request` or any other member (`overview.md` §0).
|
|
76
|
+
|
|
77
|
+
**`exp-only` (RFC 0210).** `exp-only` names a host that honors `exp` and re-checks revocation never: it consults no introspection endpoint, no userinfo endpoint, no revocation list and no host-side epoch or `validAfter` record, so a credential revoked at the trust root is accepted until its own `exp`. Its window is therefore the only bound there is, and it is enforced rather than described. A host advertising `exp-only` on a lane MUST refuse a credential presented on that lane when **either** `exp − iat` (total lifetime) **or** `exp − now` (remaining lifetime) exceeds the advertised `revocationWindowSeconds`, with `401 credential_lifetime_exceeded`; a credential carrying no `iat` MUST be refused with the same code, because the first bound cannot be evaluated without it (§2.1 fail-closed). Both bounds are load-bearing: `exp − iat` alone admits a ten-year token minted ten years ago, `exp − now` alone admits a freshly minted ten-year token in its ninth year. A host that cannot enforce both MUST NOT advertise `exp-only`. `exp-only` SHOULD be advertised with a window of one hour or less; the corpus states no maximum, because no upstream specification does. `exp-only` MUST NOT be advertised on the `api-key` or `session` lane — there the host issued the credential itself, so revocation is in its own hands — and `auth.schema.json` refuses that pairing (invariant `lane-exp-only-lifetime-bounded`).
|
|
74
78
|
|
|
75
79
|
### 2.3 Minimum assurance (§B.4)
|
|
76
80
|
|
|
@@ -80,6 +84,12 @@ Each lane MUST advertise `minimumAssurance: bearer | sender-constrained | key-bo
|
|
|
80
84
|
|
|
81
85
|
The proof format is lane-scoped: mTLS key binding or DPoP for the two JWT lanes (`oauth2`, `oidc`), SVID chains for `workload`. A host MUST advertise the proofs it accepts under `auth.lanes[].delegationProofs[]` (`mtls-key-binding | dpop | svid-chain`). A chain with no acceptable proof MUST be refused as `identity_unverified`. The chain rules keep their codes: a chain longer than the bound is `delegation_chain_too_long`, a cyclic chain is `delegation_chain_cyclic`, and a link that widens scope is `delegation_scope_amplified` (invariants `delegation-chain-bounded-acyclic`, `delegation-no-scope-amplification`, `delegation-provenance-not-authorization`).
|
|
82
86
|
|
|
87
|
+
### 2.5 Protected-resource metadata and challenges (RFC 0200)
|
|
88
|
+
|
|
89
|
+
A host advertising an `oauth2` or `oidc` lane MUST serve RFC 9728 metadata at the well-known URI formed from its resource identifier, the base URL it serves this API under, with `/.well-known/oauth-protected-resource` inserted before any path (`https://h.example/api` → `https://h.example/.well-known/oauth-protected-resource/api`), unauthenticated. `resource` MUST equal that identifier; `authorization_servers` MUST list exactly the URL-form issuers of those lanes; `scopes_supported` MUST list the scopes the host enforces; `dpop_bound_access_tokens_required` or `tls_client_certificate_bound_access_tokens` MAY be `true` only where every such lane's `minimumAssurance` requires that binding (§2.3).
|
|
90
|
+
|
|
91
|
+
On such a host a `401` MUST carry `WWW-Authenticate: Bearer resource_metadata="<url>"`, adding `error="invalid_token"` when a credential was presented and no error code when none was, and a `403` for insufficient scope MUST carry `error="insufficient_scope"` with `scope` listing every scope the operation requires; a `403` for resource binding carries no `insufficient_scope`. Other hosts SHOULD send `WWW-Authenticate: Bearer` on a `401`. A challenge attaches only to a response already `401` or `403` and MUST NOT change a status: where a rule requires `404` for an unknown or unauthorized resource, the `404` stands and carries none (invariant `auth-challenge-no-oracle`).
|
|
92
|
+
|
|
83
93
|
## 3. The link is a record (RFC 0170 §C; `schemas/v2/subject-link.schema.json`)
|
|
84
94
|
|
|
85
95
|
```json
|
|
@@ -90,13 +100,13 @@ The proof format is lane-scoped: mTLS key binding or DPoP for the two JWT lanes
|
|
|
90
100
|
|
|
91
101
|
The record and both `SubjectRef`s are closed; `a`, `b`, `keyClass`, `issuer`, `tenant`, `formedAt` are REQUIRED. A link MUST be tenant-scoped, MUST join exactly two subjects whose `issuer` values are bound to one IdP entityID (`issuer` on the record), and MUST NOT include a legacy (`urn:openwop:legacy` is schema-rejected) or anonymous subject. Deactivation sets `deniedAt`; the SAML decision path MUST consult it (the leaver contract). The link is a reference, not a merge: nothing rewrites a subject already stamped on a run.
|
|
92
102
|
|
|
93
|
-
`auth.subjectLinking` is removed (
|
|
103
|
+
`auth.subjectLinking` is removed (migration row `C2.5`): advertising both `saml` and `scim` lanes implies the contract. Lanes stay separate facets; there is no single "enterprise identity" profile. The `auth.subjectLinkKey` facet (`opaque-idp | configured-immutable`) names the key class the host forms links under.
|
|
94
104
|
|
|
95
105
|
## 4. Resume tokens (RFC 0170 §E.1; RFC 0176 §B.2)
|
|
96
106
|
|
|
97
107
|
An interrupt resume token is `ow2.<alg>.<kid>.<payload>.<mac>`: `alg ∈ {hs256}` at the cut (`interrupt.tokenAlgs[]` advertises it), `kid` (`keyId` grammar) selects the verification secret, `payload` and `mac` as in v1. A host MUST refuse a token whose `alg` it does not advertise or whose `kid` it does not hold with `401` `interrupt_token_invalid`. The `{token}` path parameter carries the grammar (`api/v2/openapi.yaml`).
|
|
98
108
|
|
|
99
|
-
|
|
109
|
+
A token the host issued under v1 (any token not `ow2.`-prefixed; the rule is the prefix, never a segment count, persistence.md) MUST remain resolvable under `kid: legacy` until its `expiresAt`; a run suspended on an interrupt at the cut continues under `persistence.md` and its outstanding token resolves the same way. Interrupt semantics are `interrupt.md`.
|
|
100
110
|
|
|
101
111
|
## 5. Identifier grammars (`schemas/v2/ids.schema.json`)
|
|
102
112
|
|
|
@@ -113,17 +123,17 @@ Every id field in every v2 schema and every `api/v2/openapi.yaml` parameter and
|
|
|
113
123
|
| `nodeId`, `workflowId`, `agentId`, `chainId`, `pluginId`, `templateId`, `libraryId` | `^[A-Za-z0-9._~:-]{1,128}$` | author |
|
|
114
124
|
| `typeId` | `^[a-z][a-z0-9_-]*(\.[a-z][a-zA-Z0-9_-]*)+$`, maxLength 256 | author |
|
|
115
125
|
|
|
116
|
-
`scripts/check-id-kinds-bound.mjs` enforces this against `spec/v2/id-field-bindings.json`, which sorts every `*Id` property in a v2 schema into two sets: it **is** a kind above (and MUST `$ref` it), or nothing here governs it (reason recorded)
|
|
126
|
+
`scripts/check-id-kinds-bound.mjs` enforces this against `spec/v2/id-field-bindings.json`, which sorts every `*Id` property in a v2 schema into two sets: it **is** a kind above (and MUST `$ref` it), or nothing here governs it (reason recorded); a field in neither set fails. *Every id field*, not every matching name.
|
|
117
127
|
|
|
118
128
|
A host MUST reject a tenant-bound id whose tenant segment is not the caller's with `403` `id_tenant_mismatch`. A host-minted opaque segment MUST match `^[A-Za-z0-9._~-]{16,128}$`.
|
|
119
129
|
|
|
120
|
-
**On the wire a tenant-bound id is one path segment, projected** (RFC 0184): every UTF-8 byte outside `[A-Za-z0-9._-]` becomes `~` plus two uppercase hex digits, so `acme/r-9f3c…` travels as `acme~2Fr-9f3c…`. A host MUST emit it in every link and MUST accept it on every tenant-bound parameter; it MUST still accept `tenant%2Fopaque`, and MUST decode either before matching the grammar. A host MUST NOT mint a tenant-bound id containing `~`; ids already minted MUST still resolve. A host MUST project exactly once, where an id leaves it, and MUST NOT re-encode its own output.
|
|
130
|
+
**On the wire a tenant-bound id is one path segment, projected** (RFC 0184): every UTF-8 byte outside `[A-Za-z0-9._-]` becomes `~` plus two uppercase hex digits, so `acme/r-9f3c…` travels as `acme~2Fr-9f3c…`. A host MUST emit it in every link and MUST accept it on every tenant-bound parameter; it MUST still accept `tenant%2Fopaque`, and MUST decode either before matching the grammar. A host MUST NOT mint a tenant-bound id containing `~`; ids already minted MUST still resolve. A host MUST project exactly once, where an id leaves it, and MUST NOT re-encode its own output.
|
|
121
131
|
|
|
122
|
-
**Through the overlap the bare form is admitted on a major-2 path parameter
|
|
132
|
+
**Through the overlap the bare form is admitted on a major-2 path parameter**. A parameter carrying only the opaque segment (what a `/v1/` create hands out) MUST resolve under the caller's tenant and never another's, and the response MUST name the resource bound (`versioning.md` §5); the credential supplies the segment the `403` check would read. Once a host advertises no `1.x` member it MUST refuse the bare form `400 validation_error` (not `id_tenant_mismatch`, not `not_found`). Ids in documents and bodies are bound, always. A client MAY bind at its request seam. Handle grammars (`memoryRef`, workspace `path`/`etag`, the plugin version token) and their `resolvability` class are specified where each handle is used; an importer MUST re-mint every `host`-scoped handle (`spec/v2/ext/portability/`).
|
|
123
133
|
|
|
124
134
|
## 6. Identity error codes (`spec/v2/errors.json`)
|
|
125
135
|
|
|
126
|
-
Every code below is a row with `
|
|
136
|
+
Every code below is a row with `retriable: false` and no `details` contract; the envelope is `errors.md`. All are `since: "2.0"` except `credential_lifetime_exceeded` (`since: "2.36"`, RFC 0210).
|
|
127
137
|
|
|
128
138
|
| Code | HTTP | Raised when |
|
|
129
139
|
| --- | --- | --- |
|
|
@@ -131,6 +141,7 @@ Every code below is a row with `since: "2.0"`, `retriable: false`, and no `detai
|
|
|
131
141
|
| `identity_unresolvable` | 401 | a verified identity resolves to no Subject |
|
|
132
142
|
| `audience_mismatch` | 401 | the credential's audience is not this host |
|
|
133
143
|
| `credential_revoked` | 401 | a revoked key or session is presented (§2.2) |
|
|
144
|
+
| `credential_lifetime_exceeded` | 401 | a credential on an `exp-only` lane exceeds the advertised lifetime bound, or carries no `iat` (§2.2) |
|
|
134
145
|
| `delegation_expired` | 401 | a delegation or workload credential is past its lifetime |
|
|
135
146
|
| `sender_constraint_missing` | 401 | the request is below the lane's `minimumAssurance` (§2.3) |
|
|
136
147
|
| `delegation_chain_too_long` | 400 | the actor chain exceeds depth 4 |
|
|
@@ -143,4 +154,4 @@ Every code below is a row with `since: "2.0"`, `retriable: false`, and no `detai
|
|
|
143
154
|
|
|
144
155
|
## 7. Invariants (RFC 0170 §E.2)
|
|
145
156
|
|
|
146
|
-
|
|
157
|
+
The RFC 0170 §E.2 invariants are registered in `SECURITY/invariants.yaml` with their scenarios; an invariant that reaches the cut without a witness is demoted from `protocol` tier and recorded.
|