@openwop/spec-artifacts 2.44.5 → 2.44.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CORPUS-STAMP.json +49 -47
- package/api/seams-v2.yaml +1 -1
- package/api/v2/asyncapi.yaml +1 -1
- package/api/v2/openapi.yaml +31 -122
- package/package.json +1 -1
- package/schemas/v2/agent-ref.schema.json +2 -3
- package/schemas/v2/budget-policy.schema.json +2 -3
- package/schemas/v2/capabilities.schema.json +42 -38
- package/schemas/v2/debug-bundle.schema.json +2 -2
- package/schemas/v2/error-envelope.schema.json +282 -4
- package/schemas/v2/node-pack-manifest.schema.json +2 -2
- package/schemas/v2/prompt-ref.schema.json +2 -3
- package/schemas/v2/prompt-template.schema.json +3 -3
- package/schemas/v2/run-event-payloads.schema.json +5 -5
- package/spec/v1/alias-detectors.json +25 -1
- package/spec/v1/core-standard-manifest.json +2 -2
- package/spec/v1/deprecations.json +89 -1
- package/spec/v1/gaps.json +46 -12
- package/spec/v1/migrations.json +57 -1
- package/spec/v2/core/artifact-type-packs.md +5 -1
- package/spec/v2/core/capabilities.md +20 -22
- package/spec/v2/core/conformance.md +24 -7
- package/spec/v2/core/errors.md +17 -119
- package/spec/v2/core/events.md +7 -7
- package/spec/v2/core/execution.md +56 -16
- package/spec/v2/core/host-services.md +57 -58
- package/spec/v2/core/i18n.md +10 -10
- package/spec/v2/core/idempotency.md +3 -3
- package/spec/v2/core/identity.md +11 -9
- package/spec/v2/core/interop.md +6 -8
- package/spec/v2/core/interrupt.md +8 -8
- package/spec/v2/core/oauth.md +4 -4
- package/spec/v2/core/packs.md +11 -5
- package/spec/v2/core/persistence.md +11 -13
- package/spec/v2/core/portability.md +4 -4
- package/spec/v2/core/replay.md +8 -10
- package/spec/v2/core/runs.md +20 -7
- package/spec/v2/core/security-defaults.md +0 -2
- package/spec/v2/core/storage.md +18 -20
- package/spec/v2/core/versioning.md +4 -9
- package/spec/v2/core/webhooks.md +13 -8
- package/spec/v2/declaration.json +3 -3
- package/spec/v2/errors.json +206 -2
- package/spec/v2/facets/agents.schema.json +367 -0
- package/spec/v2/facets/aiProviders.schema.json +2 -2
- package/spec/v2/generated/error-codes.md +130 -0
- package/spec/v2/release.json +2 -2
- package/spec/v2/surface-baseline.json +38 -2
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
`interrupt` is how a run waits for something outside itself: a decision, an answer, an event, a conversation turn. Every kind shares one payload shape, event pair, resolve contract and token scheme
|
|
8
|
+
`interrupt` is how a run waits for something outside itself: a decision, an answer, an event, a conversation turn. Every kind shares one payload shape, event pair, resolve contract and token scheme.
|
|
9
9
|
|
|
10
10
|
## Payload
|
|
11
11
|
|
|
@@ -35,7 +35,7 @@ Per-kind rules:
|
|
|
35
35
|
|
|
36
36
|
### Re-entry and resume values
|
|
37
37
|
|
|
38
|
-
`key` is the deterministic re-entry key of one invocation. A host MUST derive it from at least the run, the node and the node's visit index:
|
|
38
|
+
`key` is the deterministic re-entry key of one invocation. A host MUST derive it from at least the run, the node and the node's visit index: how many of that node's interrupts in this run had their resolution consumed before this execution began. Its spelling is host-defined.
|
|
39
39
|
|
|
40
40
|
- A replay or recovery of the same execution MUST re-derive the same key.
|
|
41
41
|
- A later execution of the node, reached over an edge, MUST derive a different key, MUST raise a new `interrupt.requested`, and MUST NOT return an earlier visit's `resumeValue`.
|
|
@@ -45,7 +45,7 @@ Per-kind rules:
|
|
|
45
45
|
- An in-memory cache MAY serve in-process replays but MUST NOT replace the event log for cross-process replays.
|
|
46
46
|
- A host MUST validate the resume value against `resumeSchema` when one is declared, and MUST refuse a failing value with `400 validation_error`.
|
|
47
47
|
|
|
48
|
-
`timeoutMs`, when set, is the interrupt's own deadline
|
|
48
|
+
`timeoutMs`, when set, is the interrupt's own deadline; for an approval gate see §Rejection.
|
|
49
49
|
|
|
50
50
|
## Events
|
|
51
51
|
|
|
@@ -54,7 +54,7 @@ Every kind uses two registered types ([events.md](events.md)):
|
|
|
54
54
|
- `interrupt.requested` — the payload is the `InterruptPayload` verbatim.
|
|
55
55
|
- `interrupt.resolved` — the closed payload is `interruptResolved`. Resolving an approval-kind interrupt MUST record the applied `action` there, with the field §Approval requires.
|
|
56
56
|
|
|
57
|
-
The kind-specific `approval.*` and `clarification.*` types remain registered
|
|
57
|
+
The kind-specific `approval.*` and `clarification.*` types remain registered; their payloads in `schemas/v2/run-event-payloads.schema.json` are `$ref` aliases of `interruptRequested` and `interruptResolved`. A host emitting `interrupt.requested` MAY also emit the kind-specific type.
|
|
58
58
|
|
|
59
59
|
Both events are durable and appear in the `updates` and `debug` stream modes. While suspended, `RunSnapshot.currentNodeId` names the node and `status` is `waiting-approval`, `waiting-input` or `waiting-external`.
|
|
60
60
|
|
|
@@ -72,7 +72,7 @@ Both events are durable and appear in the `updates` and `debug` stream modes. Wh
|
|
|
72
72
|
|
|
73
73
|
### Callback delivery
|
|
74
74
|
|
|
75
|
-
`createRun.callbackUrl` names where a host
|
|
75
|
+
`createRun.callbackUrl` names where a host advertising `interrupt.callbackDelivery: true` delivers notice of an interrupt, for resolution through the token surface. Payload, timing and signing are host-defined.
|
|
76
76
|
|
|
77
77
|
A host advertising the facet:
|
|
78
78
|
|
|
@@ -98,7 +98,7 @@ The token grammar and the `interrupt.tokenAlgs[]` / `kid` check are [identity.md
|
|
|
98
98
|
|
|
99
99
|
- **Expiry.** Every token MUST carry `expiresAt`. The default SHOULD be 30 minutes, and a host MUST cap the lifetime at the interrupt's `timeoutMs` when one exists. A token MUST NOT outlive the interrupt it resolves; past `expiresAt` the host MUST answer `410 interrupt_expired`.
|
|
100
100
|
- **Invalidation.** A token MUST be invalidated when its interrupt is resolved or its run is cancelled or completed. Later use MUST answer `409 interrupt_already_resolved`.
|
|
101
|
-
- **Verification.** MAC comparison MUST be constant-time. `kid` selects the verification secret, so secrets rotate without orphaning
|
|
101
|
+
- **Verification.** MAC comparison MUST be constant-time. `kid` selects the verification secret, so secrets rotate without orphaning tokens.
|
|
102
102
|
- **Intent.** A token minted with `intent: resolve` authorizes both operations. A host MAY mint `intent: inspect` tokens; a resolve with one MUST be refused with `403`.
|
|
103
103
|
|
|
104
104
|
## Approval
|
|
@@ -141,9 +141,9 @@ The facet `spec/v2/facets/interrupt.schema.json` carries `tokenAlgs[]` (REQUIRED
|
|
|
141
141
|
- **`audience`** is a notification hint, never eligibility. When omitted, the host SHOULD notify the union of the eligibility refs.
|
|
142
142
|
- A host that does not advertise a ref kind MUST ignore that field.
|
|
143
143
|
|
|
144
|
-
Eligibility binds every writer of the suspension record
|
|
144
|
+
Eligibility binds every writer of the suspension record. A host whose durable store is writable by a principal other than the engine MUST enforce the same eligibility at the store, or MUST NOT expose the record to that principal for write.
|
|
145
145
|
|
|
146
|
-
Refs are opaque to the engine
|
|
146
|
+
Refs are opaque to the engine. Membership MUST be resolved at decision time and MUST NOT be re-resolved during replay or `forkRun`: the recorded eligibility decision is fixed history ([replay.md](replay.md)).
|
|
147
147
|
|
|
148
148
|
## During the v1 overlap
|
|
149
149
|
|
package/spec/v2/core/oauth.md
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
A connector node needs a token a user granted to a third party. The host obtains, stores and refreshes it and hands it to the node's sandbox
|
|
8
|
+
A connector node needs a token a user granted to a third party. The host obtains, stores and refreshes it and hands it to the node's sandbox; a pack names a provider and scopes and never touches the grant. Here the host is an OAuth client; [identity.md](identity.md) covers it as a protected resource.
|
|
9
9
|
|
|
10
10
|
## Credentials
|
|
11
11
|
|
|
12
|
-
A host advertising `credentials` MUST resolve a `{ ref, scope }` reference (`schemas/v2/credential-reference.schema.json`) at node execution and inject the material into the node sandbox
|
|
12
|
+
A host advertising `credentials` MUST resolve a `{ ref, scope }` reference (`schemas/v2/credential-reference.schema.json`) at node execution and inject the material only into the node sandbox.
|
|
13
13
|
|
|
14
14
|
- The material MUST NOT appear in inputs, variables, events, the debug bundle or replay state (invariant `credential-payload-redaction`).
|
|
15
15
|
- A failed resolution is `credential_not_found`, `credential_forbidden` (outside the caller's scope; fail closed) or `credential_scope_unsupported` (a scope not in `credentials.scopes`).
|
|
@@ -46,7 +46,7 @@ Where the provider is reached as an MCP server:
|
|
|
46
46
|
- It fetches the server's Protected Resource Metadata (RFC 9728) only from URLs derived from the manifest's server URL.
|
|
47
47
|
- Discovery verifies and never selects. A discovered issuer or endpoint that differs from the manifest's, or from the tuple pinned at registration, MUST be refused `connection_auth_metadata_mismatch`. A grant for such a provider whose manifest declares no `issuer` MUST be refused the same way.
|
|
48
48
|
|
|
49
|
-
This binds a
|
|
49
|
+
This binds a host-configured provider as well as a connection-pack one: the configured server URL and issuer stand in for the manifest's, pinned when the host loads that configuration.
|
|
50
50
|
|
|
51
51
|
## The credential interrupt
|
|
52
52
|
|
|
@@ -61,6 +61,6 @@ Then:
|
|
|
61
61
|
- `connectUrl` MUST be host-owned, MUST NOT be pre-authenticated, and MUST complete only for the initiating Subject.
|
|
62
62
|
- The host resolves the interrupt when the grant completes. A resolve of `authorized` MUST be refused `400 validation_error` unless a credential now resolves.
|
|
63
63
|
- `declined` fails the node with `connector_auth_declined`.
|
|
64
|
-
- A host that binds a node to one credential reference (
|
|
64
|
+
- A host that binds a node to one credential reference (such as a connection) reads "resolves" as that reference resolving with the node's scopes, carries it as `credentialRef`, and still completes `connectUrl` only for the initiating Subject.
|
|
65
65
|
|
|
66
66
|
*Sources: RFCs 0046, 0047, 0199.*
|
package/spec/v2/core/packs.md
CHANGED
|
@@ -14,7 +14,7 @@ A manifest's `engines.openwop` MUST match the grammar in `schemas/v2/node-pack-m
|
|
|
14
14
|
- A v2 host MUST treat a range with no upper bound as bounded by `<2.0.0`.
|
|
15
15
|
- A host MUST refuse to install a version whose range does not admit the host's protocol major with `pack_engine_unsupported` (`spec/v2/errors.json`).
|
|
16
16
|
- `pack_runtime_requirement_unmet` remains a runtime-requirement code and MUST NOT be used for the protocol major.
|
|
17
|
-
- Both checks MUST run at install on every publication path — the canonical registry, a vendor registry's write API, and a mirror ingest
|
|
17
|
+
- Both checks MUST run at install on every publication path — the canonical registry, a vendor registry's write API, and a mirror ingest.
|
|
18
18
|
|
|
19
19
|
The range is a claim about the pack's own surface, not about run semantics. Admitting major M asserts that:
|
|
20
20
|
|
|
@@ -37,7 +37,7 @@ A host advertises `packs` when it serves the registry surface above:
|
|
|
37
37
|
|
|
38
38
|
The registry is versioned by tree, not header. It publishes `registry/v2/packs/<name>/-/<version>.{json,sbom.json,sig,tgz}` as a parallel tree of re-signed manifests with regenerated SBOMs and index.
|
|
39
39
|
|
|
40
|
-
- A signed compatibility overlay MUST be rejected
|
|
40
|
+
- A signed compatibility overlay MUST be rejected; a mirror re-derives the signer at ingest.
|
|
41
41
|
- `.well-known/openwop-registry.json` `endpoints` is the negotiation: it names both trees, and a client MUST resolve every registry path through it rather than construct one.
|
|
42
42
|
- `publicKey` is unversioned: keys are not protocol-versioned.
|
|
43
43
|
|
|
@@ -85,7 +85,7 @@ There is one signing scheme. `signing` on a version manifest (`schemas/v2/regist
|
|
|
85
85
|
- **`keyId`** is the signing key id.
|
|
86
86
|
- A block carrying `method`, `publicKeyRef` or `signatureRef` fails validation.
|
|
87
87
|
|
|
88
|
-
The same block applies to a **bare manifest** — the `pack.json` inside the tarball, which is the document the signature covers. `signing` is OPTIONAL there
|
|
88
|
+
The same block applies to a **bare manifest** — the `pack.json` inside the tarball, which is the document the signature covers. `signing` is OPTIONAL there; when present it MUST be the identical closed `{ keyId, scheme }` object, on every bare manifest kind.
|
|
89
89
|
|
|
90
90
|
- A verifier MUST verify the signature against the issuing registry's key for `keyId`, and MUST check the pack name against that key's `permittedNamespaces`.
|
|
91
91
|
- A signature over tarball bytes is not a v2 signature; such a pack MUST be re-signed, not relabeled.
|
|
@@ -103,7 +103,7 @@ A registry MUST refuse a submission that breaks these rules or republishes a ver
|
|
|
103
103
|
|
|
104
104
|
## The registry's own schemas
|
|
105
105
|
|
|
106
|
-
A registry MUST validate submissions against vendored copies of these schemas pinned to a corpus tag, and MUST re-sync them from that tag before any v2 publication. An unpinned or drifted vendored schema is a registry defect
|
|
106
|
+
A registry MUST validate submissions against vendored copies of these schemas pinned to a corpus tag, and MUST re-sync them from that tag before any v2 publication. An unpinned or drifted vendored schema is a registry defect.
|
|
107
107
|
|
|
108
108
|
## Errors
|
|
109
109
|
|
|
@@ -126,7 +126,13 @@ A `frontend-plugin` pack (`schemas/v2/frontend-plugin-manifest.schema.json`) shi
|
|
|
126
126
|
|
|
127
127
|
A host without `uiPlugins` MUST reject the pack and render its own way.
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
On the message protocol:
|
|
130
|
+
|
|
131
|
+
- A breaking method change is `ui-plugin/2`.
|
|
132
|
+
- `host.announce` MUST be length-capped (SHOULD ≤ 400 characters) and SHOULD be rate-limited.
|
|
133
|
+
- `host.documentChanged` SHOULD be debounced. A plugin MUST treat the latest one as authoritative and tolerate unknown `host.selectionChanged` kinds.
|
|
134
|
+
|
|
135
|
+
`maxEntryBytes` caps an entry bundle.
|
|
130
136
|
|
|
131
137
|
## During the v1 overlap
|
|
132
138
|
|
|
@@ -14,7 +14,7 @@ How a v2 host reads what a v1 host wrote, what happens to a run in flight at the
|
|
|
14
14
|
- A host MUST NOT carry a private mapping.
|
|
15
15
|
- A vendor-prefixed v1 type the codemap does not name MUST be read under its own name, unchanged.
|
|
16
16
|
- "Vendor-prefixed" means the first segment is an org registered in the `extensions` object of `spec/v2/declaration.json` ([events.md §Rules](events.md)). `openwop.` is the only reserved prefix. An unregistered first segment is not a vendor prefix and falls to the refusal in §"The reader rule".
|
|
17
|
-
- An org is registered by pull request against the corpus and takes effect on the `@openwop/spec-artifacts` release that carries it. A shipped entry is append-only
|
|
17
|
+
- An org is registered by pull request against the corpus and takes effect on the `@openwop/spec-artifacts` release that carries it. A shipped entry is append-only.
|
|
18
18
|
|
|
19
19
|
## The era key
|
|
20
20
|
|
|
@@ -32,9 +32,9 @@ Discovery MUST advertise the value the host writes for new runs and nothing else
|
|
|
32
32
|
Absent stays era `2` forever; it is never backfilled.
|
|
33
33
|
|
|
34
34
|
- A host MUST NOT rewrite historical rows to add an explicit `2`, and a reader MUST NOT require one.
|
|
35
|
-
- A host with more than one creation path MUST begin stamping `3` on all of them in the same change. An unstamped path's runs read as era `2
|
|
35
|
+
- A host with more than one creation path MUST begin stamping `3` on all of them in the same change. An unstamped path's runs read as era `2`.
|
|
36
36
|
|
|
37
|
-
The snapshot field is required on the wire, and MAY be synthesized: for an era-`2` run with nothing stored, the host MUST supply `2` from the absent-⇒-`2` rule rather than fail the read.
|
|
37
|
+
The snapshot field is required on the wire, and MAY be synthesized: for an era-`2` run with nothing stored, the host MUST supply `2` from the absent-⇒-`2` rule rather than fail the read.
|
|
38
38
|
|
|
39
39
|
## The `eventLog` family
|
|
40
40
|
|
|
@@ -44,7 +44,7 @@ The snapshot field is required on the wire, and MAY be synthesized: for an era-`
|
|
|
44
44
|
- MUST serve the cursor contract of [events.md §"Poll"](events.md) over that log;
|
|
45
45
|
- MUST NOT emit an event `type` the codemap does not name.
|
|
46
46
|
|
|
47
|
-
The record
|
|
47
|
+
The record makes no separate storage claim. Its `crossEngineOrdering` facet is a replay property, specified in [replay.md §"Cross-engine ordering"](replay.md).
|
|
48
48
|
|
|
49
49
|
## The reader rule
|
|
50
50
|
|
|
@@ -58,16 +58,16 @@ A v2 host reading a run in era `2` MUST translate every event through the codema
|
|
|
58
58
|
|
|
59
59
|
The rule binds every reader: poll, SSE, fork, replay divergence, debug bundle, summary memory.
|
|
60
60
|
|
|
61
|
-
The translation is a read projection. A host MUST NOT rewrite era-`2` rows in place. The one exception is a background backfill that stamps `3` and rewrites `type` under the same `(runId, sequence)` key: it is permitted only as an atomic per-run operation with the original preserved,
|
|
61
|
+
The translation is a read projection. A host MUST NOT rewrite era-`2` rows in place. The one exception is a background backfill that stamps `3` and rewrites `type` under the same `(runId, sequence)` key: it is permitted only as an atomic per-run operation with the original preserved, so a fork prefix stays byte-equivalent ([replay.md](replay.md)).
|
|
62
62
|
|
|
63
63
|
### The writer rule
|
|
64
64
|
|
|
65
65
|
The era key is fixed when the run is created and fixes the log's vocabulary for the run's lifetime.
|
|
66
66
|
|
|
67
67
|
- An append to a run in era `2` MUST use v1 vocabulary — the name the codemap maps *from*, not the v2 name it maps to.
|
|
68
|
-
- A host that upgrades mid-flight MUST NOT begin writing v2 names into a log the reader translates as era `2`.
|
|
68
|
+
- A host that upgrades mid-flight MUST NOT begin writing v2 names into a log the reader translates as era `2`.
|
|
69
69
|
- A run created after the upgrade is era `3` and is written in v2 vocabulary, untranslated.
|
|
70
|
-
- A writer that emits a property a closed def cannot seat MUST mark the row with what it could not seat, so the refusal names the writer
|
|
70
|
+
- A writer that emits a property a closed def cannot seat MUST mark the row with what it could not seat, so the refusal names the writer.
|
|
71
71
|
|
|
72
72
|
This binds every writer for as long as an era-`2` run stays open (§"Runs pinned to v1"). Its witness is `v2-era-2-append-vocabulary`.
|
|
73
73
|
|
|
@@ -76,7 +76,7 @@ This binds every writer for as long as an era-`2` run stays open (§"Runs pinned
|
|
|
76
76
|
Through the overlap ([versioning.md §5](versioning.md)), an era-`3` log, stored in v2 vocabulary, must still read on `/v1/…` exactly as before the cut. A host serving both majors:
|
|
77
77
|
|
|
78
78
|
- MUST map an era-`3` log's `type` back to its v1 spelling on the v1 read path, through the same codemap row, inverted.
|
|
79
|
-
- MUST verify at load that `spec/v2/event-codemap.json` is a bijection
|
|
79
|
+
- MUST verify at load that `spec/v2/event-codemap.json` is a bijection. If a row folds two v1 names onto one v2 name, the host MUST refuse to serve the v1 representation rather than guess a spelling.
|
|
80
80
|
- MUST emit a type with no codemap row (v2-only vocabulary, which has no v1 spelling) unchanged on the v1 read path, MUST NOT drop the row, and MUST NOT refuse the read for it.
|
|
81
81
|
|
|
82
82
|
### The seat
|
|
@@ -84,7 +84,7 @@ Through the overlap ([versioning.md §5](versioning.md)), an era-`3` log, stored
|
|
|
84
84
|
- The adapter MUST sit at the storage boundary every reader passes through — the storage interface's event-list method, not a wrapper some call sites bypass.
|
|
85
85
|
- A host leg MUST name its seat in its ADR.
|
|
86
86
|
|
|
87
|
-
The seat is a claims-check ([conformance.md §Witness class](conformance.md)): it is discharged by that disclosure and by audit, never by the wire. It binds every reader, including ones the suite has no name for
|
|
87
|
+
The seat is a claims-check ([conformance.md §Witness class](conformance.md)): it is discharged by that disclosure and by audit, never by the wire. It binds every reader, including ones the suite has no name for.
|
|
88
88
|
|
|
89
89
|
## Runs pinned to v1
|
|
90
90
|
|
|
@@ -100,9 +100,9 @@ Multi-region skew is read-side only: after the cut, a v2 region MUST NOT accept
|
|
|
100
100
|
|
|
101
101
|
- **Certification bundles** — never upgraded. A v1 bundle substantiates no new certification after 2026-11-10; every host produces a fresh v2-rc bundle before the cut ([conformance.md](conformance.md)).
|
|
102
102
|
- **Webhook deliveries** — dual-emitted, and queued deliveries drained, per [webhooks.md §"Dual emission through the overlap"](webhooks.md).
|
|
103
|
-
- **Interrupt resume tokens** — drained
|
|
103
|
+
- **Interrupt resume tokens** — drained, per [identity.md](identity.md) §4.
|
|
104
104
|
- **Layer-1 and Layer-2 records** (idempotency, idempotent responses, invocation claims and logs, effect-escape ledger, dispatch outbox, envelope correlations) — unchanged; keyed on ids the cut does not rename. `GET /runs/{runId}/effects` and `GET /runs/{runId}/compensation` are new reads over them ([security-defaults.md](security-defaults.md)).
|
|
105
|
-
- **Owner stamps** — a run without a Subject
|
|
105
|
+
- **Owner stamps** — a run without a Subject is legacy-stamped per [identity.md](identity.md) §1.2. A host's stored owner fields are projected to the Subject; the projection is the host's to name.
|
|
106
106
|
- **Audit log** — never upgraded.
|
|
107
107
|
|
|
108
108
|
## Per-store disposition
|
|
@@ -141,6 +141,4 @@ Template — one row per store:
|
|
|
141
141
|
|
|
142
142
|
A host MAY claim a qualification rung (`durable-single-instance`, `durable-multi-instance`, `multi-region-qualified`; cumulative) only with the evidence [RFC 0158 §D](https://github.com/openwop/openwop/blob/main/RFCS/0158-durable-execution-and-disaster-recovery-qualification.md) names for it, and MUST NOT claim one from tests in which no process was terminated. A rung is evidence, not a capability: discovery carries none, and the certification bundle publishes the rung and its bounds ([conformance.md §Bundle v3](conformance.md)).
|
|
143
143
|
|
|
144
|
-
See also: [overview.md](overview.md), [events.md](events.md), [replay.md](replay.md), [identity.md](identity.md), [webhooks.md](webhooks.md).
|
|
145
|
-
|
|
146
144
|
*Sources: RFCs 0041, 0158, 0170, 0171, 0172, 0176, 0180, 0185, 0186, 0187.*
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
A tenant's reusable estate moves between hosts as one export bundle, and lands on the destination under the importing caller's identity.
|
|
8
|
+
A tenant's reusable estate moves between hosts as one export bundle, and lands on the destination under the importing caller's identity. An adapter that turns another platform's export into a bundle is host tooling.
|
|
9
9
|
|
|
10
10
|
## The export bundle
|
|
11
11
|
|
|
@@ -24,8 +24,8 @@ An `ExportBundle` (`schemas/v2/export-bundle.schema.json`) carries `bundleVersio
|
|
|
24
24
|
|
|
25
25
|
## Import rules
|
|
26
26
|
|
|
27
|
-
- **No credential material.** A bundle MUST NOT contain credential values: a `connection-ref` item carries only references and provider ids.
|
|
28
|
-
- **Dry run.** When `import` is advertised, import MUST offer a dry run
|
|
27
|
+
- **No credential material.** A bundle MUST NOT contain credential values: a `connection-ref` item carries only references and provider ids. A host MUST reject with `422` an imported bundle whose payload carries a literal credential value. The importer MUST report unbound references in `secretsToRebind` and MUST NOT invent or transfer secret material.
|
|
28
|
+
- **Dry run.** When `import` is advertised, import MUST offer a dry run, which MUST NOT write and MUST return the plan it would execute: creates, updates, skips, conflicts and unbound credential references.
|
|
29
29
|
- **Idempotent.** Import MUST be idempotent: re-applying a bundle resolves each item to `skipped` or `updated`, never to a duplicate create.
|
|
30
30
|
- **Ordered.** Items MUST be applied in `dependsOn` topological order. A cycle is a `422`.
|
|
31
31
|
- **Re-owned.** Every imported entity MUST be re-owned to the caller's Subject at the destination ([identity.md](identity.md) §1). `source.originPrincipal` is informational and MUST NOT grant any access. Host-scoped handles are re-minted as [identity.md](identity.md) §5 requires.
|
|
@@ -39,6 +39,6 @@ An applied import emits `import.applied` (`schemas/v2/run-event-payloads.schema.
|
|
|
39
39
|
|
|
40
40
|
## Routes
|
|
41
41
|
|
|
42
|
-
No protocol path is defined for export or import: a host serves them on routes of its own. A host-private migration from an anonymous sandbox into a signed-in tenant fits this contract
|
|
42
|
+
No protocol path is defined for export or import: a host serves them on routes of its own. A host-private migration from an anonymous sandbox into a signed-in tenant fits this contract.
|
|
43
43
|
|
|
44
44
|
*Sources: RFCs 0043, 0096, 0098.*
|
package/spec/v2/core/replay.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
## The surface
|
|
11
11
|
|
|
12
|
-
A host
|
|
12
|
+
A host advertising `replay` ([capabilities.md](capabilities.md)) serves `forkRun` (`api/v2/openapi.yaml`, `POST /runs/{runId}:fork`) and `getEffectSeamManifest` (`GET /host/effect-seams`).
|
|
13
13
|
|
|
14
14
|
The `replay` facet (`spec/v2/facets/replay.schema.json`) is `{ modes[], retention?, effectSeamsManifest }`:
|
|
15
15
|
|
|
@@ -80,11 +80,11 @@ For a fork with `mode: replay`:
|
|
|
80
80
|
5. The guarantee is whole-run and requires both classification before execution and a default-deny guard at every effect seam.
|
|
81
81
|
6. A dispatch to a peer host is an outbound call under rule 2; the peer is never contacted.
|
|
82
82
|
|
|
83
|
-
Pure nodes and LLM calls served from the invocation log MUST re-execute live
|
|
83
|
+
Pure nodes and LLM calls served from the invocation log MUST re-execute live.
|
|
84
84
|
|
|
85
85
|
**Fan-out.** A host that projects its log outward — webhook delivery, A2A push, outbound streams, analytics or audit sinks — MUST NOT deliver events a replay re-emits as fixed history, and a fork of either mode MUST NOT inherit its source's A2A push configs. Replay-ness MUST be read from the run, never from the event type; the fork's own log MUST still carry the re-emitted events ([webhooks.md](webhooks.md)).
|
|
86
86
|
|
|
87
|
-
**Branch.** A branch re-fires effects for sequences `>= fromSeq
|
|
87
|
+
**Branch.** A branch re-fires effects for sequences `>= fromSeq`. A host MAY suppress branch effects and MUST NOT report that as replay suppression. A host SHOULD surface the re-fire in operator-facing fork UI.
|
|
88
88
|
|
|
89
89
|
### The effect-seam manifest
|
|
90
90
|
|
|
@@ -95,7 +95,7 @@ A host advertising `replay` MUST publish `schemas/v2/effect-seam-manifest.schema
|
|
|
95
95
|
- Two seams a host guards through one code path but that leave by different mechanisms are different `kind`s; two that leave by the same mechanism for different business reasons are one.
|
|
96
96
|
- `other` is the escape for a mechanism this list does not name — raw TCP, gRPC, a filesystem write, a device SDK. A row using it MUST carry `note` naming that mechanism.
|
|
97
97
|
|
|
98
|
-
**Completeness outranks driveability.** Every outbound effect path the node runtime can reach MUST be listed, including one the suite cannot drive (typically `smtp` or `other`); the scenario records that one `inapplicable`, naming the mechanism. A host MUST NOT omit a seam because the suite cannot drive it, and MUST NOT relabel it as a `kind` the suite can drive.
|
|
98
|
+
**Completeness outranks driveability.** Every outbound effect path the node runtime can reach MUST be listed, including one the suite cannot drive (typically `smtp` or `other`); the scenario records that one `inapplicable`, naming the mechanism. A host MUST NOT omit a seam because the suite cannot drive it, and MUST NOT relabel it as a `kind` the suite can drive.
|
|
99
99
|
|
|
100
100
|
## Replay-from-event-log internals
|
|
101
101
|
|
|
@@ -111,7 +111,7 @@ A v2 host MUST fork a run created before the cut (era `2`, [persistence.md](pers
|
|
|
111
111
|
|
|
112
112
|
- The fork's prefix MUST be byte-equivalent to the *translated* parent — the parent as read through the codemap, not its stored bytes.
|
|
113
113
|
- `run.started` on the fork MUST carry the legacy Subject (`issuer: urn:openwop:legacy`, [identity.md](identity.md)) where the parent had none.
|
|
114
|
-
- A backfill of an era-`2` log is permitted only atomically per run with the original preserved
|
|
114
|
+
- A backfill of an era-`2` log is permitted only atomically per run with the original preserved.
|
|
115
115
|
|
|
116
116
|
## Cross-engine ordering
|
|
117
117
|
|
|
@@ -120,16 +120,14 @@ When a host advertises both `idempotency.multiRegion` and `eventLog.crossEngineO
|
|
|
120
120
|
- `status`, `variables`, and the projected event log up to `fromSeq` MUST be byte-equivalent across regions.
|
|
121
121
|
- Per-region wall-clock and entropy fields in events after the boundary MAY differ.
|
|
122
122
|
|
|
123
|
-
A host
|
|
123
|
+
A host advertising only one of the two, or neither, keeps the single-region contract.
|
|
124
124
|
|
|
125
125
|
## Retention
|
|
126
126
|
|
|
127
|
-
A host advertising `replay` MUST document retention for source snapshots, source logs, the invocation records replay depends on, and forked runs; `retention.days` MAY advertise the window. When the range `fromSeq` needs has expired, the host MUST reject the fork with `410` or `422`; `details` SHOULD carry `sourceRunId`, `fromSeq`, and the boundary.
|
|
127
|
+
A host advertising `replay` MUST document retention for source snapshots, source logs, the invocation records replay depends on, and forked runs; `retention.days` MAY advertise the window. When the range `fromSeq` needs has expired, the host MUST reject the fork with `410 run_expired` or `422`; `details` SHOULD carry `sourceRunId`, `fromSeq`, and the boundary.
|
|
128
128
|
|
|
129
129
|
## Declared nondeterminism
|
|
130
130
|
|
|
131
131
|
`nondeterminismPolicy` is the host's statement of which nondeterministic sources it declares rather than suppresses. A host advertising `nondeterminismPolicy.declared` MUST record every declared source in the run's event log at the point it is read, so a fork replays the recorded value rather than re-drawing it. A source the host neither declares nor suppresses is a replay defect, not a policy choice.
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
*Sources: RFCs 0036, 0039, 0041, 0057, 0104, 0111, 0140, 0173, 0176, 0194.*
|
|
133
|
+
*Sources: RFCs 0036, 0039, 0041, 0057, 0104, 0111, 0140, 0173, 0176, 0194, 0228.*
|
package/spec/v2/core/runs.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Runs
|
|
2
2
|
|
|
3
3
|
> **Status: Stable.**
|
|
4
|
-
> **Normative home:** `runList`, `limits`, `conversationPrimitive`, `dataResidency`, `deadLetter`.
|
|
4
|
+
> **Normative home:** `runList`, `limits`, `conversationPrimitive`, `dataResidency`, `deadLetter`, `budget`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
@@ -38,7 +38,7 @@ Every operation accepts `OpenWOP-Version` ([overview.md](overview.md)) and every
|
|
|
38
38
|
| `getRunCompensation` | `GET /runs/{runId}/compensation` | `runs:read` | `compensation` |
|
|
39
39
|
| `getRunEffects` | `GET /runs/{runId}/effects` | `runs:read` | `idempotency` |
|
|
40
40
|
|
|
41
|
-
A gated operation the host does not advertise (or an absent `diffRun`) answers `404
|
|
41
|
+
A gated operation the host does not advertise (or an absent `diffRun`) answers `404 not_found` ([errors.md](errors.md)).
|
|
42
42
|
|
|
43
43
|
## Create
|
|
44
44
|
|
|
@@ -67,7 +67,7 @@ The `201` response is `{ runId, status, eventsUrl, statusUrl? }`. `status` is on
|
|
|
67
67
|
|
|
68
68
|
### Refusals
|
|
69
69
|
|
|
70
|
-
- `mode: eval` makes `evalSuiteRef` and `agentId` REQUIRED. It starts an eval-suite projection that emits the content-free `eval.*` family and terminates with an `EvalSummary`. A host that does not advertise `agents.evalSuite` MUST reject it
|
|
70
|
+
- `mode: eval` makes `evalSuiteRef` and `agentId` REQUIRED. It starts an eval-suite projection that emits the content-free `eval.*` family and terminates with an `EvalSummary`. A host that does not advertise `agents.evalSuite` MUST reject it `422 capability_not_provided`.
|
|
71
71
|
- A host advertising `dataResidency` MUST reject a `residency.region` outside `dataResidency.regions` with `422 residency_unavailable` and create no run (see §"Conversation and residency capabilities").
|
|
72
72
|
- A workflow that references a capability-gated reserved node type on a host that does not advertise the capability MUST be rejected with `422 capability_required`.
|
|
73
73
|
|
|
@@ -99,9 +99,12 @@ The `201` response is `{ runId, status, eventsUrl, statusUrl? }`. `status` is on
|
|
|
99
99
|
|
|
100
100
|
### Limits
|
|
101
101
|
|
|
102
|
-
`limits` always carries `clarificationRounds` (per task), `schemaRounds` (per envelope) and `envelopesPerTurn` (per chat turn).
|
|
102
|
+
`limits` always carries `clarificationRounds` (per task), `schemaRounds` (per envelope) and `envelopesPerTurn` (per chat turn).
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
- A host advertising `maxNodeExecutions`, `maxRunDurationMs` or `maxLoopIterations` MUST enforce it.
|
|
105
|
+
- On any breach the host MUST emit `cap.breached` and fail the node or run. The event carries `nodeId` for a `clarification` or `schema` breach. Its `observed` exceeds `limit` and MUST be reused on replay and fork, never recomputed.
|
|
106
|
+
- `budget.maxTokens` and `budget.maxCostUsd` clamp to `maxBudgetTokens` and `maxBudgetCostUsd`.
|
|
107
|
+
- `maxRequestBodyBytes` is the largest REST request body accepted.
|
|
105
108
|
|
|
106
109
|
### `ai` section
|
|
107
110
|
|
|
@@ -114,6 +117,16 @@ The `201` response is `{ runId, status, eventsUrl, statusUrl? }`. `status` is on
|
|
|
114
117
|
|
|
115
118
|
`tokenBudget` resolves to `min(tokenBudget, memory.distillation.maxTokenBudget)`. A run that cannot distill within it MUST fail atomically with `token_budget_exceeded`.
|
|
116
119
|
|
|
120
|
+
### `budget` section
|
|
121
|
+
|
|
122
|
+
`budget` caps a run's spend. The effective budget is the minimum across the `scopes` that apply (run, workflow, agent, project), clamped per §Limits; only the run scope has a wire surface. Whichever of `budget` and the `run` section binds first fires its own `cap.breached` kind.
|
|
123
|
+
|
|
124
|
+
- `budget.reserved` records the effective budget, and consumption is derived from `provider.usage`, `agent.toolCalled` and `node.retried`, never measured twice. A replay reuses both.
|
|
125
|
+
- Under either `enforce` mode a host MUST emit `budget.reserved`, `budget.threshold-crossed` and `budget.exhausted`, and MAY coalesce `budget.consumed`.
|
|
126
|
+
- `hard` exhaustion under `onExhaustion: fail` emits `cap.breached` (`kind: budget-*`) and fails the run `budget_exhausted`. Under `interrupt` it raises an approval whose `resumeValue` adds budget, recorded by a second `budget.reserved`. An `advisory` host MUST NOT stop the run.
|
|
127
|
+
- A resolved model outside `modelAllow`, or in `modelDeny` (which wins), is refused `budget_model_denied` before the call.
|
|
128
|
+
- `dimensions` lists only what the host enforces and MAY omit `cost`. The `budget.*` events and `cap.breached` MUST NOT carry rate cards, unit prices, cost breakdowns, credentials or model prose (`budget-no-pricing-leak`); the aggregate cost is allowed.
|
|
129
|
+
|
|
117
130
|
### Validation and persistence
|
|
118
131
|
|
|
119
132
|
- An unknown root key, an unknown key inside a section, or a dotted key (`ai.provider` as a string key) MUST be rejected with `400 validation_error`.
|
|
@@ -277,10 +290,10 @@ A host advertising `deadLetter` MUST, when a run or node exhausts its retry poli
|
|
|
277
290
|
- keep the failed run fork-eligible for `retentionDays`, and not purge it sooner;
|
|
278
291
|
- purge it after `retentionDays`, after which a fork fails as for an unknown run.
|
|
279
292
|
|
|
280
|
-
|
|
293
|
+
Queue messages ([host-services.md](host-services.md) §`queueBus`) and webhook deliveries ([webhooks.md](webhooks.md) §Dead letters) have sinks of their own.
|
|
281
294
|
|
|
282
295
|
## During the v1 overlap
|
|
283
296
|
|
|
284
297
|
A non-terminal run inherited from v1 continues, or is cancelled `v1_pin_unsupported`, per [persistence.md](persistence.md) §"Runs pinned to v1".
|
|
285
298
|
|
|
286
|
-
*Sources: RFCs 0053, 0058, 0084, 0170, 0171, 0176, 0182.*
|
|
299
|
+
*Sources: RFCs 0053, 0058, 0084, 0170, 0171, 0176, 0182, 0228.*
|
|
@@ -140,6 +140,4 @@ A threat model missing a sibling section fails the template gate.
|
|
|
140
140
|
|
|
141
141
|
Rows `C6.1`–`C6.9` are `spec/v1/migrations.json` entries.
|
|
142
142
|
|
|
143
|
-
See also: identity.md, replay.md, webhooks.md, capabilities.md, conformance.md.
|
|
144
|
-
|
|
145
143
|
*Sources: RFCs 0150, 0163, 0164, 0170, 0173, 0214, 0218, 0224.*
|
package/spec/v2/core/storage.md
CHANGED
|
@@ -5,37 +5,35 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
A node pack keeps files, records and indexes in storage services the host provides, reached through `ctx` ([host-services.md](host-services.md)).
|
|
8
|
+
A node pack keeps files, records and indexes in storage services the host provides, reached through `ctx` ([host-services.md](host-services.md)). Each family below is a contract a host takes on by advertising it.
|
|
9
9
|
|
|
10
10
|
## Operations
|
|
11
11
|
|
|
12
|
-
A host advertising a family MUST expose each of its operations to pack code, returning at least the fields shown; it MAY return more. An operation marked † is required only when
|
|
12
|
+
A host advertising a family MUST expose each of its operations to pack code, returning at least the fields shown; it MAY return more. An operation marked † is required only when its facet is `true`: `kvStorage.atomicIncrement`, `kvStorage.compareAndSwap`, `sql.transactions` or `blobStorage.presignSupported`.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
| `blobStorage` (`ctx.storage.blob`) | `put(bucket, key, bytes, contentType?) → url, sizeBytes` · `get(bucket, key) → bytes, contentType?` · `delete(bucket, key) → deleted` · `list(bucket, prefix?, cursor?) → entries[key, sizeBytes], nextCursor?` · † `presign(bucket, key, expiresInSeconds, method) → url, expiresAt` |
|
|
24
|
-
| `cache` (`ctx.storage.cache`) | `get(key) → value?, expiresAt?` · `put(key, value, ttlSeconds) → ok` · `delete(key) → deleted` |
|
|
14
|
+
- **`fs`** (`ctx.fs`): `read(path) → bytes, contentType?` · `write(path, bytes, contentType?) → path, sizeBytes` · `delete(path) → deleted` · `stat(path) → sizeBytes, modifiedAt, contentType?` · `list(prefix?, cursor?, limit?) → entries[path, sizeBytes], nextCursor?`
|
|
15
|
+
- **`kvStorage`** (`ctx.storage.kv`): `get(key) → value?, expiresAt?` · `put(key, value, ttlSeconds?) → ok` · `delete(key) → deleted` · `list(prefix?, cursor?, limit?) → entries[key], nextCursor?` · † `atomicIncrement(key, delta?) → value` · † `compareAndSwap(key, expectedValue, newValue) → swapped`
|
|
16
|
+
- **`tableStorage`** (`ctx.storage.table`): `createTable(name, schema) → ok` · `insert(table, row) → rowId` · `get(table, rowId) → row?` · `query(table, filter?, cursor?, limit?) → rows, nextCursor?` · `update(table, rowId, patch) → ok` · `delete(table, rowId) → deleted`
|
|
17
|
+
- **`sql`** (`ctx.db.sql`): `query(datasourceId, sql, params) → rows, rowCount` · `execute(datasourceId, sql, params) → rowsAffected` · † `transaction(datasourceId, operations[sql, params]) → committed`
|
|
18
|
+
- **`nosql`** (`ctx.db.nosql`): `insert(datasourceId, collection, doc) → id` · `get(…, id) → doc?` · `query(…, filter, cursor?, limit?) → docs, nextCursor?` · `update(…, id, patch) → ok` · `delete(…, id) → deleted`
|
|
19
|
+
- **`vectorStore`** (`ctx.db.vector`): `upsert(collection, vectors[id, embedding, metadata?]) → upserted` · `query(collection, embedding, k, filter?) → matches[id, score, metadata?]` · `delete(collection, ids) → deleted`
|
|
20
|
+
- **`searchIndex`** (`ctx.db.search`): `index(index, docs[id, fields]) → indexed` · `query(index, q, k?, filter?) → hits[id, score, fields?]` · `delete(index, ids) → deleted`
|
|
21
|
+
- **`blobStorage`** (`ctx.storage.blob`): `put(bucket, key, bytes, contentType?) → url, sizeBytes` · `get(bucket, key) → bytes, contentType?` · `delete(bucket, key) → deleted` · `list(bucket, prefix?, cursor?) → entries[key, sizeBytes], nextCursor?` · † `presign(bucket, key, expiresInSeconds, method) → url, expiresAt`
|
|
22
|
+
- **`cache`** (`ctx.storage.cache`): `get(key) → value?, expiresAt?` · `put(key, value, ttlSeconds) → ok` · `delete(key) → deleted`
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
`presign` takes `method` `GET` or `PUT`, and `…` in a `nosql` operation repeats `datasourceId, collection`.
|
|
27
25
|
|
|
28
26
|
## Shared rules
|
|
29
27
|
|
|
30
28
|
- **Tenant isolation.** A read for one tenant MUST NOT return data another tenant wrote, even under an identical key or name: `kvStorage` `get` and `list`, `tableStorage` `get` and `query`, `vectorStore` and `searchIndex` `query`, `blobStorage` and `cache` `get`.
|
|
31
|
-
- **Datasources.** `sql` and `nosql` datasources are scoped per tenant
|
|
29
|
+
- **Datasources.** `sql` and `nosql` datasources are scoped per tenant; access to another tenant's datasource MUST be refused.
|
|
32
30
|
- **Backend-invariant.** The operation shapes of `sql` and `nosql` MUST NOT vary with the advertised `drivers`, nor those of `vectorStore` and `searchIndex` with the advertised `backends`. A host MAY back `sql` with any driver it advertises.
|
|
33
31
|
- **Size limits.** A key over `kvStorage.maxKeyBytes`, and a write over `fs.maxFileSizeBytes`, `kvStorage.maxValueBytes`, `blobStorage.maxObjectBytes` or `cache.maxValueBytes`, MUST be refused. A `tableStorage` insert MUST be refused once `maxRowsPerTable` is reached.
|
|
34
32
|
- **Expiry.** `kvStorage` and `cache` MUST honour an entry's expiry, as a read sees it, with at most one second of drift. `maxTtlSeconds` caps `ttlSeconds` for each.
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
A family's advertisement also names its targets: `sql.datasources`, `nosql.datasources`, `vectorStore.collections`, `searchIndex.indexes` and `blobStorage.buckets`. `tableStorage` advertises `maxColumnsPerRow`, `indexable` and `fullTextSearch` as limits and features, with no further rule.
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
A refused call carries `not_found`, `forbidden`, `validation_error`, or `storage_limit_exceeded` for a limit or quota ([errors.md](errors.md) §Host-service refusals). A sandbox escape is `forbidden` with `details.reason: path-outside-sandbox`.
|
|
39
37
|
|
|
40
38
|
## `fs`
|
|
41
39
|
|
|
@@ -57,8 +55,8 @@ No error code specific to these families is registered; [errors.md](errors.md) g
|
|
|
57
55
|
|
|
58
56
|
## `sql` and `nosql`
|
|
59
57
|
|
|
60
|
-
- `sql` MUST be treated as a parametric template: bound values MUST flow through `params`, never through string interpolation. A
|
|
61
|
-
- When `sql.transactions` is `true`, a partial failure inside `transaction` MUST roll back the whole batch
|
|
58
|
+
- `sql` MUST be treated as a parametric template: bound values MUST flow through `params`, never through string interpolation. A pack MUST NOT concatenate user input into `sql`, and a host SHOULD verify parameter binding before execution.
|
|
59
|
+
- When `sql.transactions` is `true`, a partial failure inside `transaction` MUST roll back the whole batch, which resolves `committed: false`.
|
|
62
60
|
- `nosql` filter operators MUST NOT permit injection. Server-side script evaluation, such as MongoDB `$where`, MUST be refused unless an explicit allowlist is configured.
|
|
63
61
|
|
|
64
62
|
## `vectorStore` and `searchIndex`
|
|
@@ -70,4 +68,4 @@ No error code specific to these families is registered; [errors.md](errors.md) g
|
|
|
70
68
|
|
|
71
69
|
Presigned URLs MUST expire at the advertised TTL. A presigned request after expiry MUST fail at the storage layer, not after an authorization skip.
|
|
72
70
|
|
|
73
|
-
*Sources: RFCs 0014, 0015, 0016, 0018, 0019.*
|
|
71
|
+
*Sources: RFCs 0014, 0015, 0016, 0018, 0019, 0228.*
|
|
@@ -13,11 +13,11 @@ How a v2 host selects a major, what each version axis means, and what a release
|
|
|
13
13
|
A v2 host MUST advertise two root fields, both REQUIRED in `schemas/v2/capabilities.schema.json` ([capabilities.md](capabilities.md)):
|
|
14
14
|
|
|
15
15
|
- `protocolVersions[]` — every `<major>.<minor>` it serves, each matching `^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$`. Through the overlap that is `["1.<n>", "2.<m>"]`; after v1 end-of-support, `["2.<m>"]`.
|
|
16
|
-
- `preferredVersion` — MUST be a member of `protocolVersions[]`. Through the overlap it MUST name a 1.x member
|
|
16
|
+
- `preferredVersion` — MUST be a member of `protocolVersions[]`. Through the overlap it MUST name a 1.x member ([capabilities.md](capabilities.md) §1; §1.3). On a host serving a single major, it MUST equal `protocolVersion`.
|
|
17
17
|
|
|
18
18
|
A host that drops v1 advertises a `2.x` `preferredVersion`; its header-less representation becomes the closed v2 root.
|
|
19
19
|
|
|
20
|
-
A v2 consumer reads `preferredVersion` as the header-less default. When it is absent on a v1 document, the default is `max(protocolVersions[])`, else `protocolVersion`.
|
|
20
|
+
A v2 consumer reads `preferredVersion` as the header-less default. When it is absent on a v1 document, the default is `max(protocolVersions[])`, else `protocolVersion`.
|
|
21
21
|
|
|
22
22
|
### 1.2 Paths
|
|
23
23
|
|
|
@@ -47,8 +47,6 @@ A request on an unversioned path MAY carry `OpenWOP-Version: <major>` or `OpenWO
|
|
|
47
47
|
| Header absent on any other unversioned path | MUST serve major 2: the path is the v2 surface (§1.2) |
|
|
48
48
|
| `/v1/…` path with `OpenWOP-Version` other than `1` | MUST answer `400` `protocol_version_mismatch` |
|
|
49
49
|
|
|
50
|
-
`/.well-known/openwop` is the one resource both majors serve at the same unversioned path, so it is the only place a header-less request can come from a client of the earlier major.
|
|
51
|
-
|
|
52
50
|
A request on a `/v1/…` path key MUST NOT carry `OpenWOP-Version` with a value other than `1`. `protocol_version_unsupported`, `protocol_version_mismatch` and `client_version_unsupported` (§1.5) are rows in `spec/v2/errors.json` ([errors.md](errors.md)).
|
|
53
51
|
|
|
54
52
|
### 1.4 The response header
|
|
@@ -131,7 +129,6 @@ Dispositions:
|
|
|
131
129
|
|
|
132
130
|
- One grammar covers protocol, envelope-kind and pack axes wherever a version is `<major>.<minor>` (#1, #2, #11, #15).
|
|
133
131
|
- `typeId@<semver>` is a pack axis (`packs.md`); the `2` in `typeId@2.0.0` never means `OpenWOP-Version: 2`.
|
|
134
|
-
- `docs/PROTOCOL-STATUS.md` carries one row per axis.
|
|
135
132
|
|
|
136
133
|
### 2.1 `engineVersion` (axis 3)
|
|
137
134
|
|
|
@@ -141,7 +138,7 @@ Dispositions:
|
|
|
141
138
|
|
|
142
139
|
### 2.2 `eventLogSchemaVersion` (axis 4)
|
|
143
140
|
|
|
144
|
-
`eventLogSchemaVersion` is the era key; the schema floor is `minimum 2`. Its stamping, absent-⇒-`2
|
|
141
|
+
`eventLogSchemaVersion` is the era key; the schema floor is `minimum 2`. Its stamping, absent-⇒-`2`, discovery and reader rules are in [persistence.md](persistence.md) §"The era key".
|
|
145
142
|
|
|
146
143
|
## 3. Where v2 lives
|
|
147
144
|
|
|
@@ -168,15 +165,13 @@ Through the overlap a host:
|
|
|
168
165
|
- MUST emit `OpenWOP-Version` on every response (§1.4);
|
|
169
166
|
- MUST serve `/.well-known/openwop` as one resource whose representation the request header selects ([capabilities.md](capabilities.md)).
|
|
170
167
|
|
|
171
|
-
The dual-stack scenario creates one run through `/v1/runs` with no header and reads it through `/runs` with `OpenWOP-Version: 2`; the response headers name the contract used.
|
|
172
|
-
|
|
173
168
|
A run minted under major 1 and read under major 2 MUST use the tenant-bound projection `<tenantId>/<v1-id>` ([identity.md](identity.md) §5). A host MUST NOT return a bare v1 id in a major-2 response.
|
|
174
169
|
|
|
175
170
|
### Retirement
|
|
176
171
|
|
|
177
172
|
The overlap ends at v1 end-of-support ([overview.md](overview.md)): `protocolVersions[]` drops the `1.<n>` member, and every alias carrying the `v1-end-of-support` trigger is removed.
|
|
178
173
|
|
|
179
|
-
- **Retirement is atomic.**
|
|
174
|
+
- **Retirement is atomic.** Dropping v1 retires the whole `/v1` path space at once (§1.1).
|
|
180
175
|
- **Retirement changes every header-less request's default contract**, from major 1 to major 2. Before retirement, a host MUST check for collisions between manifest top-level path segments and non-protocol unversioned routes, and MUST move each colliding route or apply §1.4 content negotiation.
|
|
181
176
|
|
|
182
177
|
### Host-proprietary paths: `/host/<org>/…`
|