@openwop/spec-artifacts 2.42.6 → 2.42.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CORPUS-STAMP.json +70 -67
- package/api/seams-v2.yaml +238 -322
- package/api/v2/asyncapi.yaml +83 -113
- package/api/v2/openapi.yaml +408 -724
- package/package.json +1 -1
- package/schemas/audit-verify-result.schema.json +2 -2
- package/schemas/v2/audit-verify-result.schema.json +2 -2
- package/schemas/v2/capabilities.schema.json +4 -7
- package/schemas/v2/error-envelope.schema.json +144 -69
- package/spec/v1/alias-detectors.json +6 -0
- package/spec/v1/core-standard-manifest.json +2 -2
- package/spec/v1/deprecations.json +22 -0
- package/spec/v1/gaps.json +188 -1
- package/spec/v1/migrations.json +18 -0
- package/spec/v2/README.md +35 -37
- package/spec/v2/core/capabilities.md +186 -119
- package/spec/v2/core/conformance.md +78 -27
- package/spec/v2/core/connection-packs.md +8 -3
- package/spec/v2/core/conversation.md +10 -4
- package/spec/v2/core/errors.md +30 -9
- package/spec/v2/core/events.md +116 -80
- package/spec/v2/core/form-content-packs.md +11 -13
- package/spec/v2/core/headers.md +23 -14
- package/spec/v2/core/host-services.md +22 -5
- package/spec/v2/core/i18n.md +64 -0
- package/spec/v2/core/idempotency.md +48 -36
- package/spec/v2/core/identity.md +110 -42
- package/spec/v2/core/interop.md +82 -21
- package/spec/v2/core/interrupt.md +79 -40
- package/spec/v2/core/node-pack-runtimes.md +18 -4
- package/spec/v2/core/oauth.md +40 -6
- package/spec/v2/core/overview.md +61 -33
- package/spec/v2/core/packs.md +53 -28
- package/spec/v2/core/persistence.md +61 -80
- package/spec/v2/core/portability.md +44 -0
- package/spec/v2/core/replay.md +56 -42
- package/spec/v2/core/runs.md +198 -60
- package/spec/v2/core/security-defaults.md +57 -33
- package/spec/v2/core/tool-catalog.md +29 -6
- package/spec/v2/core/versioning.md +115 -50
- package/spec/v2/core/webhooks.md +61 -20
- package/spec/v2/core/workflow-chain-packs.md +16 -14
- package/spec/v2/corrections.json +17 -1
- package/spec/v2/corrections.schema.json +1 -1
- package/spec/v2/declaration.json +26 -9
- package/spec/v2/declaration.schema.json +10 -0
- package/spec/v2/errors.json +121 -4
- package/spec/v2/errors.schema.json +38 -0
- package/spec/v2/ext/README.md +36 -23
- package/spec/v2/ext/a2uiSurface/README.md +75 -89
- package/spec/v2/ext/brand/README.md +15 -15
- package/spec/v2/ext/canvas/README.md +15 -15
- package/spec/v2/ext/chat/README.md +15 -15
- package/spec/v2/ext/coordination/README.md +15 -15
- package/spec/v2/ext/dataIntegration/README.md +17 -20
- package/spec/v2/ext/entities/README.md +15 -15
- package/spec/v2/ext/grpc-transport/README.md +6 -8
- package/spec/v2/ext/kanban/README.md +15 -15
- package/spec/v2/ext/knowledge/README.md +15 -15
- package/spec/v2/ext/launchStudio/README.md +16 -16
- package/spec/v2/ext/messaging/README.md +15 -15
- package/spec/v2/ext/portability/README.md +3 -17
- package/spec/v2/ext/provider-idempotency/README.md +7 -10
- package/spec/v2/ext/restTransport/README.md +30 -19
- package/spec/v2/ext/sandbox-runtime-notes/README.md +7 -9
- package/spec/v2/ext/webResearch/README.md +16 -16
- package/spec/v2/profiles.json +6 -2
- package/spec/v2/release.json +2 -2
- package/spec/v2/surface-baseline.json +544 -2
package/spec/v2/core/runs.md
CHANGED
|
@@ -1,129 +1,267 @@
|
|
|
1
1
|
# Runs
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
> **Normative home:** `runList`, `limits`, `conversationPrimitive`, `dataResidency`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
A run is the unit of execution, ownership and observation. This document
|
|
8
|
+
A run is the unit of execution, ownership and observation. This document covers the run surface of `api/v2/openapi.yaml`: how a run is created, read, streamed, cancelled, paused, forked and diffed. Every host projects the same snapshot shape from the same event log ([events.md](events.md)).
|
|
9
9
|
|
|
10
10
|
## Identity
|
|
11
11
|
|
|
12
|
-
Every id `$ref`s `schemas/v2/ids.schema.json` (identity.md). A `runId` is tenant-bound
|
|
12
|
+
Every id `$ref`s `schemas/v2/ids.schema.json` ([identity.md](identity.md)). A `runId` is host-minted and tenant-bound: `<tenantId>/<opaque>`.
|
|
13
|
+
|
|
14
|
+
- A caller MUST treat every id as opaque.
|
|
15
|
+
- A host MUST reject a `runId` whose tenant segment is not the caller's with `403 id_tenant_mismatch`, and MUST NOT disclose whether the run exists.
|
|
13
16
|
|
|
14
17
|
## Surface
|
|
15
18
|
|
|
16
|
-
Every operation accepts `OpenWOP-Version` (overview.md)
|
|
19
|
+
Every operation accepts `OpenWOP-Version` ([overview.md](overview.md)) and every response carries it. Every mutating operation accepts `Idempotency-Key` ([idempotency.md](idempotency.md)). Scopes are the vocabulary listed in `api/v2/openapi.yaml`'s security schemes, matched as [identity.md](identity.md) §2.1 describes.
|
|
17
20
|
|
|
18
21
|
| Operation | Method and path | Scope | Gate |
|
|
19
22
|
| --- | --- | --- | --- |
|
|
20
23
|
| `createRun` | `POST /runs` | `runs:create` | — |
|
|
21
24
|
| `getRun` | `GET /runs/{runId}` | `runs:read` | — |
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
25
|
+
| `listRuns` | `GET /runs` | `runs:read` | `runList` |
|
|
26
|
+
| `streamRunEvents` | `GET /runs/{runId}/events` | `runs:read` | [events.md](events.md) |
|
|
27
|
+
| `pollRunEvents` | `GET /runs/{runId}/events/poll` | `runs:read` | [events.md](events.md) |
|
|
24
28
|
| `cancelRun` | `POST /runs/{runId}/cancel` | `runs:cancel` | — |
|
|
25
29
|
| `bulkCancelRuns` | `POST /runs:bulk-cancel` | `runs:cancel` | — |
|
|
26
30
|
| `pauseRun` | `POST /runs/{runId}:pause` | `runs:cancel` | — |
|
|
27
31
|
| `resumeRun` | `POST /runs/{runId}:resume` | `runs:cancel` | — |
|
|
28
|
-
| `forkRun` | `POST /runs/{runId}:fork` | `runs:create` + `runs:read` | `replay`
|
|
29
|
-
| `diffRun` | `GET /runs/{runId}:diff?against=` | `runs:read` on both | OPTIONAL
|
|
30
|
-
| `getRunAncestry` | `GET /runs/{runId}/ancestry` | `runs:read` | `multiAgent.executionModel.crossHostCausation.ancestryEndpointSupported
|
|
31
|
-
| `createAnnotation` / `listAnnotations` | `POST` / `GET /runs/{runId}/annotations` | `runs:annotate` / `runs:read` | `feedback
|
|
32
|
+
| `forkRun` | `POST /runs/{runId}:fork` | `runs:create` + `runs:read` | `replay` |
|
|
33
|
+
| `diffRun` | `GET /runs/{runId}:diff?against=` | `runs:read` on both | OPTIONAL |
|
|
34
|
+
| `getRunAncestry` | `GET /runs/{runId}/ancestry` | `runs:read` | `multiAgent.executionModel.crossHostCausation.ancestryEndpointSupported` |
|
|
35
|
+
| `createAnnotation` / `listAnnotations` | `POST` / `GET /runs/{runId}/annotations` | `runs:annotate` / `runs:read` | `feedback` |
|
|
32
36
|
| `getArtifact` | `GET /runs/{runId}/artifacts/{artifactId}` | `artifacts:read` | — |
|
|
33
|
-
| `getEvalSummary` | `GET /runs/{runId}/eval-summary` | `runs:read` | `agents.evalSuite
|
|
34
|
-
| `getRunCompensation` | `GET /runs/{runId}/compensation` | `runs:read` | `compensation`
|
|
35
|
-
| `getRunEffects` | `GET /runs/{runId}/effects` | `runs:read` | `idempotency`
|
|
36
|
-
|
|
37
|
+
| `getEvalSummary` | `GET /runs/{runId}/eval-summary` | `runs:read` | `agents.evalSuite` |
|
|
38
|
+
| `getRunCompensation` | `GET /runs/{runId}/compensation` | `runs:read` | `compensation` |
|
|
39
|
+
| `getRunEffects` | `GET /runs/{runId}/effects` | `runs:read` | `idempotency` |
|
|
40
|
+
|
|
41
|
+
A gated operation the host does not advertise (or an absent `diffRun`) answers `404`; for annotations it is `404 not_found`.
|
|
37
42
|
|
|
38
43
|
## Create
|
|
39
44
|
|
|
40
|
-
The `createRun` body is closed
|
|
45
|
+
The `createRun` body is closed (`unevaluatedProperties: false`). Its fields:
|
|
46
|
+
|
|
47
|
+
- `workflowId` — REQUIRED unless `mode: eval`.
|
|
48
|
+
- `inputs`, `residency`, `tenantId`, `scopeId`.
|
|
49
|
+
- `callbackUrl` — see [interrupt.md](interrupt.md) §Callback delivery. A refused value is `400 validation_error` with `details.field: "callbackUrl"`.
|
|
50
|
+
- `mode`, `evalSuiteRef`, `agentId`.
|
|
51
|
+
- The `RunOptions` fields `configurable`, `tags`, `metadata`. A body without `RunOptions` MUST be accepted as if it were `{}`.
|
|
52
|
+
|
|
53
|
+
### Request headers
|
|
41
54
|
|
|
42
55
|
| Header | Rule |
|
|
43
56
|
| --- | --- |
|
|
44
|
-
| `Idempotency-Key` | RECOMMENDED
|
|
57
|
+
| `Idempotency-Key` | RECOMMENDED. A replayed create MUST NOT create a second run, and carries `OpenWOP-Idempotent-Replay: true`. |
|
|
45
58
|
| `OpenWOP-Dedup: enforce` | The host MUST reject a duplicate `(tenantId, scopeId)` with `409 run_already_active` and `Retry-After`. |
|
|
46
|
-
| `OpenWOP-Force-Engine-Version` | Test keys only
|
|
59
|
+
| `OpenWOP-Force-Engine-Version` | Test keys only (the seams profile). A host MUST reject it on a production credential with `403`. |
|
|
60
|
+
|
|
61
|
+
### Response
|
|
62
|
+
|
|
63
|
+
The `201` response is `{ runId, status, eventsUrl, statusUrl? }`. `status` is one of `pending`, `running`, `waiting-approval`, `waiting-input`, `waiting-external`.
|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
- `eventsUrl` and `statusUrl` MUST resolve under the origin the request was made to — a relative path, or an absolute URL on the same origin — and MUST NOT downgrade the scheme. A link naming a different host, or `http://` on an `https://` origin, is non-conformant.
|
|
66
|
+
- The base that minted `runId` MUST resolve it: `GET /runs/{runId}` and `GET /runs/{runId}/events/poll` at that base MUST answer `200` for the returned id, percent-encoded per [identity.md](identity.md) §5. (A front door that decodes `%2F` before routing makes every tenant-bound id unreachable.)
|
|
49
67
|
|
|
50
|
-
|
|
68
|
+
### Refusals
|
|
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.
|
|
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
|
+
- 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
|
+
|
|
74
|
+
### The start event
|
|
75
|
+
|
|
76
|
+
`run.started` ([events.md](events.md)) MUST echo the run's `owner` block exactly as `RunSnapshot.owner` carries it. Its `transport` records `rest`, `mcp`, `a2a` or `ui`.
|
|
51
77
|
|
|
52
78
|
## Run options
|
|
53
79
|
|
|
54
80
|
`schemas/v2/run-options.schema.json` is `{ configurable?, tags?, metadata? }`.
|
|
55
81
|
|
|
56
|
-
`configurable` is `schemas/v2/configurable.schema.json`: closed, nested and versioned
|
|
82
|
+
`configurable` is `schemas/v2/configurable.schema.json`: closed, nested and versioned. The request body `$ref`s it directly; there is no `allOf`-merge of an open map. `version` is REQUIRED and is `1`. It has five sections:
|
|
83
|
+
|
|
84
|
+
| Section | Keys |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `run` | `recursionLimit`, `runTimeoutMs`, `maxLoopIterations`, `escalationThreshold` |
|
|
87
|
+
| `ai` | `provider`, `model`, `temperature` (0..2), `maxTokens`, `credentialRef`, `promptOverrides`, `mockProvider`, `reasoningVerbosity` (`none` \| `summary` \| `full`), `maxRefusals` |
|
|
88
|
+
| `distillation` | `tokenBudget` |
|
|
89
|
+
| `budget` | `schemas/v2/budget-policy.schema.json` — the run's budget policy |
|
|
90
|
+
| `extensions` | `<org>: {…}` — a vendor key lives under its registered org and nowhere else |
|
|
91
|
+
|
|
92
|
+
### `run` section
|
|
57
93
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
| `distillation` | `tokenBudget` | Resolves to `min(tokenBudget, memory.distillation.maxTokenBudget)`; a run that cannot distill within it MUST fail atomically with `token_budget_exceeded`. |
|
|
63
|
-
| `budget` | `schemas/v2/budget-policy.schema.json` | The run's budget policy. |
|
|
64
|
-
| `extensions` | `<org>: {…}` | A vendor key lives under its registered org and nowhere else. |
|
|
94
|
+
- `recursionLimit` is clamped to `limits.maxNodeExecutions`.
|
|
95
|
+
- `runTimeoutMs` resolves to `min(runTimeoutMs, limits.maxRunDurationMs)`. An out-of-range value MUST return `400 validation_error` at create. A breach MUST emit `cap.breached { kind: 'run-duration' }` and terminate the run `failed` with `run_timeout`.
|
|
96
|
+
- `maxLoopIterations` resolves against `limits.maxLoopIterations`. A breach MUST emit `cap.breached { kind: 'loop-iterations' }` and fail with `loop_limit_exceeded`.
|
|
97
|
+
- `escalationThreshold` is the `low-confidence` threshold ([interrupt.md](interrupt.md)).
|
|
65
98
|
|
|
66
|
-
|
|
99
|
+
### `ai` section
|
|
67
100
|
|
|
68
|
-
`
|
|
101
|
+
- `provider` MUST be in `aiProviders.providers`, else `400 validation_error`.
|
|
102
|
+
- `credentialRef` MUST reference a provider in `aiProviders.byok`, else `403 credential_forbidden`. It never carries key material.
|
|
103
|
+
- `mockProvider` is test-keys-only: a host MUST refuse it on a production credential with `403`.
|
|
104
|
+
- `maxRefusals` is the refusal ceiling ([events.md](events.md) E5).
|
|
105
|
+
|
|
106
|
+
### `distillation` section
|
|
107
|
+
|
|
108
|
+
`tokenBudget` resolves to `min(tokenBudget, memory.distillation.maxTokenBudget)`. A run that cannot distill within it MUST fail atomically with `token_budget_exceeded`.
|
|
109
|
+
|
|
110
|
+
### Validation and persistence
|
|
111
|
+
|
|
112
|
+
- 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`.
|
|
113
|
+
- A host MUST persist `RunOptions` on the run at creation, MUST surface the same `configurable` to every attempt of a node, and MUST NOT allow `configurable` to change after creation.
|
|
114
|
+
- A workflow's `configurableSchema` MUST be validated against at create time and MUST be surfaced on `getWorkflow`.
|
|
115
|
+
|
|
116
|
+
### `tags` and `metadata`
|
|
117
|
+
|
|
118
|
+
- `tags` is an opaque string array: at most 100 entries, each at most 256 characters, valid UTF-8. A host MUST NOT reject a tag on format, and MUST return `400 validation_error` over the limits.
|
|
119
|
+
- `metadata` is a free-form JSON object. A host MUST persist it, and the engine MUST NOT consume it for any execution decision.
|
|
120
|
+
- Both surface unchanged on `RunSnapshot`.
|
|
69
121
|
|
|
70
122
|
## Snapshot
|
|
71
123
|
|
|
72
|
-
`getRun` returns `schemas/v2/run-snapshot.schema.json
|
|
124
|
+
`getRun` returns `schemas/v2/run-snapshot.schema.json`: the fold of the event log through the run projection. The object is closed. `runId`, `workflowId`, `status`, `owner` and `eventLogSchemaVersion` are REQUIRED.
|
|
73
125
|
|
|
74
|
-
| Field |
|
|
126
|
+
| Field | Meaning |
|
|
75
127
|
| --- | --- |
|
|
76
|
-
| `owner` | `{ tenant, workspace?, subject }`, closed
|
|
77
|
-
| `status` |
|
|
78
|
-
| `eventLogSchemaVersion` | The era key, integer ≥ 2 (persistence.md §"The era key")
|
|
79
|
-
| `engineVersion` | Integer
|
|
80
|
-
| `compensationStatus` | `none`, `pending`, `running`, `completed`, `partial`, `failed`, `manual
|
|
81
|
-
| `currentNodeId` | Set while suspended; names the node holding the interrupt
|
|
82
|
-
| `error` | `{ code, message, details? }` on terminal `failed
|
|
83
|
-
| `configurable`, `tags`, `metadata` | The persisted `RunOptions
|
|
84
|
-
| `agent`, `runOrchestrator` | `schemas/v2/agent-ref.schema.json
|
|
85
|
-
| `metrics.openwopCost` | `{ usd, tokens { input, output }, model, provider, duration_ms }`; absence is not zero
|
|
86
|
-
|
|
87
|
-
|
|
128
|
+
| `owner` | `{ tenant, workspace?, subject }`, closed; `subject` REQUIRED (`schemas/v2/subject.schema.json`) |
|
|
129
|
+
| `status` | Run state (below) |
|
|
130
|
+
| `eventLogSchemaVersion` | The era key, integer ≥ 2 ([persistence.md](persistence.md) §"The era key") |
|
|
131
|
+
| `engineVersion` | Integer |
|
|
132
|
+
| `compensationStatus` | `none`, `pending`, `running`, `completed`, `partial`, `failed`, `manual` |
|
|
133
|
+
| `currentNodeId` | Set while suspended; names the node holding the interrupt |
|
|
134
|
+
| `error` | `{ code, message, details? }` on terminal `failed` |
|
|
135
|
+
| `configurable`, `tags`, `metadata` | The persisted `RunOptions` |
|
|
136
|
+
| `agent`, `runOrchestrator` | `schemas/v2/agent-ref.schema.json` |
|
|
137
|
+
| `metrics.openwopCost` | `{ usd, tokens { input, output }, model, provider, duration_ms }`; absence is not zero |
|
|
138
|
+
|
|
139
|
+
Field rules:
|
|
140
|
+
|
|
141
|
+
- `status` is one of `pending`, `running`, `paused`, `waiting-approval`, `waiting-input`, `waiting-external`, `completed`, `failed`, `cancelling`, `cancelled`. `waiting-external` MUST be used when the suspended interrupt's `kind` is `external-event`. `cancelling` is the state between an accepted cancel and the terminal `cancelled`. The vocabulary grows by [overview.md](overview.md) §0.
|
|
142
|
+
- `owner`: a run created before the host emitted subjects reads with the subject rule in [identity.md](identity.md), stamped at first read and never rewritten.
|
|
143
|
+
- `compensationStatus`: a host that does not advertise `compensation` MUST omit it. A host that does MUST include it on every snapshot, `none` when never requested.
|
|
144
|
+
- `runOrchestrator` MUST NOT change for the run's lifetime.
|
|
145
|
+
|
|
146
|
+
### Caching and encoding
|
|
147
|
+
|
|
148
|
+
- The `200` SHOULD carry a strong `ETag` derived from the latest persisted `sequence`. When present it MUST change on every observable transition and be stable otherwise.
|
|
149
|
+
- When the host sends an `ETag`, a request whose `If-None-Match` matches it MUST receive `304` with no body.
|
|
150
|
+
- A host MAY compress (`gzip` baseline; `br` and `zstd` only where advertised under `extensions["<org>.rest-transport"].contentEncodings`, [ext/restTransport](../ext/restTransport/README.md)). It MUST then set `Content-Encoding` and `Vary: Accept-Encoding`. The decoded body is byte-identical.
|
|
88
151
|
|
|
89
152
|
## List
|
|
90
153
|
|
|
91
|
-
`GET /runs` (
|
|
154
|
+
`GET /runs` (gated on `runList`) returns `{ runs: RunSnapshot[], nextCursor? }`: the caller's runs, newest first.
|
|
155
|
+
|
|
156
|
+
- Only runs whose tenant segment is the caller's appear, and every `runId` is bound ([identity.md](identity.md) §5). A run the caller created MUST appear.
|
|
157
|
+
- A page MUST NOT exceed `runList.maxPageSize`.
|
|
158
|
+
- `cursor` is opaque. A cursor the host did not mint MUST be refused with `400 validation_error`.
|
|
159
|
+
- `workflowId` and `status` are exact-match filters when `runList.filters` names them. An unadvertised filter is ignored.
|
|
92
160
|
|
|
93
161
|
## Cancel
|
|
94
162
|
|
|
95
|
-
`cancelRun` accepts `{ reason? }` and answers `200 { runId, status }
|
|
163
|
+
`cancelRun` accepts `{ reason? }` and answers `200 { runId, status }`, where `status` is `cancelling` or `cancelled`. The cascade MAY be asynchronous; the run emits `run.cancelled` when it completes.
|
|
164
|
+
|
|
165
|
+
- A cancel on a terminal run (`completed`, `failed`, `cancelled`) MUST be refused with `409 run_terminal`. A `200` echoing the terminal state is non-conformant.
|
|
166
|
+
- Cancelling a parent MUST NOT silently abandon an active compensation ([security-defaults.md](security-defaults.md)).
|
|
167
|
+
- `run.cancelled.parentRunId` with `reason: parent-cancelled` records a cascade from a parent.
|
|
168
|
+
|
|
169
|
+
### Bulk cancel
|
|
170
|
+
|
|
171
|
+
`bulkCancelRuns` accepts `{ runIds[1..100], reason? }`.
|
|
172
|
+
|
|
173
|
+
- Over the host's cap (RECOMMENDED 100) it MUST return `400 validation_error` with `details.maxRunIds`.
|
|
174
|
+
- The host MUST process each id independently, and MUST return `200 { results[] }` in request order, even when every id failed.
|
|
175
|
+
- The host MUST enforce authorization per id. A run the caller cannot see yields `ok: false` with an error envelope in that entry, never a top-level `403`.
|
|
176
|
+
|
|
177
|
+
Per-entry errors ([errors.md](errors.md)):
|
|
96
178
|
|
|
97
|
-
|
|
179
|
+
| Condition | Code |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| The id's tenant segment is not the caller's ([identity.md](identity.md) §5 applies inside an entry as on a path) | `id_tenant_mismatch`, or `not_found` where existence is not leaked |
|
|
182
|
+
| A run in the caller's tenant the caller may not cancel | `run_forbidden` |
|
|
183
|
+
| A run already terminal | `run_terminal` |
|
|
98
184
|
|
|
99
|
-
`
|
|
185
|
+
`ok: true` carries `status` `cancelling` or `cancelled`; `ok: false` carries the error envelope.
|
|
100
186
|
|
|
101
187
|
## Pause and resume
|
|
102
188
|
|
|
103
|
-
`pauseRun` accepts `{ reason?, drainPolicy? }`
|
|
189
|
+
`pauseRun` accepts `{ reason?, drainPolicy? }` and answers `202 { runId, status: 'paused', pausedAt? }`. The transition emits `run.paused`, whose payload echoes the request's `drainPolicy` word.
|
|
190
|
+
|
|
191
|
+
`drainPolicy` is one of:
|
|
192
|
+
|
|
193
|
+
- `drain-current-node` (default) — the executing node reaches a terminal first.
|
|
194
|
+
- `immediate` — the run is snapshotted between events. The executing attempt is cut: it has no terminal node event, a host MUST NOT record `node.failed` (or any terminal node event) for it, and the resumed run's `node.started` begins a fresh attempt. `run.paused` itself records the interruption; its payload MAY carry `interruptedNodeId` and `interruptedAttempt` so a `debug` consumer can see which attempt was cut.
|
|
195
|
+
|
|
196
|
+
`resumeRun` accepts `{ reason? }`, answers `202 { runId, status: 'running', resumedAt? }`, and emits `run.resumed`.
|
|
197
|
+
|
|
198
|
+
Rules:
|
|
199
|
+
|
|
200
|
+
- A pause on a run that is already paused, terminal, or otherwise unpausable MUST receive `409`.
|
|
201
|
+
- A resume on a run that is not paused MUST return `409`.
|
|
202
|
+
- In both cases the code is `run_terminal` when the run is terminal, else `run_state_conflict` with `details.runStatus` naming the refusing status.
|
|
203
|
+
- Only `resumeRun` or a cancel exits `paused`.
|
|
204
|
+
- A replay MUST fold `run.paused` and `run.resumed` as no-ops for projected state.
|
|
104
205
|
|
|
105
206
|
## Fork
|
|
106
207
|
|
|
107
|
-
`forkRun` accepts `{ mode: replay | branch, fromSeq?, runOptionsOverlay? }
|
|
208
|
+
`forkRun` accepts `{ mode: replay | branch, fromSeq?, runOptionsOverlay? }`. Events with `sequence < fromSeq` are fixed history; events `≥ fromSeq` re-execute.
|
|
209
|
+
|
|
210
|
+
- `fromSeq` is REQUIRED for `branch` and defaults to `0` for `replay`.
|
|
211
|
+
- `runOptionsOverlay` is `branch`-only. A `replay` with a non-empty overlay MUST be rejected with `400`.
|
|
212
|
+
- A `fromSeq` not in the source log MUST be rejected with `422 fork_point_invalid`.
|
|
213
|
+
|
|
214
|
+
The `201` response is `{ runId, sourceRunId, fromSeq?, mode, status, eventsUrl }`. The child's `owner` is copied verbatim from the parent. Determinism, side-effect suppression, and forking an era-2 parent are in [replay.md](replay.md).
|
|
108
215
|
|
|
109
216
|
## Diff and ancestry
|
|
110
217
|
|
|
111
|
-
`diffRun` returns `schemas/v2/run-diff-response.schema.json`: `divergedAtSeq`, ordered `eventDiffs[]`, `stateDiff`, optional `truncated`.
|
|
218
|
+
`diffRun` returns `schemas/v2/run-diff-response.schema.json`: `divergedAtSeq`, ordered `eventDiffs[]`, `stateDiff`, optional `truncated`.
|
|
219
|
+
|
|
220
|
+
- The diff MUST be a pure function of the two logs. Identical logs MUST yield `divergedAtSeq: null` and empty `eventDiffs`.
|
|
221
|
+
- `eventId`, `runId`, `timestamp` and other run-scoped fields MUST be excluded from comparison.
|
|
222
|
+
- A host that diffs an in-flight prefix MUST set `truncated: true`.
|
|
223
|
+
- A caller lacking `runs:read` on either run MUST receive `403`.
|
|
224
|
+
|
|
225
|
+
`getRunAncestry` returns `schemas/v2/run-ancestry-response.schema.json` (`runId`, `hostId`, `parent` or `null`). A client walks the chain one hop at a time via `parent.wellKnownUrl`.
|
|
112
226
|
|
|
113
227
|
## Annotations, artifacts, eval summary
|
|
114
228
|
|
|
115
|
-
|
|
229
|
+
### Annotations
|
|
230
|
+
|
|
231
|
+
`createAnnotation` accepts `schemas/v2/annotation-create.schema.json` and returns `201` with `schemas/v2/annotation.schema.json`. `listAnnotations` returns `{ annotations[] }`.
|
|
232
|
+
|
|
233
|
+
An annotation is a live notification (`run.annotated`), never a run event. It MUST NOT enter the event log and MUST be excluded from fork, replay and diff.
|
|
234
|
+
|
|
235
|
+
### Artifacts
|
|
236
|
+
|
|
237
|
+
`getArtifact` answers `application/json` with an implementation-defined object or, when `Accept` prefers `application/a2a+json`, an A2A `Artifact` (`schemas/v2/artifact.schema.json`). A host SHOULD offer the latter.
|
|
238
|
+
|
|
239
|
+
- A body served as `application/a2a+json` MUST validate against that schema, with `artifactId` equal to the path's.
|
|
240
|
+
- A `url` Part in it MUST NOT resolve beyond the caller's `artifacts:read` authorization.
|
|
241
|
+
|
|
242
|
+
### Eval summary
|
|
243
|
+
|
|
244
|
+
`getEvalSummary` returns `schemas/v2/eval-summary.schema.json` for a terminal eval run, `409` while it is running, and `404` when the run is not an eval run. The summary MUST be free of task output, rubric prose and credentials.
|
|
116
245
|
|
|
117
246
|
## Conversation and residency capabilities
|
|
118
247
|
|
|
119
|
-
`conversationPrimitive`
|
|
120
|
-
|
|
121
|
-
does not advertise `
|
|
122
|
-
|
|
248
|
+
### `conversationPrimitive`
|
|
249
|
+
|
|
250
|
+
`conversationPrimitive` carries no payload: its presence is the claim ([capabilities.md](capabilities.md) §2). A host that does not advertise it MUST refuse a workflow whose `nodes[].typeId` references `core.conversationGate` — at registration or at run creation — with `422 capability_required`, naming the family in `details.requiredCapability`.
|
|
251
|
+
|
|
252
|
+
A conversation turn MAY carry `parts`: a non-empty array of A2A `Part` objects (`schemas/v2/part.schema.json`) that marks the turn A2A-shaped. A producer SHOULD emit it and keep `content` readable by consumers that predate it. A turn without `parts` stays valid on emission, replay and fork.
|
|
253
|
+
|
|
254
|
+
### `dataResidency`
|
|
255
|
+
|
|
256
|
+
A host advertising `dataResidency` MUST honor-or-reject, and MUST NOT silently accept-and-ignore:
|
|
257
|
+
|
|
258
|
+
- accept a `residency` constraint naming a region in `dataResidency.regions`;
|
|
259
|
+
- refuse one it does not advertise with `residency_unavailable`.
|
|
260
|
+
|
|
261
|
+
A host that does not advertise `dataResidency` MAY ignore or reject a `residency` constraint, but MUST NOT claim to honor it.
|
|
262
|
+
|
|
263
|
+
## During the v1 overlap
|
|
123
264
|
|
|
124
|
-
A
|
|
265
|
+
A non-terminal run inherited from v1 continues, or is cancelled `v1_pin_unsupported`, per [persistence.md](persistence.md) §"Runs pinned to v1".
|
|
125
266
|
|
|
126
|
-
|
|
127
|
-
region in `dataResidency.regions`, refuse one it does not advertise with `residency_unavailable`,
|
|
128
|
-
and MUST NOT silently accept-and-ignore. A host that does not advertise `dataResidency` MAY ignore or
|
|
129
|
-
reject a `residency` constraint but MUST NOT claim to honor it.
|
|
267
|
+
*Sources: RFC 0170, RFC 0171, RFC 0176, RFC 0182.*
|
|
@@ -1,74 +1,99 @@
|
|
|
1
1
|
# Security Defaults
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
> **Normative home:** `sandbox`, `compensation`, `purposePropagation`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
In v2 a security behavior is not an opt-in flag. This document is the obligation table: which surface binds which behavior, the invariant, and the witness.
|
|
9
9
|
|
|
10
10
|
## The rule
|
|
11
11
|
|
|
12
|
-
A security-load-bearing behavior is an obligation of the surface that needs it
|
|
12
|
+
A security-load-bearing behavior is an obligation of the surface that needs it. Advertising the surface binds the behavior; no discovery field gates it.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
- Every obligation in `core/` MUST name a surface, an invariant, and a witness class other than `unwitnessable`. A row that cannot is not in `core/`.
|
|
15
|
+
- A host MUST NOT advertise a surface whose obligation it has relaxed.
|
|
15
16
|
|
|
16
17
|
## The obligation table
|
|
17
18
|
|
|
18
|
-
| Surface advertised | Obligation
|
|
19
|
+
| Surface advertised | Obligation | Witness | Invariant |
|
|
19
20
|
| --- | --- | --- | --- |
|
|
20
|
-
| any lane in `auth.lanes[]` | the lane obligations
|
|
21
|
-
| both `saml` and `scim` lanes | the leaver contract (
|
|
22
|
-
| `replay` (any mode) | side-effect suppression
|
|
23
|
-
| `webhooks` | durable delivery
|
|
24
|
-
| `interrupt` with `approversList` or `refKinds` | enforcement
|
|
25
|
-
| `packs` (pack execution) | isolation
|
|
26
|
-
| `compensation` |
|
|
27
|
-
| `idempotency` | Layer-2 effect identity
|
|
28
|
-
| an `oauth2` or `oidc` lane | protected-resource metadata and challenges (identity.md §2.5) | witnessable-gated | `auth-challenge-no-oracle` |
|
|
21
|
+
| any lane in `auth.lanes[]` | the lane obligations (§Auth lanes) | unaided or seam-gated per lane | `sender-constraint-no-bearer-downgrade`; per-lane rows |
|
|
22
|
+
| both `saml` and `scim` lanes | the leaver contract (mandatory) | seam-gated | `subject-link-leaver-deny`, `subject-link-mandatory-when-both-advertised` |
|
|
23
|
+
| `replay` (any mode) | side-effect suppression (§Replay suppression) | witnessable-gated via the effect-seam manifest | `replay-fanout-no-refire`; effect-seam rows registered at Accepted |
|
|
24
|
+
| `webhooks` | durable delivery (§Webhook durability) | witnessable-gated (`webhook-signed-delivery` + dead-letter leg) | registered at Accepted |
|
|
25
|
+
| `interrupt` with `approversList` or `refKinds` | approver enforcement (§Approver enforcement) | witnessable-gated | registered at Accepted |
|
|
26
|
+
| `packs` (pack execution) | isolation (§Sandbox isolation) | witnessable-gated (eight `sandbox-*` scenarios) | `node-pack-sandbox-*` |
|
|
27
|
+
| `compensation` | plan, attempt and inverse-action obligations (§Compensation) | witnessable-gated (reads) + seam-gated (operator actions) | `compensation-replay-no-refire`, `compensation-effect-id-retry-stable` |
|
|
28
|
+
| `idempotency` | Layer-2 effect identity (§Layer-2 effect identity) | witnessable-gated (fixture provider) | `logical-effect-id-retry-stable` |
|
|
29
|
+
| an `oauth2` or `oidc` lane | protected-resource metadata and challenges ([identity.md §2.5](identity.md)) | witnessable-gated | `auth-challenge-no-oracle` |
|
|
29
30
|
| any outbound request | no inbound credential on an onward hop (§Onward hops) | seam-gated | `inbound-credential-no-passthrough` |
|
|
30
31
|
|
|
31
32
|
### Auth lanes
|
|
32
33
|
|
|
33
|
-
The obligations are identity.md §2 and bind on advertisement
|
|
34
|
+
The obligations are [identity.md §2](identity.md) and bind on advertisement:
|
|
35
|
+
|
|
36
|
+
- the verify → bind → audience → resolve → fail-closed pipeline;
|
|
37
|
+
- a named trust root as `subject.issuer`;
|
|
38
|
+
- revocation for the lane;
|
|
39
|
+
- the advertised `minimumAssurance` floor, with `mtls.required` becoming `key-bound`;
|
|
40
|
+
- lane-scoped delegation proof.
|
|
34
41
|
|
|
35
42
|
### Replay suppression
|
|
36
43
|
|
|
37
|
-
Stated in replay.md §Suppression and §"The effect-seam manifest"; the `replay-side-effect-suppression` scenario witnesses it.
|
|
44
|
+
Side-effect suppression with `recorded-outcome` semantics is the only conforming behavior; `none` is not a value. Stated in replay.md §Suppression and §"The effect-seam manifest"; the `replay-side-effect-suppression` scenario witnesses it.
|
|
38
45
|
|
|
39
46
|
### Webhook durability
|
|
40
47
|
|
|
41
|
-
Stated in webhooks.md §Durability; the `webhook-durable-delivery` scenario witnesses it.
|
|
48
|
+
Durable delivery means retries per the advertised policy with backoff, dead-letter on exhaustion, and at-least-once delivery; best-effort is not a conforming delivery mode. Stated in webhooks.md §Durability; the `webhook-durable-delivery` scenario witnesses it.
|
|
42
49
|
|
|
43
50
|
### Approver enforcement
|
|
44
51
|
|
|
45
|
-
Stated in interrupt.md §"Approver enforcement"; the `approver-enforced` scenario witnesses it.
|
|
52
|
+
A resolver not in the list, group, or role MUST be refused; `refKinds[]` stays a facet. Stated in interrupt.md §"Approver enforcement"; the `approver-enforced` scenario witnesses it.
|
|
46
53
|
|
|
47
54
|
### Sandbox isolation
|
|
48
55
|
|
|
49
|
-
A host that executes third-party packs
|
|
56
|
+
A host that executes third-party packs:
|
|
57
|
+
|
|
58
|
+
- MUST enforce the eight `node-pack-sandbox-*` invariants of `SECURITY/invariants.yaml`: `no-process`, `network-gated`, `fs-gated`, `no-env`, `timeout`, `memory-cap`, `isolated-context`, `no-eval`;
|
|
59
|
+
- MUST advertise `sandbox.isolationModel ∈ wasm | process | container | vm` (`spec/v2/facets/sandbox.schema.json`). `node:vm` is not a value. `sandbox.isolationModel` names the mechanism and never relaxes the property.
|
|
60
|
+
|
|
61
|
+
A host that cannot isolate MUST NOT execute third-party packs; it MAY register and validate them. The `no-eval` row stays reference-impl in `ext/sandbox-runtime-notes`. The `pack-isolation` scenario drives the eight legs.
|
|
50
62
|
|
|
51
63
|
The remaining `sandbox` facets name the bound each invariant already carries: `allowedHostCalls` is the host-call allowlist `network-gated` and `fs-gated` enforce, `memoryLimitBytes` is the ceiling of `memory-cap`, and `wallClockLimitMs` is the ceiling of `timeout`. An advertised bound MUST be enforced; none of the three gates the invariant, which binds whether or not the facet is advertised.
|
|
52
64
|
|
|
53
65
|
### Compensation
|
|
54
66
|
|
|
55
|
-
A host that advertises `compensation` MUST serve `GET /runs/{runId}/compensation` (`schemas/v2/compensation-projection.schema.json`), keyed on the node and attempt the operator family uses
|
|
67
|
+
A host that advertises `compensation` MUST serve `GET /runs/{runId}/compensation` (`schemas/v2/compensation-projection.schema.json`), keyed on the node and attempt the operator family uses. The read projection and the operator action family are the canonical wire. A host that does not advertise `compensation` has no obligation.
|
|
56
68
|
|
|
57
|
-
The facets bind the policy shape (`schemas/v2/compensation-policy.schema.json`):
|
|
69
|
+
The facets bind the policy shape (`schemas/v2/compensation-policy.schema.json`):
|
|
70
|
+
|
|
71
|
+
- `compensation.orderingModels` MUST list `reverse-completion` and MAY add `dependency-graph`; a policy naming a model outside it MUST be refused at registration.
|
|
72
|
+
- `compensation.profileVersion` participates in the inverse-action identity, so a policy naming a different one MUST be refused.
|
|
73
|
+
- `compensation.manualIntervention` is the `manual` status — a host advertising it records the unwind rather than abandoning it.
|
|
58
74
|
|
|
59
75
|
### Layer-2 effect identity
|
|
60
76
|
|
|
61
|
-
A host that advertises `idempotency` MUST serve `GET /runs/{runId}/effects`; the keying, provider-key, retention and projection rules are idempotency.md §"Layer 2: effect identity"
|
|
77
|
+
Layer-2 effect identity is keyed on business identity; the activity recipe is the fallback. A host that advertises `idempotency` MUST serve `GET /runs/{runId}/effects`; the keying, provider-key, retention and projection rules are idempotency.md §"Layer 2: effect identity".
|
|
62
78
|
|
|
63
79
|
### Onward hops
|
|
64
80
|
|
|
65
|
-
A host MUST NOT attach a credential it received inbound
|
|
81
|
+
A host MUST NOT attach a credential it received inbound to any outbound request: A2A, MCP, webhook, callback, `httpClient` or connector. Inbound credentials are an `Authorization`, `Cookie` or `Proxy-Authorization` value, a DPoP proof, an interrupt token, a peer's bearer, or credential material carried in a body.
|
|
82
|
+
|
|
83
|
+
Outbound authentication uses only credentials the host holds for that destination (oauth.md). A verified delegation chain is not a passthrough: it carries provenance, never the inbound credential ([identity.md §2.4](identity.md)).
|
|
66
84
|
|
|
67
|
-
|
|
85
|
+
One credential is not inbound in this sense: an A2A push-config credential (`authentication.credentials`, `token`) is one the client gave the host for its registered push URL ([interop.md §"A2A push delivery"](interop.md)). A host:
|
|
86
|
+
|
|
87
|
+
- MAY attach it only to a push delivery to that URL's origin;
|
|
88
|
+
- MUST NOT attach it after a redirect or to any other request;
|
|
89
|
+
- MUST discard it when the config is deleted or the task's final push is attempted;
|
|
90
|
+
- MUST hold it by reference outside the event log, run state, debug bundle and every response.
|
|
91
|
+
|
|
92
|
+
A host advertising `purposePropagation` MUST re-emit a `permittedPurposes` label it received (A2A `metadata.openwop.permittedPurposes`, `TriggerEvent.permittedPurposes`) on every onward hop of the same data, narrowing and never widening, and MUST treat `[]` as no onward use. `purposePropagation.propagatesOnward` is `false` only on a host with no onward hop. The family advertises propagation, not enforcement.
|
|
68
93
|
|
|
69
94
|
## Relaxations
|
|
70
95
|
|
|
71
|
-
A relaxation, where one is legitimate — a development deployment, a single-tenant appliance — is an operator setting, never a discovery field
|
|
96
|
+
A relaxation, where one is legitimate — a development deployment, a single-tenant appliance — is an operator setting, never a discovery field. Every relaxation a host runs under MUST be recorded in its certification bundle as `host.relaxations[]` (`schemas/v2/certification-bundle.schema.json`): `{ obligation, durability, reason }`, `durability ∈ session | deployment | persisted`.
|
|
72
97
|
|
|
73
98
|
| Durability | Meaning |
|
|
74
99
|
| --- | --- |
|
|
@@ -80,7 +105,7 @@ A bundle that records a relaxation MUST NOT certify the profile the relaxed obli
|
|
|
80
105
|
|
|
81
106
|
## Three dispositions
|
|
82
107
|
|
|
83
|
-
Every security obligation in `core/` is exactly one of
|
|
108
|
+
Every security obligation in `core/` is exactly one of:
|
|
84
109
|
|
|
85
110
|
| Disposition | Where | Requirement |
|
|
86
111
|
| --- | --- | --- |
|
|
@@ -88,23 +113,22 @@ Every security obligation in `core/` is exactly one of (RFC 0173 §D.1):
|
|
|
88
113
|
| extension | `spec/v2/ext/` | MUST declare a witness class and both maturity axes. |
|
|
89
114
|
| removed | — | No text survives. |
|
|
90
115
|
|
|
91
|
-
Operation ids in the declaration file are canonical, and aliases are migration
|
|
92
|
-
register rows. The provider semantic-option registry is
|
|
93
|
-
`spec/v2/ext/provider-idempotency/registry.json`; provider qualification uses a
|
|
94
|
-
fixture that rejects a changed idempotency key (§D.2).
|
|
116
|
+
Operation ids in the declaration file are canonical, and aliases are migration register rows. The provider semantic-option registry is `spec/v2/ext/provider-idempotency/registry.json`; provider qualification uses a fixture that rejects a changed idempotency key.
|
|
95
117
|
|
|
96
118
|
## Threat models
|
|
97
119
|
|
|
98
|
-
The following threat-model artifacts are required:
|
|
99
|
-
|
|
100
120
|
| Artifact | Requirement |
|
|
101
121
|
| --- | --- |
|
|
102
122
|
| `SECURITY/threat-model-replay.md` §6 Residual risks | MUST record branch re-fires, seams outside the manifest, and the manifest as a self-declaration. |
|
|
103
|
-
| `SECURITY/threat-model-replay.md` §7 Verification, §8 References | MUST name the manifest scenario and `fork-a-v1-run
|
|
123
|
+
| `SECURITY/threat-model-replay.md` §7 Verification, §8 References | MUST name the manifest scenario and `fork-a-v1-run`. |
|
|
104
124
|
| `SECURITY/threat-model-interop.md` | MUST cover downgrade, identity, and cross-tenant risks in protocol composition. |
|
|
105
125
|
|
|
126
|
+
A threat model missing a sibling section fails the template gate.
|
|
127
|
+
|
|
106
128
|
## Migration
|
|
107
129
|
|
|
108
130
|
Rows `C6.1`–`C6.9` are `spec/v1/migrations.json` entries.
|
|
109
131
|
|
|
110
132
|
See also: identity.md, replay.md, webhooks.md, capabilities.md, conformance.md.
|
|
133
|
+
|
|
134
|
+
*Sources: RFC 0150, RFC 0163, RFC 0164, RFC 0170, RFC 0173, RFC 0214.*
|
|
@@ -1,22 +1,45 @@
|
|
|
1
1
|
# Tool catalog
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
> **Normative home:** `toolCatalog`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
A host's tool catalog lists the tools a caller may invoke, with a projection onto MCP `ToolAnnotations`. The shapes are `schemas/v2/tool-descriptor.schema.json` and `schemas/v2/compact-tool-descriptor.schema.json`.
|
|
9
9
|
|
|
10
10
|
## The catalog
|
|
11
11
|
|
|
12
|
-
A host advertising `toolCatalog` MUST serve `GET /tools` (a `ToolDescriptor[]`) and `GET /tools/{toolId}`, both read-only.
|
|
12
|
+
A host advertising `toolCatalog` MUST serve `GET /tools` (a `ToolDescriptor[]`) and `GET /tools/{toolId}`, both read-only.
|
|
13
|
+
|
|
14
|
+
- The list MUST hold only tools the caller may invoke in its tenant.
|
|
15
|
+
- An unknown or unauthorized `toolId` MUST return `404`.
|
|
16
|
+
- `toolCatalog.sources` names the sources projected; a consumer MUST tolerate any subset.
|
|
17
|
+
- A host SHOULD return tools sorted by `toolId`, so an unchanged catalog reads identically.
|
|
13
18
|
|
|
14
19
|
## The descriptor
|
|
15
20
|
|
|
16
|
-
`toolId` MUST be unique in the catalog and stable for a host version.
|
|
21
|
+
- `toolId` MUST be unique in the catalog and stable for a host version.
|
|
22
|
+
- `safetyTier: "exec"` MUST carry `source: "host-extension"`.
|
|
23
|
+
- A descriptor MUST NOT carry credential material.
|
|
24
|
+
- The host MUST assign `safetyTier`, `replayPolicy` and `egress` itself, and MUST NOT copy them from an MCP server's `annotations`, which are untrusted.
|
|
25
|
+
- A `source: "mcp"` tool the host has not classified MUST be `safetyTier: "write"`.
|
|
26
|
+
|
|
27
|
+
`annotations`, when present, MUST carry all four MCP hints, derived from those fields and not from MCP defaults (upstream, `destructiveHint` and `openWorldHint` default to `true`):
|
|
17
28
|
|
|
18
|
-
|
|
29
|
+
| Hint | True iff |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `readOnlyHint` | `safetyTier` is `pure` or `read` |
|
|
32
|
+
| `destructiveHint` | `safetyTier` is `write` or `exec` |
|
|
33
|
+
| `idempotentHint` | `replayPolicy` is `deterministic` or `idempotent` |
|
|
34
|
+
| `openWorldHint` | `egress` is not `none` |
|
|
19
35
|
|
|
20
36
|
## Views and sessions
|
|
21
37
|
|
|
22
|
-
A host advertising `toolCatalog.compactView` MUST answer `?view=compact` on both endpoints with `CompactToolDescriptor`s (the list as `{ tools: [...] }`) carrying the standard view's `toolId` set
|
|
38
|
+
A host advertising `toolCatalog.compactView` MUST answer `?view=compact` on both endpoints with `CompactToolDescriptor`s (the list as `{ tools: [...] }`) carrying the standard view's `toolId` set.
|
|
39
|
+
|
|
40
|
+
- A compact `inputSchema` MUST NOT use `$ref`, `oneOf`, `allOf`, `anyOf`, `not`, `patternProperties` or `dependentSchemas` at any depth.
|
|
41
|
+
- Any other `view`, or a host not advertising it, yields the standard view.
|
|
42
|
+
|
|
43
|
+
A host advertising `toolCatalog.sessionLifecycle` MAY bracket calls with content-free `tool.session.opened` and `tool.session.closed`; a consumer MUST tolerate their absence.
|
|
44
|
+
|
|
45
|
+
*Sources: RFC 0078, RFC 0112, RFC 0204.*
|