{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://openwop.dev/spec/v1/capabilities.schema.json",
  "title": "Capabilities",
  "description": "openwop capability declaration returned from `GET /.well-known/openwop`. Required v1 fields identify the protocol version, envelope catalog, schema versions, and base limits. Optional v1 fields have stable shapes but MAY be omitted when unsupported.",
  "type": "object",
  "required": ["protocolVersion", "supportedEnvelopes", "schemaVersions", "limits"],
  "properties": {
    "protocolVersion": {
      "type": "string",
      "description": "openwop protocol version the server speaks (e.g., `1.0`). Independent of `engineVersion` (which gates persisted-doc compatibility per version-negotiation.md).",
      "minLength": 1
    },
    "supportedEnvelopes": {
      "type": "array",
      "items": { "type": "string", "minLength": 1 },
      "description": "Envelope `type` strings the engine recognizes (e.g., `prd.create`, `theme.create`, `tasks.create`, `clarification.request`)."
    },
    "schemaVersions": {
      "type": "object",
      "additionalProperties": { "type": "integer", "minimum": 0 },
      "description": "Active schema version per envelope type (e.g., `{ \"prd.create\": 2 }`)."
    },
    "limits": {
      "type": "object",
      "required": ["clarificationRounds", "schemaRounds", "envelopesPerTurn"],
      "properties": {
        "clarificationRounds": {
          "type": "integer",
          "minimum": 0,
          "description": "Maximum total clarification envelopes per task before the engine forces the LLM to proceed."
        },
        "schemaRounds": {
          "type": "integer",
          "minimum": 0,
          "description": "Maximum total schema-validation rounds per envelope before the node fails."
        },
        "envelopesPerTurn": {
          "type": "integer",
          "minimum": 0,
          "description": "Maximum total envelopes the LLM may emit in a single chat turn."
        },
        "maxNodeExecutions": {
          "type": "integer",
          "minimum": 1,
          "description": "Engine-side ceiling on total node executions per run. Acts as the upper bound for `RunOptions.configurable.recursionLimit`. Optional in v1; hosts that advertise it MUST enforce it."
        },
        "maxRunDurationMs": {
          "type": "integer",
          "minimum": 1000,
          "description": "RFC 0058. Engine-side wall-clock ceiling per run (milliseconds). Upper bound for `RunOptions.configurable.runTimeoutMs`. Breach emits `cap.breached { kind: 'run-duration' }` + error `run_timeout`. Optional; hosts that advertise it MUST enforce it."
        },
        "maxLoopIterations": {
          "type": "integer",
          "minimum": 1,
          "description": "RFC 0058. Authoritative engine-side ceiling on agent-loop iterations. Upper bound for `RunOptions.configurable.maxLoopIterations`. Breach emits `cap.breached { kind: 'loop-iterations' }` + error `loop_limit_exceeded`. Optional; hosts that advertise it MUST enforce it."
        },
        "maxBudgetTokens": {
          "type": "integer",
          "minimum": 1,
          "description": "RFC 0084. Engine-side ceiling clamping `RunOptions.configurable.budget.maxTokens` (`min(requested, ceiling)`). Optional; only meaningful with `capabilities.budget`."
        },
        "maxBudgetCostUsd": {
          "type": "number",
          "minimum": 0,
          "description": "RFC 0084. Engine-side cost ceiling clamping `RunOptions.configurable.budget.maxCostUsd`. Optional; only meaningful with `capabilities.budget`."
        },
        "maxRequestBodyBytes": {
          "type": "integer",
          "minimum": 1,
          "description": "RFC 0094 §H. Maximum REST request body size (bytes) the host accepts. Optional v1 field per `capabilities.md` §3 (previously documented as reserved — the closed `limits` object made advertising it a schema-validation failure). Hosts that advertise it MUST enforce it."
        }
      },
      "additionalProperties": false,
      "description": "Hard limits enforced by the engine. See capabilities.md §3."
    },
    "envelopeStrictness": {
      "type": "string",
      "enum": ["warn", "strict"],
      "description": "AI Envelope schema-version drift handling per `spec/v1/ai-envelope.md` §\"Capability handshake integration\" (DRAFT v1.x). Optional in v1.x; default when absent is `warn`. Under `warn`, the engine MUST attempt validation against the advertised version of a kind and log `envelope_schema_version_drift` when the emitted `schemaVersion` is lower than advertised. Under `strict`, the same condition MUST cause refusal with `unknown_schema_version`. Emitted `schemaVersion` higher than advertised MUST refuse regardless of strictness."
    },
    "envelopeContracts": {
      "type": "object",
      "description": "AI Envelope contract-gating advertisement per `spec/v1/ai-envelope.md` §\"Capability handshake integration\" (DRAFT v1.x). Optional in v1.x. When `advertised: true`, the host's node-pack manifests carry `EnvelopeContract` blocks per `ai-envelope.md` §\"Envelope Contract\"; tooling and conformance scenarios gate on this flag. When `advertised: false` or the block is absent, hosts MAY accept envelopes without per-typeId `accepts[]` enforcement.",
      "required": ["advertised"],
      "properties": {
        "advertised": {
          "type": "boolean",
          "description": "Host's node-pack manifests carry `EnvelopeContract` blocks."
        }
      },
      "additionalProperties": false
    },
    "envelopes": {
      "type": "object",
      "description": "Envelope LLM-contract advertisement container introduced by the RFC 0030–0033 envelope-hardening track. Each sub-block is independently opt-in; hosts that adopt a subset advertise only the sub-blocks they implement. `additionalProperties: true` reserved for future RFCs that extend the track; host-private extensions go under `x-host-<host>-<key>` per `host-extensions.md` §\"Canonical-prefix table\".",
      "additionalProperties": true,
      "properties": {
        "reasoning": {
          "type": "object",
          "description": "RFC 0030 §A + §C. Host's envelope payload schemas support the OPTIONAL `reasoning` field on kinds where multi-step reasoning materially improves output quality; the host's system-prompt-injection helper instructs the model to populate it. Absent block = no advertisement (the field MAY still appear on envelope schemas; this advertisement signals the host's prompt-injection posture).",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host's envelope payload schemas carry `reasoning` (OPTIONAL) on kinds where reasoning materially improves output quality, per RFC 0030 §A. Hosts SHOULD prompt the model to populate the field; hosts MUST NOT reject envelopes where `reasoning` is absent (the field is OPTIONAL by spec); hosts SHALL NOT route on `reasoning` contents."
            },
            "promptDirective": {
              "type": "string",
              "enum": ["mandatory", "advisory", "off"],
              "description": "Strength of the host's system-prompt instruction to populate `reasoning`. `mandatory`: the directive is firmly worded (the host instructs the model very forcefully to emit `reasoning`). `advisory` (default when absent): the directive is suggestive. `off`: the host emits no directive — applications that want `reasoning` populated must inject the instruction themselves. Note: `mandatory` is a prompt-injection posture, NOT a wire-level refusal contract — hosts MUST NOT reject envelopes where `reasoning` is absent regardless of this value."
            }
          }
        },
        "tierOneSubsetCompliance": {
          "type": "string",
          "enum": ["strict", "warn", "off"],
          "description": "RFC 0030 §B + §C. Host's self-attested compliance posture for the Tier-1 cross-vendor structured-output subset documented in `spec/v1/structured-output-subset.md`. `strict`: every host-served envelope schema passes the static subset-compliance check (object-root, `additionalProperties: false` everywhere, every property in `required`, no `oneOf`/`allOf`/`not`/`prefixItems`/`propertyNames`, no string format/pattern/length constraints, no number bounds, no array bounds, ≤5 nesting depth, ≤100 property count). `warn`: host serves non-compliant schemas but logs the violations. `off` (default when absent): no self-attestation. The conformance-suite static-subset scenario gates on this flag."
        },
        "reliability": {
          "type": "object",
          "additionalProperties": false,
          "required": ["supported"],
          "description": "RFC 0032 §C. Host emits the envelope-reliability event family on documented adverse paths. Hosts opt into the family via `supported: true` AND explicitly list the events they emit via `events[]`. Hosts that advertise `supported: true` MUST include at least `envelope.retry.exhausted` and `envelope.refusal` in `events[]` (the two MUST-tier events). The other four (`envelope.retry.attempted`, `envelope.truncated`, `envelope.nlToFormat.engaged`, `envelope.recovery.applied`) are SHOULD/MAY-tier per RFC 0032 §B and may be omitted.",
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host emits the RFC 0032 envelope-reliability event family on the documented adverse paths. When `false` or absent, conformance scenarios for the family soft-skip."
            },
            "events": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "envelope.retry.attempted",
                  "envelope.retry.exhausted",
                  "envelope.refusal",
                  "envelope.truncated",
                  "envelope.nlToFormat.engaged",
                  "envelope.recovery.applied"
                ]
              },
              "uniqueItems": true,
              "description": "Subset of the six reliability events the host actually emits. Hosts that advertise `supported: true` MUST include `envelope.retry.exhausted` and `envelope.refusal`. Conformance scenarios soft-skip for events absent from this list (the event-emission assertion is skipped; other host behavior assertions remain in force)."
            },
            "maxRetryAttempts": {
              "type": "integer",
              "minimum": 1,
              "maximum": 16,
              "description": "Host's retry budget per envelope emission. Conformance scenarios use this to construct fixtures that exercise the retry-exhausted path. Independent of `capabilities.limits.schemaRounds` (which is the engine-side per-emission cap) — `maxRetryAttempts` reports the host's actual configured value, not the spec's upper bound."
            },
            "completion": {
              "type": "object",
              "additionalProperties": false,
              "required": ["distinguishesTruncation"],
              "description": "RFC 0033 §E. Host's self-attested envelope-completion contract posture — does the host distinguish truncation from schema-violation in its retry routing per RFC 0033 §A + §B + §C?",
              "properties": {
                "distinguishesTruncation": {
                  "type": "boolean",
                  "description": "Host implements RFC 0033's truncation-vs-schema-violation retry-routing distinction: truncation → increased output budget (NO corrective fragment); schema-violation → corrective system fragment (NO budget increase). When `false` or absent, the host conflates the two paths (legacy behavior); RFC 0033 conformance scenarios soft-skip."
                },
                "truncationBudgetMultiplier": {
                  "type": "number",
                  "minimum": 1,
                  "maximum": 8,
                  "description": "Host's per-attempt output-budget multiplier on truncation retries. Informational; clients MAY surface this in cost-estimation UIs. Defaults to 2 when absent and `distinguishesTruncation: true`. Spec recommendation is 2× per RFC 0033 §B."
                }
              }
            }
          }
        }
      }
    },
    "prompts": {
      "type": "object",
      "description": "RFC 0027 + RFC 0028 prompt-template support advertisement. The `supported` flag gates **node-execution resolution** of PromptRef values per RFC 0027 (Phase A); the separate `endpointsSupported` flag gates the **REST surface** at `/v1/prompts*` per RFC 0028 (Phase B). The two axes are independent — a host MAY implement Phase A composition without exposing the REST surface, and vice versa. Absent block = no support; consumers passing PromptRef values to such a host MUST tolerate the keys being treated as opaque strings.",
      "required": ["supported"],
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "RFC 0027 Phase A. When `true`, the host resolves PromptRef values in `WorkflowNode.config.{systemPromptRef, userPromptRef, additionalPromptRefs}` at node-execution time AND emits `prompt.composed` run events per spec/v1/prompts.md §\"Composition + observability\". Does NOT imply the `/v1/prompts*` REST surface is available — see `endpointsSupported` for that. False or absent = the PromptRef keys on `WorkflowNode.config` are treated as opaque strings and never composed."
        },
        "endpointsSupported": {
          "type": "boolean",
          "description": "RFC 0028 §A. When `true`, the host serves the `/v1/prompts*` REST surface — at minimum the read endpoints (`listPromptTemplates`, `getPromptTemplate`, `renderPromptTemplate`). The mutating endpoints additionally require `mutableLibrary: true`; the pack-install path additionally requires `packsSupported: true`. False or absent = every `/v1/prompts*` request returns `501 capability_not_provided`. Independent of `supported` — a host MAY advertise `supported: true, endpointsSupported: false` (composition without library REST) or `supported: false, endpointsSupported: true` (library REST against an external resolver, e.g., a façade host)."
        },
        "templateKinds": {
          "type": "array",
          "items": { "$ref": "./prompt-kind.schema.json" },
          "uniqueItems": true,
          "description": "Subset of PromptKind values the host accepts. Defaults to all four (`system`, `user`, `few-shot`, `schema-hint`) when omitted. The $ref resolves to the shared `prompt-kind.schema.json` `$def` per RFC 0027 §A — uses a relative URI so redocly's lint walker resolves it against the local file rather than fetching the openwop.dev URL."
        },
        "variableSources": {
          "type": "array",
          "items": { "type": "string", "enum": ["input", "variable", "secret", "context"] },
          "uniqueItems": true,
          "description": "Subset of PromptVariable.source values the host supports. `secret` SHOULD only appear when `capabilities.secrets.supported` is also true."
        },
        "maxTemplateBytes": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65536,
          "description": "Host limit on PromptTemplate.text length. MUST NOT exceed the schema cap (65536). When advertised, the host MUST reject larger bodies on install + on `POST /v1/prompts` (RFC 0028)."
        },
        "observability": {
          "type": "string",
          "enum": ["off", "hashed", "full"],
          "description": "How `prompt.composed` events expose resolved bodies. `off`: event not emitted. `hashed`: payload carries only sha256 + per-variable-binding hashes (no plaintext). `full`: payload carries the composed body with secret-sourced values redacted to `[REDACTED:<secretId>]` markers and untrusted segments wrapped in `<UNTRUSTED>` markers. Default when absent: `hashed`."
        },
        "packsSupported": {
          "type": "boolean",
          "description": "RFC 0028 §C. When `true`, the host installs `kind: \"prompt\"` registry packs and exposes their templates at `GET /v1/prompts` with `meta.source: \"pack\"` + `meta.packName` + `meta.packVersion`. When `false` or absent, packs are not loaded; only host built-ins and (when `mutableLibrary: true`) user-created templates are visible."
        },
        "mutableLibrary": {
          "type": "boolean",
          "description": "RFC 0028 §C. When `true`, the host honors the mutating endpoints `POST /v1/prompts`, `PUT /v1/prompts/{templateId}`, `DELETE /v1/prompts/{templateId}`. When `false` or absent, those endpoints return 501. Pack-sourced and host-built-in templates remain read-only even under `mutableLibrary: true` — deletion of those returns 403 per RFC 0028 §A."
        },
        "library": {
          "type": "object",
          "additionalProperties": false,
          "description": "RFC 0028 §C. Per-library configuration knobs that influence how clients call the prompt surface.",
          "properties": {
            "id": {
              "type": "string",
              "description": "Host's library identifier exposed at `GET /v1/prompts` (filterable via the structured `PromptRef.libraryId` field). Single-library hosts MAY omit this."
            },
            "renderEndpoint": {
              "type": "string",
              "format": "uri-reference",
              "description": "Absolute or relative URI of the `:render` endpoint. Defaults to `/v1/prompts:render`. Useful for hosts mounting the surface under a prefix."
            },
            "maxRenderRequestBytes": {
              "type": "integer",
              "minimum": 1,
              "description": "Hard cap on `POST /v1/prompts:render` request body size."
            }
          }
        },
        "defaults": {
          "type": "object",
          "additionalProperties": false,
          "description": "RFC 0029 §B. Per-kind host-default PromptRefs that apply at resolution chain layer 4 (`host-defaults`) per `spec/v1/prompts.md` §\"Resolution chain (normative)\". Advertised so clients can preview the full chain at edit time without dispatching a node. Hosts MAY ship per-kind defaults; the openwop spec ships none.",
          "properties": {
            "system": { "$ref": "./prompt-ref.schema.json" },
            "user": { "$ref": "./prompt-ref.schema.json" },
            "few-shot": { "$ref": "./prompt-ref.schema.json" },
            "schema-hint": { "$ref": "./prompt-ref.schema.json" }
          }
        },
        "agentBindings": {
          "type": "boolean",
          "description": "RFC 0029 §B. When `true`, the host honors `AgentManifest.promptOverrides[kind]` and `AgentManifest.promptLibraryRef` at resolution chain layer 2 (`agent-overrides` / `agent-library-default`) per `spec/v1/prompts.md` §\"Resolution chain (normative)\". When `false` or absent, layer 2 is skipped — the host applies only layers 1, 3, 4. Conformance scenarios for agent-binding resolution are gated on this flag."
        }
      },
      "additionalProperties": false
    },
    "extensions": {
      "type": "object",
      "description": "Optional per-canvas-type or per-workflow extensions (additional envelope types, override limits)."
    },
    "implementation": {
      "type": "object",
      "description": "Optional v1 server identity (name + version + vendor).",
      "properties": {
        "name": { "type": "string" },
        "version": { "type": "string" },
        "vendor": { "type": "string" }
      },
      "additionalProperties": false
    },
    "engineVersion": {
      "type": "integer",
      "minimum": 0,
      "description": "Optional v1 server engineVersion (see version-negotiation.md). Independent of `protocolVersion`."
    },
    "eventLogSchemaVersion": {
      "type": "integer",
      "minimum": 0,
      "description": "Optional v1 server eventLogSchemaVersion (per-run subcollection contract)."
    },
    "supportedTransports": {
      "type": "array",
      "items": { "type": "string", "enum": ["rest", "mcp", "a2a", "grpc"] },
      "description": "Optional v1 transport advertisement. REST is required whether or not this field is present."
    },
    "grpc": {
      "type": "object",
      "description": "RFC 0094 §H. gRPC transport advertisement per `grpc-transport.md` §\"Capability advertisement\". Optional — absent ⇒ the host exposes no gRPC transport. A host that exposes the gRPC surface advertises this block AND includes `\"grpc\"` in `supportedTransports`. REST + SSE remain exposed regardless.",
      "required": ["supported", "service", "tls"],
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Toggle — `true` when the gRPC surface is live."
        },
        "endpoint": {
          "type": "string",
          "minLength": 1,
          "pattern": "^grpcs?://",
          "description": "Full URI: `grpc://` (cleartext, intra-trusted-network only) OR `grpcs://` (TLS). Hosts SHOULD require TLS in production."
        },
        "service": {
          "type": "string",
          "const": "openwop.v1.Engine",
          "description": "Canonical service name. v1 hosts MUST use `openwop.v1.Engine` per `grpc-transport.md` §\"Field semantics\"."
        },
        "tls": {
          "type": "string",
          "enum": ["required", "optional", "disabled"],
          "description": "TLS posture. Production hosts MUST set `\"required\"`."
        }
      },
      "additionalProperties": false
    },
    "configurable": {
      "type": "object",
      "description": "Optional v1 per-run overlay schema — what `RunOptions.configurable` keys this server honors."
    },
    "nodePackRuntimes": {
      "type": "object",
      "description": "Optional v1 advertisement of node-pack runtimes the host loads. See `node-packs.md` §runtime formats and RFC 0008 (WASM ABI). Hosts that don't load packs MAY omit this block entirely. NOTE: this block intentionally uses `additionalProperties: true` (here and on the nested runtime objects) so a future RFC may add a runtime type (e.g., `python-wasm`, `js-wasm`) without a breaking-change rev of this schema; the trade is that a strict-mode validator will accept arbitrary extra keys under these objects. A future RFC that promotes the open-set fields to first-class SHOULD tighten them to `additionalProperties: false` in the same RFC.",
      "properties": {
        "wasm": {
          "type": "object",
          "description": "WASM core-module runtime per RFC 0008. Hosts that load `runtime.language: \"wasm\"` packs MUST advertise `supported: true` and at least one entry in `abiVersions[]`. `additionalProperties: true` preserved for future RFC 0008 amendments (e.g., per-pack memory accounting fields).",
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host loads RFC 0008 WASM packs."
            },
            "abiVersions": {
              "type": "array",
              "items": {
                "type": "integer",
                "minimum": 1
              },
              "uniqueItems": true,
              "minItems": 1,
              "description": "ABI versions the loader accepts. v1.1 hosts MUST include `1`. Packs declaring `openwop_abi_version()` outside this list MUST be rejected at load time per RFC 0008 §H."
            },
            "maxMemoryBytes": {
              "type": "integer",
              "minimum": 1048576,
              "maximum": 8589934592,
              "description": "Per-pack memory ceiling enforced by the host loader. Range: 1 MiB ≤ value ≤ 8 GiB. RFC 0008 §K — when a pack exceeds this, the host MUST emit `cap.breached` with `kind: \"wasm-memory\"`."
            },
            "loadedPacks": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              },
              "uniqueItems": true,
              "description": "Pack names that passed instantiation (ABI check + load). Packs rejected per RFC 0008 §H MUST NOT appear here. Hosts MAY advertise this for observability; conformance asserts rejection by absence (Track 7)."
            }
          },
          "additionalProperties": true
        },
        "wasmComponent": {
          "type": "object",
          "description": "WASM Component Model variant (WIT-defined interfaces). Reserved for hosts that load `runtime.language: \"wasm-component\"` packs. `additionalProperties: true` preserved until the Component Model spec stabilises in the v1.x line; tighten in the RFC that promotes wasm-component to a first-class runtime alongside `wasm`.",
          "properties": {
            "supported": { "type": "boolean" }
          },
          "additionalProperties": true
        }
      },
      "additionalProperties": true
    },
    "observability": {
      "type": "object",
      "description": "Optional v1 hints about emitted spans/metrics/logs. See observability.md.",
      "properties": {
        "otel": {
          "type": "object",
          "description": "OTel-specific advertisement. Hosts that emit OTLP traces/metrics MAY advertise the transport protocols they support so collectors can configure exporters accordingly. Track 11 follow-up.",
          "properties": {
            "exportProtocols": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": ["http/json", "http/protobuf", "grpc"]
              },
              "uniqueItems": true,
              "description": "OTLP export protocols the host supports emitting. `http/json` and `http/protobuf` are mandatory for hosts advertising OTel emission; `grpc` is opt-in per Track 11."
            }
          },
          "additionalProperties": true
        },
        "testSeams": {
          "type": "object",
          "additionalProperties": false,
          "description": "RFC 0034 — Conformance-only test seams under the host-extension namespace. Hosts that opt in expose introspection endpoints so cross-host conformance scenarios can verify BYOK canaries do not leak into OTel span attributes or debug-bundle exports. Production hosts SHOULD return 404 or 403 from these seams unless an env-gate (e.g., `OPENWOP_TEST_OTEL_SCRAPE=true`) is set.",
          "properties": {
            "otelScrape": {
              "type": "boolean",
              "description": "Host exposes `GET /v1/host/sample/test/otel/spans?runId=<id>` returning `{ spans: Array<{ name, attributes, events }> }` scoped to the run. Conformance verifies span attributes don't carry BYOK canaries. When advertised true, the host MUST serve a 200 response with the documented shape."
            },
            "debugBundleExport": {
              "type": "boolean",
              "description": "Host exposes `POST /v1/host/sample/test/debug-bundle/export` returning the same payload shape as `GET /v1/runs/{runId}/debug-bundle` per `spec/v1/debug-bundle.md`. Conformance verifies the serialized bundle doesn't carry BYOK canaries. When advertised true, the host MUST serve a 200 response with the documented shape."
            }
          }
        }
      },
      "additionalProperties": true
    },
    "minClientVersion": {
      "type": "string",
      "description": "Optional v1 minimum client SDK version the server interops with."
    },
    "secrets": {
      "type": "object",
      "description": "Optional v1 secret/credential resolution advertisement. Clients gate BYOK flows on this. Hosts that don't store credentials return `supported: false`.",
      "required": ["supported"],
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Host has any secret-resolution at all."
        },
        "scopes": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": ["tenant", "user", "run", "workspace"]
          },
          "uniqueItems": true,
          "description": "Subset of scopes the host implements. Tenant-scoped secrets are workspace-shared; user-scoped are per-end-user; run-scoped are ephemeral per-run; `workspace` (RFC 0046/0048) is the explicit sub-tenant scope. Appended `workspace` is additive — hosts that omit it are unaffected."
        },
        "resolution": {
          "type": "string",
          "enum": ["host-managed"],
          "description": "Resolution mode. v1.x supports only `host-managed` (clients reference stored secrets via opaque ids); reserved for future modes."
        }
      },
      "additionalProperties": false
    },
    "connections": {
      "type": "object",
      "description": "RFC 0095 (`Draft`). Connection packs — portable, registry-distributable provider definitions (`kind: \"connection\"`, `connection-pack-manifest.schema.json`) that the RFC 0045/0047 `provider` string resolves against. Only useful alongside `oauth.supported` (RFC 0047) or `credentials.supported` (RFC 0046); a host SHOULD NOT advertise this block without at least one of those.",
      "required": ["packsSupported"],
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "OPTIONAL convenience flag mirroring the other capability families. RFC 0095 keys behavior on `packsSupported`; hosts MAY also advertise `supported` for family-shape uniformity."
        },
        "packsSupported": {
          "type": "boolean",
          "description": "RFC 0095 §C. When `true`, the host installs `kind: \"connection\"` registry packs and MUST implement the §B.6 resolution contract: an RFC 0045 connector's `auth.provider` (or an RFC 0047 `host.oauth` provider string) resolves against the installed connection pack whose `provider.id` matches, with installed-vs-built-in precedence per SemVer §11 and `connection_provider_unresolved` / `connection_provider_conflict` diagnostics. When `false` or absent, connection packs are not loaded and provider resolution stays implementation-defined."
        }
      },
      "additionalProperties": false
    },
    "credentials": {
      "type": "object",
      "description": "RFC 0046 (`Draft`). Portable credential resolution + lifecycle contract — sibling to `secrets`, first-class store-at-rest + workspace sharing + two-key-overlap rotation. A pack references a credential by `{ ref, scope }` (see `credential-reference.schema.json`); the host resolves it into the node sandbox ONLY — never into inputs, persisted variables, channels, any run.* event payload, the debug bundle, or replay state (SECURITY invariant `credential-payload-redaction`). Supersedes the informal BYOK annex; the `secrets` advertisement stays valid.",
      "required": ["supported"],
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Host implements the host.credentials resolution + lifecycle contract."
        },
        "scopes": {
          "type": "array",
          "items": { "type": "string", "enum": ["user", "workspace", "tenant"] },
          "uniqueItems": true,
          "description": "Subset of resolution scopes the host implements. `workspace` is the RFC 0048 sub-tenant; `tenant` and `user` align with the `secrets.scopes` vocabulary."
        },
        "encryptionAtRest": {
          "type": "boolean",
          "description": "Host encrypts stored credential material at rest."
        },
        "rotation": {
          "type": "string",
          "enum": ["none", "two-key-overlap"],
          "description": "`two-key-overlap`: old + new credential both resolve as valid during a grace window, then the old fails with `credential_not_found` (mirrors `openwop-auth-api-key-rotation`). `none`: no rotation surface."
        },
        "sharing": {
          "type": "boolean",
          "description": "A single stored credential can be referenced by many workflows within a scope (e.g. a workspace-shared key) without copying material between references."
        }
      },
      "additionalProperties": false
    },
    "feedback": {
      "type": "object",
      "description": "RFC 0056 (`Draft`). Non-blocking human/agent quality signals (rating / correction / label / flag) attached to a run, event, or node. Annotations are a per-run side-resource recorded via `POST /v1/runs/{runId}/annotations`, listed via `GET`, and surfaced live via the `run.annotated` SSE notification — they are NOT entries in the replayable run event log (see RFC 0056 §B/§D). Hosts that do not advertise `supported: true` return `501 capability_not_provided` on the annotation endpoints.",
      "required": ["supported"],
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Host implements the RFC 0056 annotation side-store + endpoints + `run.annotated` notification."
        },
        "targets": {
          "type": "array",
          "items": { "type": "string", "enum": ["run", "event", "node"] },
          "uniqueItems": true,
          "description": "Which annotation-target granularities the host accepts. Absent = `run` only."
        },
        "signals": {
          "type": "array",
          "items": { "type": "string", "enum": ["rating", "correction", "label", "flag"] },
          "uniqueItems": true,
          "description": "Which signal kinds the host accepts. Absent = all four."
        }
      },
      "additionalProperties": false
    },
    "oauth": {
      "type": "object",
      "description": "RFC 0047 (`Draft`). Host performs OAuth 2.0 grants (authorization-code + refresh) on a user's behalf for connector nodes, stores the acquired token as a `host.credentials` (RFC 0046) entry, refreshes it transparently, and resolves it into the node sandbox as a bearer token. Token material NEVER crosses the wire (SECURITY invariant `credential-payload-redaction`). Distinct from `auth` host-authentication profiles (RFC 0010 = who is the caller; this = what third-party token a node holds).",
      "required": ["supported"],
      "properties": {
        "supported": { "type": "boolean", "description": "Host implements the host.oauth third-party token acquisition + refresh contract." },
        "grants": {
          "type": "array",
          "items": { "type": "string", "enum": ["authorization_code", "client_credentials", "refresh_token"] },
          "uniqueItems": true,
          "description": "OAuth 2.0 grant types the host performs on a node's behalf."
        },
        "providers": {
          "type": "array",
          "description": "Provider catalog the host can acquire tokens for. A connector node's `auth.provider` MUST match an `id` here.",
          "items": {
            "type": "object",
            "required": ["id"],
            "properties": {
              "id": { "type": "string", "minLength": 1, "description": "Stable provider id, e.g. `slack`, `google`." },
              "authUrl": { "type": "string", "format": "uri", "description": "Authorization endpoint." },
              "tokenUrl": { "type": "string", "format": "uri", "description": "Token endpoint." },
              "scopesSupported": { "type": "array", "items": { "type": "string" }, "description": "Scopes this provider exposes." }
            },
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": false
    },
    "authorization": {
      "type": "object",
      "description": "RFC 0049 (`Draft`). Maps an RFC 0048 principal's role to scopes (reusing the API-key scope grammar in `auth.md`) and surfaces authorization decisions as `authorization.decided` events. Fail-closed: an absent/unseeded role denies (SECURITY invariant `authorization-fail-closed`).",
      "required": ["supported"],
      "properties": {
        "supported": { "type": "boolean", "description": "Host implements the role→scope authorization-decision contract." },
        "failClosed": { "const": true, "description": "Absent/unseeded role denies; resolver errors deny. MUST be true when present — see SECURITY invariant `authorization-fail-closed`." },
        "roles": {
          "type": "array",
          "description": "Role catalog. A principal's role resolves to this scope set; a request is authorized when any role-derived scope matches the required scope per the API-key scope-match semantics.",
          "items": {
            "type": "object",
            "required": ["role", "scopes"],
            "properties": {
              "role": { "type": "string", "minLength": 1 },
              "scopes": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }
            },
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": false
    },
    "runtimeCapabilities": {
      "type": "array",
      "items": { "type": "string", "minLength": 1 },
      "uniqueItems": true,
      "description": "Optional v1 host-advertised opaque capability ids that NodeModules may declare in `NodeModule.requires`. Naming convention: dotted, domain-scoped (`chat.sendPrompt`, `canvas.write`, `secrets.byok`). Provider value shapes are documented per-capability alongside consumers, NOT in the protocol package — the protocol owns the *check*, not domain provider contracts. A client that submits a workflow whose nodes declare a `requires` entry SHOULD first verify the host advertises that capability; a host that lacks a capability MUST refuse to dispatch nodes that declare it, terminating the run with `RunSnapshot.error.code = 'capability_not_provided'`. See `capabilities.md` §\"Runtime capabilities\"."
    },
    "multiAgent": {
      "type": "object",
      "description": "RFC 0037 — Multi-agent execution model + handoff state machine. Hosts that advertise implement the supervisor→dispatch→harvest loop + the 4-state handoff state machine + the `core.workflowChain.event` emission contract per spec/v1/multi-agent-execution.md. Absent block = host implements RFCs 0006/0007/0022 individually with implementation flexibility on integration semantics; conformance scenarios gating on this flag soft-skip on absence.",
      "additionalProperties": false,
      "properties": {
        "executionModel": {
          "type": "object",
          "additionalProperties": false,
          "required": ["supported", "version"],
          "if": {
            "properties": { "tier": { "const": "experimental" } },
            "required": ["tier"]
          },
          "then": {
            "required": ["experimentalUntil"]
          },
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host implements the execution loop + handoff state machine per spec/v1/multi-agent-execution.md §\"Execution loop\" + §\"Handoff state machine\". When true, the host MUST emit `core.workflowChain.event` records on every handoff transition per the §\"Transition events\" table."
            },
            "tier": {
              "type": "string",
              "enum": ["stable", "experimental"],
              "default": "stable",
              "description": "RFC 0042 — stability claim for this capability advertisement. `stable` (the default when absent) means the host commits to the wire shape across v1.x minors. `experimental` means the host advertises the surface as a preview; the wire shape MAY shift compatibly without notice until the underlying RFC graduates to `Accepted` and the host re-advertises as `stable`. Hosts MUST omit the field for capabilities whose underlying RFC is already `Accepted`."
            },
            "experimentalUntil": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "RFC 0042 §B — required when `tier` is `experimental`. ISO-8601 `YYYY-MM-DD` no more than 12 months past the discovery response date. Reaching this date without graduating the underlying RFC to `Accepted` MUST result in the host either flipping tier to `stable` OR retracting the capability advertisement (or — with an open deprecation RFC — extending with a new `experimentalUntil` per §B sub-block 2)."
            },
            "version": {
              "type": "integer",
              "minimum": 1,
              "maximum": 6,
              "description": "Profile version. 1 = Phase 1 (execution-loop framework + planner→worker handoff). 2 = Phase 2 (confidence-floor escalation + agent-memory lifecycle, RFC 0039). 3 = Phase 3 (cross-host causation, RFC 0040). 4 = Phase 4 (replay determinism under nondeterministic models, RFC 0041). 5 = Phase 5 (stateful agent-loop lifecycle — per-iteration workspace+memory snapshot inputs, the observable `iteration` counter on `runOrchestrator.decided`, and stateful HITL resume, RFC 0061). 6 = Phase 6 (verifier/critic turn — the `agent.verified` event + `successCriteria` on the `terminate` decision, RFC 0090). A host advertising `version: N` MUST implement all phases 1..N additively."
            },
            "verifier": {
              "type": "object",
              "additionalProperties": false,
              "required": ["supported"],
              "description": "RFC 0090 (`version >= 6`). The verifier/critic turn: the host emits `agent.verified` over a prior result and (optionally) gates commit on the verdict. Absent ⇒ no verifier turn; conformance scenarios soft-skip.",
              "properties": {
                "supported": { "type": "boolean", "description": "Host emits `agent.verified` and honors RFC 0090 §A. Applies only when `version >= 6`." },
                "gating": { "type": "boolean", "description": "Host enforces the RFC 0090 §B commit-gating contract: a `fail` verdict blocks merge/terminate (fail-closed, composing RFC 0063); `revise` routes back to an actor turn. Absent/false ⇒ the verdict is observability-only." }
              }
            },
            "confidenceEscalationFloor": {
              "type": "number",
              "minimum": 0.5,
              "maximum": 1.0,
              "description": "RFC 0039 §A. Operator-declared confidence floor at or above the spec floor of 0.5; when an OrchestratorDecision carries `confidence` below this floor, the host MUST escalate via a `clarify` or `escalate` interrupt instead of executing the decision. Absent: the spec floor 0.5 applies. Values < 0.5 are non-conformant; values > 1.0 are nonsense. Applies only when `version >= 2`."
            },
            "confidenceEscalationInterruptKind": {
              "type": "string",
              "anyOf": [
                { "const": "clarification" },
                { "const": "approval" },
                { "pattern": "^x-host-[a-z][a-z0-9-]*-[a-z][a-z0-9-]*$" }
              ],
              "description": "RFC 0044 — the literal `interrupt.kind` the host emits when escalating a below-floor confidence decision per RFC 0039 §A. `clarification` and `approval` are the canonical values matching the clarify-OR-approval choice in RFC 0039 §A; vendor-extension kinds use the canonical host-extension namespace `^x-host-<host>-<kind>$` per `spec/v1/host-extensions.md` §\"Canonical prefixes\". When advertised, hosts MUST emit an interrupt of the advertised kind on every confidence-escalation event AND the host's downstream `interrupt.md` mapping determines the `waiting-*` terminal status. Absent: conformance assumes the host uses one of the two canonical kinds (the relaxed assertion accepts either). Hosts using vendor kinds MUST also publish a non-normative kind-mapping document per RFC 0044 §C."
            },
            "crossChildMemoryConcurrency": {
              "type": "string",
              "enum": ["strict", "advisory"],
              "description": "RFC 0039 §B. Cross-child memory-write concurrency posture when version >= 2. `strict` (default when absent): the host serializes concurrent writes from sibling dispatched children to the parent's shared memory scope. `advisory`: the host opts out of the serialization MUST and documents last-write-wins semantics out-of-band. Hosts that choose advisory SHOULD also advertise their resolution rule under `crossChildMemoryConcurrencyResolution` (reserved field; follow-up clarification)."
            },
            "crossHostCausation": {
              "type": "object",
              "description": "RFC 0040 §D — Phase 3 cross-host causation linking. When advertised, the host honors the cross-host `causationId` extension (a `causationHostId` field on event payloads pointing at the originating host), W3C `traceparent` propagation across MCP + A2A composition boundaries, and (when `ancestryEndpointSupported: true`) the `GET /v1/runs/{runId}/ancestry` cross-host parent-chain endpoint. Hosts advertising `version: 3` MUST advertise this sub-block with `supported: true` and a stable `hostId`.",
              "additionalProperties": false,
              "required": ["supported"],
              "properties": {
                "supported": {
                  "type": "boolean",
                  "description": "Host implements RFC 0040 Phase 3 cross-host causation contracts."
                },
                "hostId": {
                  "type": "string",
                  "minLength": 1,
                  "description": "Stable identifier for this host instance, used as the `causationHostId` value on cross-host events. SHOULD be a URL or DNS-style identifier (e.g., `myndhyve.ai/workflow-runtime`); the protocol does not normate a stricter format."
                },
                "ancestryEndpointSupported": {
                  "type": "boolean",
                  "description": "Host serves `GET /v1/runs/{runId}/ancestry` returning the cross-host parent chain per RFC 0040 §C. Optional even when crossHostCausation.supported is true — hosts that emit `causationHostId` but don't expose the ancestry-walking endpoint still satisfy the per-event chaining contract; this flag advertises the additional endpoint."
                }
              }
            },
            "replayDeterminism": {
              "type": "object",
              "description": "RFC 0041 §D — Phase 4 replay determinism. When advertised, the host honors the LLM cache-key recipe in `replay.md` §\"LLM cache-key recipe\" as NORMATIVE (graduated from informative for version >= 4), emits `replay.divergedAtRefusal` events + fails with `replay_diverged_at_refusal` on refusal-divergence (RFC 0041 §B), and guarantees observable-output-sequence determinism per RFC 0041 §C. Hosts advertising `version: 4` MUST advertise this sub-block with `supported: true`.",
              "additionalProperties": false,
              "required": ["supported"],
              "properties": {
                "supported": {
                  "type": "boolean",
                  "description": "Host implements RFC 0041 Phase 4 replay-determinism contracts."
                },
                "llmCacheKeyRecipe": {
                  "type": "string",
                  "anyOf": [
                    { "const": "spec-rfc-0041" },
                    { "pattern": "^x-host-[a-z][a-z0-9-]*-[a-z][a-z0-9-]*$" }
                  ],
                  "description": "The LLM cache-key recipe the host honors. `spec-rfc-0041` = the canonical recipe in `replay.md` §\"LLM cache-key recipe\" §A + §B + §C. Vendor-specific recipes use the canonical host-extension namespace string matching `^x-host-<host>-<recipe-name>$` per `spec/v1/host-extensions.md` §\"Canonical prefixes\"; the matching algorithm MUST be documented at the host's discovery doc."
                },
                "refusalDivergenceEmission": {
                  "type": "boolean",
                  "description": "Host emits `replay.divergedAtRefusal` events + fails replay with `error.code: replay_diverged_at_refusal` per RFC 0041 §B. Hosts advertising `version: 4` MUST set this to `true`."
                }
              }
            },
            "statefulResume": {
              "type": "boolean",
              "description": "RFC 0061 (`version >= 5`). When `true`, a `clarify`/`escalate` HITL suspend resumes the execution loop at the SAME iteration — the `runOrchestrator.decided.iteration` counter does not reset or skip — with the per-iteration memory (RFC 0039 MAE-3) + workspace (RFC 0059) snapshot lineage intact, so a mid-loop human interrupt does not lose progress. A distinct claim from plain replay re-entrancy (deterministic replay of a completed prefix); this is about a LIVE suspend preserving the counter. Omitted by hosts on `version < 5`."
            },
            "transcriptWindow": {
              "type": "integer",
              "minimum": 1,
              "description": "RFC 0061 (`version >= 5`). Host-advertised count of recent event-log entries the host feeds each orchestrator turn as the iteration's transcript input (§C input 3). Advertise-and-honor; not a fixed wire constant. Absent ⇒ the host does not bound the transcript window on the wire."
            }
          }
        }
      }
    },
    "modelCapabilities": {
      "type": "object",
      "description": "RFC 0031. Host implements model-capability gating + (optional) substitution per `NodeModule.requiredModelCapabilities` + `NodeModule.fallbackModel`. Distinct from `runtimeCapabilities` which gates on HOST capabilities — `modelCapabilities` gates on MODEL capabilities (structured-output support, discriminator-enum support, long-context, native reasoning, function-calling). Absent block = no advertisement; NodeModules' `requiredModelCapabilities` are treated as opaque metadata and dispatch proceeds without checks.",
      "additionalProperties": false,
      "required": ["supported"],
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Host honors `NodeModule.requiredModelCapabilities` and emits `model.capability.substituted` / `model.capability.insufficient` events per RFC 0031 §B + §D. When `false` or absent, the fields are treated as opaque metadata and dispatch proceeds without capability checks."
        },
        "advertised": {
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^([a-z][a-z0-9-]*|x-host-[a-z][a-z0-9-]*-[a-z][a-z0-9-]*)$"
          },
          "uniqueItems": true,
          "description": "Capability identifiers the host's active model advertises. Clients MAY introspect this at install time to determine whether their NodeModules' `requiredModelCapabilities` are satisfiable without fallback. Spec-reserved identifiers per RFC 0031 §C + RFC 0055: `structured-output`, `discriminator-enum`, `long-context`, `reasoning` (model-native thinking-tokens), `function-calling`, `vision-input` (model accepts image content in the prompt), `audio-input` (model accepts audio content), `audio-output` (model emits audio content), `image-output` (model emits images directly in its completion, distinct from the host-side `aiProviders.imageGeneration` tool surface). This is an open, pattern-validated registry — NOT a closed enum; growth requires an RFC. Host-private extensions MUST prefix with `x-host-<host>-`."
        },
        "substitutionSupported": {
          "type": "boolean",
          "description": "Host honors `NodeModule.fallbackModel` substitution per RFC 0031 §B step 3. When `false` or absent, hosts MUST refuse to dispatch (step 4) on any unmet capability — they MUST NOT attempt fallback even when the field is declared on the NodeModule. Recursive fallback is NOT permitted (RFC 0031 §\"Unresolved questions\" #3)."
        }
      }
    },
    "providerUsage": {
      "type": "object",
      "description": "RFC 0026. Hosts that emit `provider.usage` events after every LLM provider invocation per RFC 0026 §B. The event carries per-call token counts in the durable event log; cost rollup remains in `RunSnapshot.metrics.openwopCost`. Old hosts ignore.",
      "properties": {
        "supported": { "type": "boolean", "description": "Host emits one `provider.usage` event per LLM provider call." },
        "costEstimates": { "type": "boolean", "description": "When true, the host includes `costEstimateUsd` on `provider.usage` events using its internal rate table. When false/absent, only token counts are emitted." },
        "currency": { "type": "string", "pattern": "^[A-Z]{3}$", "description": "Default ISO 4217 currency for `costEstimateUsd` values. When absent, USD is assumed." }
      },
      "required": ["supported"],
      "additionalProperties": false
    },
    "aiProviders": {
      "type": "object",
      "description": "Optional v1 companion to `secrets`. Advertises which AI providers the host's AI-proxy can route to and which permit BYOK.",
      "properties": {
        "supported": {
          "type": "array",
          "items": { "type": "string", "minLength": 1 },
          "uniqueItems": true,
          "description": "Provider ids the host's AI-proxy can route to. Conventional ids (RFC 0067 §C recommended vocabulary — advisory, not a closed set): `anthropic`, `openai`, `gemini`, `vertex`, `bedrock`, `mistral`, `cohere`, `openrouter`, `litellm`, `together`, `huggingface`, `qwen`, `ollama`, `vllm`. Hosts MAY add vendor-prefixed extensions; clients MUST tolerate unknown ids."
        },
        "byok": {
          "type": "array",
          "items": { "type": "string", "minLength": 1 },
          "uniqueItems": true,
          "description": "Subset of `supported` for which BYOK is permitted. Empty array → all calls use platform-managed keys; non-empty → clients MAY pass `ai.credentialRef` in `RunOptions.configurable` for matching providers."
        },
        "input": {
          "type": "object",
          "additionalProperties": false,
          "description": "RFC 0091. Multimodal PERCEPTION input on `ctx.callAI` — the modalities a `callAI` message ContentPart may carry as model INPUT. Absent ⇒ text-only (today's behavior); a `string` message content is always valid. Distinct from `imageGeneration` (output) and the `ai-envelope.md` media emission types (output).",
          "properties": {
            "modalities": {
              "type": "array",
              "uniqueItems": true,
              "items": { "type": "string", "enum": ["text", "image", "audio", "document"] },
              "description": "Input modalities the host's `callAI` accepts as ContentParts. `text` is implicit even when omitted. A ContentPart whose `type` is not advertised here MUST be rejected with `unsupported_modality` (never silently dropped)."
            },
            "maxBytesPerPart": { "type": "integer", "minimum": 1, "description": "Optional host cap on a single inline (`data`) or `mediaRef` part." }
          }
        },
        "authModes": {
          "type": "object",
          "description": "RFC 0067 (`Draft`). Optional per-provider advertisement of HOW the host expects a provider's credential to be supplied. Keys are provider ids appearing in `supported`; values are the auth modes the host honors for that provider. Absent ⇒ no advertisement: a provider in `byok` defaults to `apiKey` semantics (client passes `ai.credentialRef`); a provider in `supported` but not in `byok` defaults to `none` (platform-managed). This map only DESCRIBES the supply mechanism — `oauth-pkce`/`oauth-device` flow mechanics compose RFC 0047 `host.oauth` and resolve credentials by `ref` (RFC 0046), never on `ai.credentialRef`. A provider with `apiKey` MUST appear in `byok`; a provider whose modes are exactly `[\"none\"]` MUST NOT appear in `byok`. Consumers MUST ignore an auth mode they don't recognize rather than reject the discovery doc.",
          "additionalProperties": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": ["apiKey", "oauth-pkce", "oauth-device", "none"]
            }
          }
        },
        "policies": {
          "type": "object",
          "description": "Optional v1 host-side policy enforcement modes for per-provider gating. Omitted → no enforcement; clients see only `optional` semantics. When present, MUST declare `modes` — an empty `{}` is not a valid third state. See `capabilities.md` §`aiProviders.policies`.",
          "required": ["modes"],
          "properties": {
            "modes": {
              "type": "array",
              "items": { "type": "string", "enum": ["disabled", "optional", "required", "restricted"] },
              "uniqueItems": true,
              "description": "Subset of policy modes this host can enforce. `disabled` = provider may not be used; `optional` = no restriction (default); `required` = BYOK required; `restricted` = model must match the policy's `allowedModels` glob list. Hosts MAY support a subset; clients MUST tolerate any subset."
            },
            "scopes": {
              "type": "array",
              "items": { "type": "string", "minLength": 1 },
              "uniqueItems": true,
              "description": "Resolution layers the host evaluates. Conventional ids: `workspace`, `project`, `canvas-type`. Precedence is host-defined and SHOULD be documented per-deployment."
            },
            "errorCode": {
              "type": "string",
              "minLength": 1,
              "description": "Wire-format error code returned on denial. Defaults to `provider_policy_denied`. Reserved for hosts that need a vendor-prefixed alias."
            }
          },
          "additionalProperties": false
        },
        "maxInlineMediaBytes": {
          "type": "integer",
          "minimum": 0,
          "default": 262144,
          "description": "RFC 0055 §C rule 2 — optional cap (bytes) on inline base64 in `media.*` envelope payloads. A `media.{image,audio,file}` asset above this size MUST be served by a tenant-scoped `url` reference rather than inlined (bounds event-log + replay-payload size). Default 256 KiB (262144) when absent. A host MAY set 0 to force URL references for all emitted media."
        }
      },
      "additionalProperties": false
    },
    "testing": {
      "type": "object",
      "description": "Testing-mode capabilities advertised to clients (closes F1). Lets conformance suites + dev clients discover which mock providers the server supports and which API-key prefix denotes test keys (since mock providers are test-keys-only).",
      "properties": {
        "mockProviders": {
          "type": "array",
          "items": { "type": "string", "minLength": 1 },
          "uniqueItems": true,
          "description": "Mock-provider IDs this server recognizes via `RunOptions.configurable.mockProvider.id`. Servers claiming OpenWOP v1.0 conformance MUST include `stream-text`. Other canonical providers (`tool-calls`, `error`, `usage-only`) are recommended. Implementations MAY add their own (vendor-prefixed)."
        },
        "testKeyPrefix": {
          "type": "string",
          "description": "API-key prefix that marks a key as test-mode. Mock providers and other test-only surfaces gate on this prefix.",
          "minLength": 1,
          "maxLength": 32
        },
        "forceEngineVersionRange": {
          "type": "object",
          "description": "Range of engine versions the server can be forced into via the `X-Force-Engine-Version` request header (test-keys-only). Used by the conformance suite to verify forward-compat fold-best-effort across the version-negotiation matrix. Closes F5.",
          "required": ["min", "max"],
          "properties": {
            "min": { "type": "integer", "minimum": 0, "description": "Lowest forceable engine version. Typically (current - 1) so back-compat is exercised." },
            "max": { "type": "integer", "minimum": 0, "description": "Highest forceable engine version. Typically (current + 1) so forward-compat is exercised." }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": true
    },
    "conformance": {
      "type": "object",
      "description": "Conformance-only capability block (RFC 0023). Advertises which conformance-only typeIds (`core.conformance.*`) the host has registered. Hosts that don't ship conformance-only typeIds omit this block entirely. Production deployments SHOULD omit this block; conformance-only typeIds carry test hooks (e.g., `mockReasoning`, `mockConfidence`) that MUST NOT be reachable from production tenants.",
      "properties": {
        "mockAgent": {
          "type": "boolean",
          "description": "RFC 0023 §B.2. When `true`, the host has registered the `core.conformance.mock-agent` typeId. The scenarios `agentReasoningEvents.test.ts` and `agentConfidenceEscalation.test.ts` rely on the typeId being reachable. Hosts that register the typeId only for workflow ids matching the conformance fixture prefix (`conformance-*`) and refuse it for other tenants MAY still advertise `true` — the advertisement says only that the typeId is reachable from the conformance suite, not that it is reachable from arbitrary workflows."
        },
        "certificationBundleUrl": {
          "type": "string",
          "format": "uri",
          "description": "OPTIONAL (RFC 0089). URL of the host's most recent conformance certification bundle (`conformance-certification-bundle.schema.json`) — a machine-readable attestation binding this host's claimed profiles to the reproducible run that substantiates them. Omitting it is fully conformant; clients MUST tolerate its absence."
        }
      },
      "additionalProperties": false
    },
    "fixtures": {
      "type": "array",
      "items": { "type": "string", "minLength": 1 },
      "uniqueItems": true,
      "description": "Optional v1 fixture workflow IDs the host has seeded. The conformance suite uses this list to decide which fixture-dependent scenarios run vs. skip. Each ID matches a stub at `node_modules/@openwop/openwop-conformance/fixtures/{id}.json`. Hosts MAY advertise vendor-prefixed IDs; clients MUST tolerate unknown IDs. Empty array or absent means the host advertises no fixtures."
    },
    "agents": {
      "type": "object",
      "description": "Multi-Agent Shift capability block (Phases 1-6). Hosts that implement any multi-agent surface declare it here; pre-MAS hosts omit the block entirely (consumers treat as 'no agent support'). Each field gates a specific conformance scenario class; conformance scenarios skip honestly when the host's advertisement doesn't claim the capability.",
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Master switch for agent identity (Phase 1). When `true`, host accepts run-level `RunSnapshot.agent` / `runOrchestrator` fields, emits `agent.*` events, and honors the confidence-escalation contract."
        },
        "profile": {
          "type": "string",
          "description": "Optional named capability profile (e.g., `wop-agents-phase-1-skeleton` for identity-only, `wop-agents-full` for orchestrator+dispatch+memory+conversation). Clients pattern-match on profile to gate feature usage."
        },
        "modelClasses": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": ["reasoning", "tool-using", "chat", "code", "vision", "multimodal", "embedding", "classification", "retrieval"]
          },
          "uniqueItems": true,
          "description": "Optional list of `AgentRef.modelClass` values this host supports for hosted agents (Phase 2). Pack manifests whose `modelClass` is not in this list MUST refuse install with `unsupported_model_class`."
        },
        "orchestratorPattern": {
          "type": "string",
          "description": "Optional orchestration pattern (Phase 2). Canonical: `single`, `delegate`, `delegate.smart`. Vendor extensions ship under `vendor.<host>.<pattern>`."
        },
        "memoryBackends": {
          "type": "array",
          "items": { "type": "string", "enum": ["long-term"] },
          "uniqueItems": true,
          "description": "Optional list of memory backends (Phase 3). `long-term` means the host implements `ExecutionHost.memory` against a durable store with the SR-1 redaction invariant intact end-to-end. Hosts that don't wire `MemoryAdapter` omit this field."
        },
        "memoryConsolidation": {
          "type": "object",
          "description": "RFC 0068 (`Draft`). Background reconciliation of LONG-TERM memory (merge/dedup/supersede/strengthen) — distinct from RFC 0062 token-budgeted distillation of TRANSACTIONAL memory. A host advertising this emits `agent.memory.consolidated` (content-free) after a consolidation pass. Requires `agents.memoryBackends` to include `long-term`. SR-1 carry-forward + CTI-1 (RFC 0004) hold across the pass. Hosts that omit this block do not consolidate; the conformance scenarios skip cleanly.",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "REQUIRED when the block is present. When `true`, the host performs background consolidation over long-term memory and emits `agent.memory.consolidated`."
            },
            "schedule": {
              "type": "string",
              "enum": ["host-managed", "scheduled", "on-demand"],
              "description": "How a consolidation pass is initiated. `host-managed`: a host-internal cadence clients do not control (default when absent and supported:true). `scheduled`: bound to a `capabilities.scheduling` (RFC 0052) trigger. `on-demand`: the host runs a pass when explicitly requested. A host MAY honor more than one path but advertises the primary."
            }
          }
        },
        "commitments": {
          "type": "object",
          "description": "RFC 0068 (`Draft`). Inferred STANDING commitments — durable, memory-derived intentions the host promotes into a time- or predicate-gated arm that fires a run later, without a fresh user turn. When an arm fires the host emits `commitment.fired` (content-free — the intention text lives in SR-1-redacted memory). Composes RFC 0052 (time arms) / RFC 0060 (predicate arms) for the fire substrate. Hosts that omit this block do not infer commitments; the conformance scenario skips cleanly.",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "REQUIRED when the block is present. When `true`, the host infers standing commitments from memory and emits `commitment.fired` when one fires."
            },
            "fireConditions": {
              "type": "array",
              "items": { "type": "string", "enum": ["time", "predicate"] },
              "uniqueItems": true,
              "description": "Which fire-condition kinds the host supports. `time` composes RFC 0052 scheduling; `predicate` composes RFC 0060 heartbeat. Absent ⇒ `['time']`."
            }
          }
        },
        "orchestrator": {
          "type": "boolean",
          "description": "Phase 5. When `true`, host advertises that it implements the `core.orchestrator.supervisor` node typeId AND honors the conservative-path suspend semantics (CP-1: low-confidence suspend via `node.suspended { reason: 'low-confidence' }`)."
        },
        "dispatch": {
          "type": "boolean",
          "description": "Phase 6. When `true`, host advertises that it implements the `core.dispatch` Core typeId AND honors the conservative-path commitment CP-2 (`core.dispatch` MUST NOT mutate the run's DAG mid-run). Implies (but does NOT require) `agents.orchestrator: true`."
        },
        "manifestRuntime": {
          "type": "object",
          "description": "RFC 0070. Agent-manifest runtime floor — the minimal tier that makes a published agent pack (RFC 0003) runnable. When `supported: true`, the host implements RFC 0003 `installAgents`: it loads each installed pack's `agents[]` into an in-process AgentRegistry, resolves `systemPromptRef` + `handoff.*SchemaRef` from the tarball at install (RFC 0003 §C/§D), and can DISPATCH a manifest agent on the existing `core.dispatch`/orchestrator loop (RFC 0007/0037/0061). Does NOT imply swarm/consensus (`host.agentRuntime`), long-term memory (`agents.memoryBackends`), or crews beyond `agents.dispatch`. A host advertising `host.agentRuntime: supported` is treated as also satisfying this flag (RFC 0070 §B). Hosts that omit this block do not instantiate manifest agents (today's default).",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "REQUIRED when the block is present. When `true`, the host loads + dispatches pack-declared manifest agents. A host with `supported: true` MUST enforce each dispatched agent's `toolAllowlist` (RFC 0002 §A14) and MUST NOT leak BYOK plaintext into `agent.*` events or handoff payloads (SR-1)."
            },
            "handoffValidation": {
              "type": "boolean",
              "default": false,
              "description": "When `true`, the host validates inbound task payloads against the agent's `handoff.taskSchemaRef` before dispatch and outbound results against `handoff.returnSchemaRef` before persistence (RFC 0003 §D). When `false`/absent, manifests carrying `handoff` schemas are dispatched with opaque payloads."
            },
            "installScope": {
              "type": "string",
              "enum": ["host", "tenant"],
              "default": "host",
              "description": "RFC 0074. Scope at which manifest agents are installed/approved and therefore enumerated by GET /v1/agents. 'host' (default): a single host-global inventory; the endpoint returns the same set for every caller (RFC 0072's original behavior). 'tenant': agents are installed per tenant·workspace (RFC 0048 owner triple); GET /v1/agents returns ONLY the agents available to the authenticated principal's workspace, and an unapproved/unknown agent 404s — the surface never discloses another tenant's inventory. Does not change dispatch (RFC 0072 §B, owner-triple-scoped POST /v1/runs) or any floor safety guarantee (toolAllowlist/systemPromptRef/SR-1 stay mandatory regardless of scope)."
            }
          }
        },
        "liveRuntime": {
          "type": "object",
          "description": "RFC 0077. The host executes manifest agents against LIVE models and tools (not the deterministic RFC 0070 sample floor) per the normative AgentManifest→live-run mapping (`multi-agent-execution.md` §\"Live manifest dispatch\"), and emits the `agent.invocation.started`/`agent.invocation.completed` content-free bracket around the existing `agent.*` family. REQUIRES `agents.manifestRuntime.supported: true` — `liveRuntime` is a strict superset of the floor. The floor's mandatory safety guarantees (toolAllowlist enforcement, handoff inbound validation, tenant scoping, untrusted-model-output handling, fail-closed per-tool authorization) stay unconditional under `liveRuntime`. Hosts that omit this block run the floor only; the behavioral conformance scenarios skip cleanly.",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "REQUIRED when present. When `true`, the host performs live manifest dispatch per the §B mapping and emits the `agent.invocation.*` bracket. Gated on `agents.manifestRuntime.supported: true`."
            },
            "structuredOutput": {
              "type": "boolean",
              "description": "When `true`, the host validates the terminal result against the agent's `handoff.returnSchemaRef` and fails the run with a structured-output error on a non-conforming result rather than shipping it. Absent ⇒ `false` (runs live but does not enforce `returnSchemaRef`)."
            },
            "confidenceEscalation": {
              "type": "boolean",
              "description": "When `true`, the host honors `AgentManifest.confidence.defaultThreshold` and triggers the RFC 0002 §F escalation contract when an `agent.decided` confidence falls below the effective threshold, rather than silently accepting the decision. Absent ⇒ `false`."
            },
            "sources": {
              "type": "array",
              "uniqueItems": true,
              "items": { "type": "string", "enum": ["workflow-node", "run-api", "chat-mention"] },
              "description": "Which invocation entry points the host exposes for live manifest dispatch. `workflow-node`: an agent step inside a workflow run (RFC 0072 §B); `run-api`: an agent as the root of POST /v1/runs; `chat-mention`: a chat @agent invocation mapped onto the run surface. Enum membership is NOT mandatory — a host with no chat surface simply omits `chat-mention`. Absent ⇒ `['workflow-node']` (the RFC 0072 §B normative path). All advertised sources MUST emit the identical `agent.invocation.*` + `agent.*` event family."
            }
          }
        },
        "evalSuite": {
          "type": "object",
          "description": "RFC 0081. The host runs portable `agent-eval-suite.schema.json` suites as eval runs (a `mode: \"eval\"` projection over POST /v1/runs, RFC 0081 §B), emits the `eval.started`/`eval.scored`/`eval.completed` content-free family, and terminates with an `eval-summary.schema.json` scorecard. Composes RFC 0026 (per-task cost), RFC 0054 (regression baseline diff), RFC 0056 (human override of an auto-score). Hosts that omit this block reject `mode: \"eval\"` with 501; the behavioral conformance scenario soft-skips. The summary + events are content-free (SECURITY invariant `eval-summary-no-content-leak`).",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "REQUIRED when present. When `true`, the host accepts `mode: \"eval\"` runs against an `evalSuiteRef`, scores each task, and serves `GET /v1/runs/{runId}/eval-summary`."
            },
            "modes": {
              "type": "array",
              "uniqueItems": true,
              "items": { "type": "string", "enum": ["golden", "rubric", "adversarial", "regression", "live-shadow"] },
              "description": "Which eval modes the host actually implements (RFC 0081 §D closed vocabulary). Truthful advertisement (RFC 0031): a host advertises ONLY the modes it gates on; a suite requesting an unadvertised mode is rejected at run-create with `400 validation_error`. Absent ⇒ no modes (the host advertises `supported` but gates nothing — effectively shape-only)."
            },
            "maxTasksPerSuite": {
              "type": "integer",
              "minimum": 1,
              "description": "MAY. Host ceiling on tasks per suite; a suite exceeding it is rejected at run-create (the RFC 0058 §A clamp pattern)."
            },
            "maxCostUsdPerSuite": {
              "type": "number",
              "minimum": 0,
              "description": "MAY. Host ceiling on total eval-run cost; composes RFC 0084 budget enforcement when advertised."
            }
          }
        },
        "deployment": {
          "type": "object",
          "description": "RFC 0082. The host implements an agent deployment lifecycle: per-(agentId, version) deployment records with the seven-state machine (draft/test/staged/active/paused/deprecated/rolled-back), named-channel binding (`agentId@channel` / `@latest` resolved + pinned per-(run, agentId, channel) at first resolution per §B), optional canary traffic-split, a rollback pointer, the content-free `deployment.*` audit events, and the `POST /v1/agents/{agentId}/deployments` promotion contract composing RFC 0049 (`deploy:*` fail-closed scopes) + RFC 0051 (approvalGate) + RFC 0081 (`requiredEval`). Hosts that omit this block reject a `channel`-bearing `AgentRef` with `validation_error` and 501 the deployment endpoint. The promotion endpoint + behavioral lifecycle scenario + reference-host store land at Active → Accepted.",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "REQUIRED when present. When `true`, the host resolves `AgentRef.channel` bindings, serves deployment records, and accepts promotion transitions."
            },
            "channels": {
              "type": "array",
              "uniqueItems": true,
              "items": { "type": "string", "minLength": 1 },
              "description": "The named channels the host resolves (e.g. `[\"stable\", \"canary\", \"latest\"]`). Truthful advertisement (RFC 0031): a `channel` not in this list resolves to no version and fails the run with `no_active_deployment`."
            },
            "canary": {
              "type": "boolean",
              "description": "When `true`, the host implements canary traffic-split (a per-run §B draw assigns the run to one of the channel's active versions by `canaryPercent`). When `false`/absent, the host MUST reject any `canaryPercent < 100`."
            },
            "rollback": {
              "type": "boolean",
              "description": "When `true`, the host implements the `rollbackPointer` recovery path (active→rolled-back restoring a prior version to active)."
            },
            "states": {
              "type": "array",
              "uniqueItems": true,
              "items": { "type": "string", "enum": ["draft", "test", "staged", "active", "paused", "deprecated", "rolled-back"] },
              "description": "The subset of the seven lifecycle states the host implements. Truthful advertisement (RFC 0031): the host MUST reject a transition into a state not advertised here."
            }
          }
        },
        "roster": {
          "type": "object",
          "description": "RFC 0086. The host maintains a standing agent roster: named, tenant-scoped agent INSTANCES (the 'digital-twin employee') that reference a manifest/deployment and own a workflow portfolio, discoverable via GET /v1/agents/roster, with trigger-fired portfolio runs attributed to the member via the content-free `roster.run.initiated` event. REQUIRES `agents.manifestRuntime.supported: true` (a roster entry instantiates a manifest agent). Triggers compose RFC 0052 (schedule) + RFC 0083 (durable work-item bridge) — no new WorkflowTrigger.type; the concrete work surface (a Kanban board) stays a host/vendor extension (§E). Hosts that omit this block do not maintain a roster (the roster reads 501). The roster-management endpoints + behavioral attribution scenario + reference-host store land at Active → Accepted.",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "REQUIRED when present. When `true`, the host serves the standing roster, projects it on the inventory, and emits `roster.run.initiated` on trigger-fired portfolio runs."
            },
            "installScope": {
              "type": "string",
              "enum": ["host", "tenant"],
              "description": "RFC 0074 carry-forward. `host`: a single global roster. `tenant`: roster entries are scoped per owner triple; GET /v1/agents/roster returns only the caller's entries and a cross-tenant entry 404s. MUST equal `agents.manifestRuntime.installScope` (a roster cannot be host-global while its manifests are tenant-scoped, or vice-versa)."
            },
            "portfolioTriggerSources": {
              "type": "array",
              "uniqueItems": true,
              "items": { "type": "string", "minLength": 1 },
              "description": "Which RFC 0052/0083 trigger sources fire portfolio runs on this host (e.g. `[\"schedule\", \"queue\", \"webhook\"]`). Truthful advertisement (RFC 0031): a source not listed does not fire portfolios here."
            }
          }
        },
        "orgChart": {
          "type": "object",
          "description": "RFC 0087. The host maintains a tenant-scoped, DESCRIPTIVE org-chart over RFC 0086 roster members: departments + roles with acyclic `reportsTo` edges + a derived responsibility roll-up, discoverable via GET /v1/agents/org-chart. The load-bearing guarantee (§B `org-position-no-authority-escalation`): an org edge confers NO authority — it MUST NOT widen `toolAllowlist` (RFC 0002 §A14), grant an RBAC scope (RFC 0049), or bypass an approval gate (RFC 0051); org position MUST NOT be an authorization input. The schema carries no authority-bearing field, and a conformant host MUST NOT derive authority from position out-of-band. REQUIRES `agents.roster.supported: true` (the chart's members are roster entries). Hosts that omit this block have no org-chart surface (the read 501s). The org-chart-management endpoints + behavioral non-authority scenario + reference-host store land at Active → Accepted.",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "REQUIRED when present. When `true`, the host serves the tenant-scoped org-chart + the responsibility roll-up. The §B non-authority guarantee holds at every `installScope` — it is never gated, weakened, or opt-out."
            },
            "installScope": {
              "type": "string",
              "enum": ["host", "tenant"],
              "description": "RFC 0074 carry-forward. `host`: a single global chart. `tenant`: charts are scoped per owner triple; GET /v1/agents/org-chart returns only the caller's chart. SHOULD equal `agents.roster.installScope` (the members are roster entries)."
            },
            "departmentNesting": {
              "type": "boolean",
              "description": "When `true`, the host supports `parentDepartmentId` trees. When `false`/absent, the host MUST reject a non-null `parentDepartmentId` (truthful advertisement, RFC 0031)."
            },
            "responsibilityView": {
              "type": "boolean",
              "description": "When `true`, the host computes the §D responsibility roll-up (the union of a department's members' RFC 0086 portfolios) on GET /v1/agents/org-chart/{departmentId}."
            }
          }
        },
        "dispatchMapping": {
          "type": "boolean",
          "default": false,
          "description": "Phase 6.1 (RFC 0022 §A). When `true`, host honors `inputMapping` / `outputMapping` / `perWorkerInputMappings` / `perWorkerOutputMappings` on `DispatchConfig` — building child inputs from parent variables before dispatch and harvesting child variables into parent variables on completion. Implies (but does NOT require) `agents.dispatch: true`. Hosts that set `agents.dispatch: true` but omit / `false` this flag MUST refuse workflows that carry non-empty mapping fields at registration with `validation_error` + `details.requiredCapability: 'agents.dispatchMapping'`."
        },
        "reasoning": {
          "type": "object",
          "description": "Phase 1 reasoning-event verbosity configuration.",
          "properties": {
            "verbosity": {
              "type": "string",
              "enum": ["summary", "full", "off"],
              "description": "Default reasoning verbosity for `agent.reasoned` events when the run does not override via `RunOptions.configurable.reasoningVerbosity`."
            },
            "tokenLimit": {
              "type": "integer",
              "minimum": 0,
              "description": "Effective cap on reasoning trace length when verbosity is `summary`. Default 512 tokens."
            },
            "streaming": {
              "type": "boolean",
              "default": false,
              "description": "RFC 0024. When `true`, the host MAY emit `agent.reasoning.delta` events while a reasoning block is still open, in addition to the final `agent.reasoned`. Hosts that omit / `false` this flag emit only the final `agent.reasoned`. Consumers MUST tolerate both modes."
            }
          },
          "additionalProperties": false
        },
        "subRunAttestation": {
          "type": "boolean",
          "default": false,
          "description": "RFC 0063 (`Active`). When `true`, host honors the optional `outputAttestation` block on `core.subWorkflow`: computes a content checksum (RFC 8785 JCS + SHA-256, the `replay.md` recipe) over a child's harvested outputs and surfaces it as the additive optional `attestation` object on the existing `core.workflowChain.event { phase: 'output.harvested' }` (RFC 0037) BEFORE applying `outputMapping`; when the config sets `requireApproval: true`, suspends the parent via an `approval` interrupt (RFC 0051) before merge and fails closed (no `accept`/`edit-accept` ⇒ no merge). Reuses RFC 0051's `approval` kind + RFC 0049 scopes for `principalScope` — no new interrupt kind, event type, or error code. Hosts that omit / `false` this flag treat `outputAttestation` as inert (blind merge, today's behavior)."
        },
        "proposals": {
          "type": "object",
          "description": "RFC 0096 (`Active`). Reviewable learning — the host synthesizes reusable artifacts (skills/packs/templates/automations) from run/tool traces as INERT, reviewable drafts that MUST NOT influence the resolution, planning, or execution of any run until an authorized principal activates them. A host advertising this serves the `/v1/host/sample/proposals` surface (promotable to `/v1/proposals`) and emits the content-free `proposal.created` / `proposal.activated` events. Activation is delegated to RFC 0051 approval-gate or RFC 0049 RBAC — no new authorization path. On `apply` the installed artifact MUST byte-match the last-persisted `artifact` (no silent re-synthesis). Hosts that omit this block do not synthesize proposals; the conformance scenarios skip cleanly.",
          "additionalProperties": false,
          "required": ["artifactKinds", "activation"],
          "properties": {
            "artifactKinds": {
              "type": "array",
              "items": { "type": "string", "enum": ["agent-pack", "workflow-chain-pack", "prompt-template", "automation"] },
              "uniqueItems": true,
              "description": "Which reusable artifact kinds the host can propose. `agent-pack` (RFC 0003), `workflow-chain-pack` (RFC 0013), `prompt-template` (RFC 0027), `automation` (RFC 0052 scheduled job)."
            },
            "duplicationDetection": {
              "type": "boolean",
              "default": false,
              "description": "When `true`, the host populates `Proposal.duplicateOf` with an existing artifact ref the proposal restates/overlaps (the 'Curator' duplication signal)."
            },
            "activation": {
              "type": "string",
              "enum": ["approval-gate", "direct-rbac"],
              "description": "`approval-gate`: `apply` MUST drive an RFC 0051 gate (role/scope/quorum, audited override) and MUST NOT install unless granted/overridden. `direct-rbac`: `apply` requires only the RFC 0049 scope the host advertises for activation."
            }
          }
        },
        "goals": {
          "type": "object",
          "description": "RFC 0097 (`Active`). Standing goals — a durable objective with explicit completion criteria, evaluated by a host-side judge (RFC 0090 verifier or host evaluator), that keeps an agent working across turns/runs until the judge is satisfied, a declared RFC 0058 bound is crossed, or the agent escalates (RFC 0044). A host advertising this serves `/v1/host/sample/goals` (promotable to `/v1/goals`) and emits the content-free `goal.evaluated` / `goal.closed` events. Completion MUST be the judge's verdict — a client MUST NOT set `state: satisfied` directly. Continuation MUST be bounded. Hosts that omit this block do not run standing goals; the conformance scenarios skip cleanly.",
          "additionalProperties": false,
          "required": ["judge", "continuation"],
          "properties": {
            "judge": {
              "type": "string",
              "enum": ["verifier", "host"],
              "description": "`verifier`: completion is an RFC 0090 verifier verdict. `host`: an opaque host evaluator."
            },
            "continuation": {
              "type": "array",
              "items": { "type": "string", "enum": ["schedule", "commitment", "heartbeat", "manual"] },
              "uniqueItems": true,
              "description": "How a goal re-engages work between judge checks. `schedule` (RFC 0052), `commitment` (RFC 0068), `heartbeat` (RFC 0060), `manual`."
            },
            "requiresBounds": {
              "type": "boolean",
              "default": true,
              "description": "When `true` (default), a goal MUST declare valid RFC 0058 `bounds` before it may activate; a `POST /goals` without bounds returns 422. The host MUST stop continuation and set `state: bound-exceeded` when any declared bound (iteration count / accumulated cost / wall-clock deadline) is crossed."
            }
          }
        }
      },
      "additionalProperties": true
    },
    "memory": {
      "type": "object",
      "description": "MemoryAdapter capability block per RFC 0004 + the optional compaction profile per RFC 0012. Hosts that don't wire any memory surface omit the entire block.",
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "When `true`, host implements the four-operation MemoryAdapter contract (`list`, `get`, `put`, `delete`) per RFC 0004 §A."
        },
        "maxEntrySizeBytes": {
          "type": "integer",
          "minimum": 1,
          "description": "Upper bound on `MemoryEntry.content` size. Hosts SHOULD reject `put` exceeding this with `validation_error`."
        },
        "ttlSupported": {
          "type": "boolean",
          "description": "When `true`, host honors `expiresAt` per RFC 0004 §E."
        },
        "compaction": {
          "type": "object",
          "description": "RFC 0012 Memory Compaction Profile (Accepted 2026-05-15). Hosts that distill many short-lived MemoryEntry rows into fewer long-lived ones MAY advertise here; advertising implies the SR-1 carry-forward invariant (`SECURITY/invariants.yaml` row `memory-compaction-sr-1-carry-forward`).",
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "REQUIRED when the sub-block is present. When `true`, host performs compaction over `longTerm` memory and emits the `memory.compacted` event per `observability.md` §Canonical event vocabulary."
            },
            "trigger": {
              "type": "string",
              "enum": ["host-managed", "client-requested", "both"],
              "description": "REQUIRED when `supported: true` per RFC 0012 §A (enforced via the `if/then` clause). `host-managed` runs on a host-internal schedule clients do not control. `client-requested` and `both` are reserved enum values; v1.x normates only `host-managed`."
            },
            "maxInputEntries": {
              "type": "integer",
              "minimum": 1,
              "description": "Informational ceiling on how many source entries one compaction call collapses. Not wire-enforced."
            },
            "maxOutputBytes": {
              "type": "integer",
              "minimum": 0,
              "description": "Informational ceiling on the distilled entry size. SHOULD be ≤ `memory.maxEntrySizeBytes`."
            }
          },
          "additionalProperties": false,
          "if": { "properties": { "supported": { "const": true } }, "required": ["supported"] },
          "then": { "required": ["supported", "trigger"] }
        },
        "distillation": {
          "type": "object",
          "description": "RFC 0062 (`Active`). Scheduled, token-budgeted background compaction — the 'dream' pattern — built on compaction (RFC 0012) + scheduling (RFC 0052) + the workspace index (RFC 0059). A distillation run IS a compaction run with a mandatory token budget, an optional schedule, and a retrieval index wrapped around it; it reuses the `memory.compacted` event (extended with the additive optional `distillation` sub-object) rather than a parallel `memory.distilled` event. SR-1 carry-forward (RFC 0012 §D) holds — a distilled archive MUST NOT re-expose a redacted secret. Hosts that omit this block keep plain on-demand compaction (RFC 0012) or no memory.",
          "required": ["supported"],
          "additionalProperties": false,
          "properties": {
            "supported": { "type": "boolean", "description": "REQUIRED when the sub-block is present. When `true`, host honors the `distillation.tokenBudget` reserved run-option key, runs budgeted distillation over `longTerm` memory, writes a stable archive, and emits `memory.compacted` with the `distillation` sub-object." },
            "maxTokenBudget": { "type": "integer", "minimum": 1, "description": "Largest per-run distillation token budget the host honors. A supplied `distillation.tokenBudget` is clamped to this; absent ⇒ the host defaults to this." },
            "scheduled": { "type": "boolean", "description": "When `true`, host can initiate distillation on a schedule (requires `capabilities.scheduling`, RFC 0052). Distillation MAY also run on-demand without scheduling." },
            "indexEmitted": { "type": "boolean", "description": "When `true`, host writes a retrievable memory-index manifest (`MEMORY-INDEX.json`, a workspace file per RFC 0059) after distillation; updating it emits `workspace.updated`." },
            "tokenizerName": { "type": "string", "description": "Identifier of the tokenizer the budget is counted against (e.g. `claude`, `gpt-4`). The budget is best-effort-honest per this tokenizer (±10% conformance tolerance), not byte-exact." },
            "archiveRetention": { "type": "string", "description": "ISO-8601 duration (e.g. `P30D`) the distilled archives persist before GC. Recursive distillation (distilling prior archives) is allowed; each level re-checks SR-1." }
          }
        },
        "attribution": {
          "type": "object",
          "description": "RFC 0057 Memory write-attribution. Hosts that emit per-node memory provenance on the run event log MAY advertise here. Advertising `emitsWriteEvents: true` commits the host to emit a `memory.written` RunEvent for every memory write a run makes (identifiers only, never content), and implies the SECURITY invariants `memory-attribution-no-content` + `memory-attribution-tenant-scoped`.",
          "required": ["supported"],
          "properties": {
            "supported": {
              "const": true,
              "description": "REQUIRED when the sub-block is present. The block is omitted entirely by hosts that do not support write attribution."
            },
            "emitsWriteEvents": {
              "type": "boolean",
              "description": "When `true`, the host emits `memory.written` (per `run-event-payloads.schema.json#/$defs/memoryWritten`) for every memory write during a run. When `false` or absent, consumers MUST tolerate the event never appearing."
            }
          },
          "additionalProperties": false
        },
        "writable": {
          "type": "boolean",
          "description": "RFC 0080 §A (`write` dimension). Absent ⇒ the host implements the full RFC 0004 four-operation MemoryAdapter (`put`/`delete` available — i.e. writable), the back-compatible default. A read-only host (`get`/`list` only) MUST set `writable: false` so a consumer can distinguish a read-only store from a read/write one. Only meaningful when `supported: true`."
        },
        "search": {
          "type": "object",
          "description": "RFC 0080 §A (`search` dimension) — NEW optional. Semantic or filtered query beyond the RFC 0004 `list` enumeration. Absent ⇒ only `list`/`get` retrieval is advertised. The query path itself stays the host-internal MemoryAdapter (RFC 0080 §B — no portable `GET /v1/memory` at v1.x).",
          "required": ["supported"],
          "additionalProperties": false,
          "properties": {
            "supported": { "type": "boolean", "description": "REQUIRED when the sub-block is present. When `true`, the host supports memory query beyond `list` (semantic and/or filter modes per `modes`)." },
            "modes": {
              "type": "array",
              "items": { "type": "string", "enum": ["semantic", "filter"] },
              "uniqueItems": true,
              "description": "The query modes the host supports. `semantic` = embedding/similarity retrieval; `filter` = structured predicate query over entry metadata. Omitted ⇒ unspecified mode (the host supports some query beyond `list`)."
            }
          }
        },
        "retention": {
          "type": "object",
          "description": "RFC 0080 §A (`retention/forget` dimension) — NEW optional. TTL expiry (`agent-memory.md §TTL`) and/or an explicit forget operation. Absent ⇒ no TTL beyond `ttlSupported` and no forget operation advertised. `forget` is a host-managed mutation OUTSIDE the replay envelope (RFC 0080 §UQ3 / `replay.md` §Recorded-fact events — a replay re-reads the log-recorded snapshot, not live memory).",
          "additionalProperties": false,
          "properties": {
            "ttl": { "type": "boolean", "description": "When `true`, memory entries expire per `expiresAt` (the `ttlSupported` semantics surfaced as a named retention dimension)." },
            "forget": { "type": "boolean", "description": "When `true`, the host supports a tenant-scoped delete-by-subject forget operation (composes the CTI-1 cross-tenant invariant — a forget MUST NOT cross tenant boundaries)." }
          }
        }
      },
      "additionalProperties": true
    },
    "conversationPrimitive": {
      "type": "boolean",
      "description": "Multi-Agent Shift Phase 4. When `true`, host advertises that it implements the `core.conversationGate` typeId AND honors the `conversation.start` / `conversation.exchange` / `conversation.close` suspend variants. Hosts that don't claim this fall back to `clarification.requested` interrupts for multi-turn user interjections."
    },
    "subWorkflow": {
      "type": "object",
      "description": "Capability surface for `core.subWorkflow` extensions. The baseline `core.subWorkflow` contract (RFC 0007 + `node-packs.md` §contract) is unconditional and does NOT require a capability flag; this object carries the additive extensions a host MAY support. Added by RFC 0022 §B + §C.",
      "properties": {
        "inputMapping": {
          "type": "boolean",
          "default": false,
          "description": "RFC 0022 §B. When `true`, host honors the `inputMapping` field on `core.subWorkflow` configs — seeding the child workflow's initial variable bag from `parentVariables[parentKey]` projections, overriding any matching `variables[].defaultValue` declaration on the child. When `false` or absent, hosts MUST refuse workflows that carry a non-empty `inputMapping` at registration with `validation_error` + `details.requiredCapability: 'subWorkflow.inputMapping'`. Silent ignore is NOT conformant."
        }
      },
      "additionalProperties": false
    },
    "fs": {
      "type": "object",
      "description": "RFC 0014 (`Active`). Filesystem capability — read/write/list/stat/delete inside a sandbox root. Required by the `core.openwop.files` pack. Hosts MUST resolve every input path relative to `sandboxRoot`, reject any path that escapes via `..` segments or symlinks, and enforce `maxFileSizeBytes` on write. Path-traversal rejection is normative — see `SECURITY/invariants.yaml` row `fs-path-traversal`.",
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Host advertises `ctx.fs.{read,write,delete,stat,list}`. `false` (or omission) signals the host does NOT expose a filesystem surface; `core.openwop.files` registration MUST refuse on such hosts."
        },
        "sandboxRoot": {
          "type": "string",
          "description": "Absolute path. Every fs operation is resolved relative to this root. MUST be set when `supported: true`."
        },
        "maxFileSizeBytes": {
          "type": "integer",
          "minimum": 0,
          "description": "Per-file size cap enforced on write. 0 = unlimited (NOT recommended). Reads of files larger than this MAY return `file_too_large`."
        },
        "image": {
          "type": "object",
          "description": "Image-processing sub-capability. Optional.",
          "properties": {
            "supported": { "type": "boolean" },
            "formats": {
              "type": "array",
              "items": { "type": "string", "enum": ["jpeg", "png", "webp", "avif", "gif"] }
            }
          },
          "additionalProperties": false
        },
        "pdf": {
          "type": "object",
          "description": "PDF-processing sub-capability. Optional.",
          "properties": {
            "supported": { "type": "boolean" }
          },
          "additionalProperties": false
        },
        "transport": {
          "type": "object",
          "description": "Network file-transport sub-capabilities. Optional.",
          "properties": {
            "ftp": { "type": "boolean" },
            "sftp": { "type": "boolean" },
            "ssh": { "type": "boolean" }
          },
          "additionalProperties": false
        }
      },
      "if": { "properties": { "supported": { "const": true } }, "required": ["supported"] },
      "then": { "required": ["supported", "sandboxRoot"] },
      "additionalProperties": false
    },
    "kvStorage": {
      "type": "object",
      "description": "RFC 0015 (`Active`). TTL-aware key-value store with atomic increment + compare-and-swap. Required by `core.openwop.storage` kv-* nodes. Hosts MUST partition values by tenant (`kv-cross-tenant-isolation` invariant) and atomically apply increments + CAS when those flags are advertised.",
      "properties": {
        "supported": { "type": "boolean" },
        "maxKeyBytes": { "type": "integer", "minimum": 0 },
        "maxValueBytes": { "type": "integer", "minimum": 0 },
        "maxTtlSeconds": { "type": "integer", "minimum": 0 },
        "atomicIncrement": { "type": "boolean" },
        "compareAndSwap": { "type": "boolean" }
      },
      "additionalProperties": false
    },
    "tableStorage": {
      "type": "object",
      "description": "RFC 0016 (`Active`). Structured-record store with user-defined schemas. Sibling to kvStorage. Cross-tenant isolation enforced (mirrors RFC 0015 invariant).",
      "properties": {
        "supported": { "type": "boolean" },
        "maxRowsPerTable": { "type": "integer", "minimum": 0 },
        "maxColumnsPerRow": { "type": "integer", "minimum": 0 },
        "indexable": { "type": "boolean" },
        "fullTextSearch": { "type": "boolean" }
      },
      "additionalProperties": false
    },
    "queueBus": {
      "type": "object",
      "description": "RFC 0017 (`Active`). Inbound queue + stream capability — publish, consume (trigger), ack/nack/dead-letter. Cross-tenant message isolation invariant (`queue-cross-tenant-isolation`). Sibling to host.messaging (which is outbound-egress-only).",
      "properties": {
        "supported": { "type": "boolean" },
        "backends": {
          "type": "array",
          "items": { "type": "string", "enum": ["rabbitmq", "kafka", "sqs", "sns", "pubsub", "mqtt", "nats", "redis-streams", "in-memory"] }
        },
        "deadLetterSupported": { "type": "boolean" },
        "stream": {
          "type": "object",
          "properties": {
            "supported": { "type": "boolean" },
            "fromBeginning": { "type": "boolean" }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "scheduling": {
      "type": "object",
      "description": "RFC 0052 (`Draft`). Time-based run initiation behind the `schedule` trigger — gives the trigger a portable, durable, once-per-tick execution contract. Composes with `queueBus` (RFC 0017) where the host backs scheduling with a queue; orthogonal to the in-DAG `core.control.delay` primitive (which delays a node mid-run, not run initiation).",
      "required": ["supported"],
      "properties": {
        "supported": { "type": "boolean" },
        "cron": { "type": "boolean", "description": "Host honors cron-expression schedules." },
        "delayed": { "type": "boolean", "description": "Host honors one-shot delayed execution." },
        "calendar": { "type": "boolean", "description": "Host honors calendar-reference schedules." },
        "maxFutureHorizon": { "type": "string", "description": "ISO-8601 duration (e.g. `P90D`); the farthest-future a run may be scheduled. Schedules beyond it MUST be rejected with `schedule_horizon_exceeded`." }
      },
      "additionalProperties": false
    },
    "heartbeat": {
      "type": "object",
      "description": "RFC 0060 (`Draft`). System-managed, predicate-gated polling: a short-interval, runtime-bounded evaluation of an idempotent predicate that emits state-change events and conditionally enqueues a run, rather than re-running an agent blindly. Composes with `scheduling` (RFC 0052) for the once-per-tick interval substrate; the controlled, request-shaped exception to openwop's poll-free design (`positioning.md`).",
      "required": ["supported"],
      "properties": {
        "supported": { "type": "boolean" },
        "minIntervalSec": { "type": "integer", "minimum": 1, "description": "Smallest interval the host honors; requests below it clamp up." },
        "maxRuntimeMs": { "type": "integer", "minimum": 1, "description": "Per-tick predicate-evaluation budget; bounded above by `capabilities.limits.maxRunDurationMs` (RFC 0058) as the hard ceiling. Over-budget evaluation is terminated and reported as `heartbeat.evaluated { status: 'timeout' }`." }
      },
      "additionalProperties": false
    },
    "toolHooks": {
      "type": "object",
      "description": "RFC 0064 (`Active`) — sibling of `heartbeat`. Per-tool authorization + rate limiting + content-free tool-call audit fields, layered on the existing `agent.toolCalled` / `agent.toolReturned` events (RFC 0002). Generalizes the MCP-specific bridges across transports (mcp / http / native). Reuses RFC 0049's `forbidden` error + `authorization-fail-closed` invariant and the existing `rate_limited` error — no new event type, error code, or invariant.",
      "required": ["supported"],
      "additionalProperties": false,
      "properties": {
        "supported": { "type": "boolean" },
        "prePostEvents": { "type": "boolean", "description": "Host populates `argsHash`/`principal`/`transport` on `agent.toolCalled` + `status`/`durationMs` on `agent.toolReturned` for every external tool call." },
        "perToolAuthorization": { "type": "boolean", "description": "Host enforces per-tool scopes against the run principal (RFC 0049), fail-closed; a lacked-or-unevaluable scope yields `agent.toolReturned { status: 'forbidden' }` + a `forbidden` (403) error and the tool is never invoked." },
        "perToolRateLimit": { "type": "boolean", "description": "Host applies a per-`(principal, tool)` token-bucket rate limit; exhaustion yields `agent.toolReturned { status: 'rate_limited' }` + a `rate_limited` (429) error." }
      }
    },
    "toolCatalog": {
      "type": "object",
      "description": "RFC 0078 (`Active`). The host exposes a read-only projection of its tool surfaces (node-pack / workflow / MCP / connector / host-extension) at `GET /v1/tools` + `GET /v1/tools/{toolId}`, returning `ToolDescriptor` records (`tool-descriptor.schema.json`). Optional; hosts that omit it expose no catalog (today's behavior) and the conformance scenarios skip cleanly. Read-only — the catalog never mutates tools; tool invocation stays on the existing surfaces (agent dispatch, `core.dispatch`, MCP). The listing is authorization-scoped + non-disclosing (RFC 0074 pattern).",
      "required": ["supported"],
      "additionalProperties": false,
      "properties": {
        "supported": { "type": "boolean", "description": "REQUIRED when present. `true` ⇒ `GET /v1/tools` + `GET /v1/tools/{toolId}` are served per RFC 0078 §B." },
        "sources": {
          "type": "array",
          "uniqueItems": true,
          "items": { "type": "string", "enum": ["node-pack", "workflow", "mcp", "connector", "host-extension"] },
          "description": "Which tool sources the catalog projects. A host advertises only the sources it actually surfaces; a consumer MUST tolerate any subset. Absent ⇒ all sources the host implements."
        },
        "sessionLifecycle": { "type": "boolean", "description": "`true` ⇒ the host emits the RFC 0078 §D tool-session lifecycle events (`tool.session.opened`/`tool.session.closed`, content-free) bracketing the existing RFC 0064 `agent.toolCalled`/`agent.toolReturned` call events for multi-step interactions. Absent ⇒ `false` (single-shot tool calls only)." }
      }
    },
    "httpClient": {
      "type": "object",
      "description": "Host outbound-HTTP surface. The host's HTTP-client node egress (e.g. `core.http.request`) MUST be SSRF-guarded (`ssrfGuard: true`) with a positive `maxResponseBodyBytes` cap — the `http-client-ssrf-guard` protocol invariant. RFC 0076 §B adds the OPTIONAL `safeFetch` sub-capability: a host-mediated `ctx.http.safeFetch(url, init?)` exposed to pack runtime code, backed by the SAME SSRF guard (resolve→pin→connect, metadata-endpoint blocklist, DNS-rebinding defeat) so packs need not reach for raw DNS/sockets. See `host-capabilities.md` §host.http.",
      "required": ["supported"],
      "additionalProperties": false,
      "properties": {
        "supported": { "type": "boolean" },
        "ssrfGuard": { "type": "boolean", "description": "Host rejects egress to loopback / RFC 1918 / link-local / cloud-metadata addresses (resolve→pin→connect). MUST be `true` when `supported` (the `http-client-ssrf-guard` invariant)." },
        "maxResponseBodyBytes": { "type": "integer", "minimum": 1, "description": "Positive ceiling on a response body the host will buffer. Reused by `safeFetch`." },
        "requestTimeoutMs": { "type": "integer", "minimum": 1, "description": "Host-enforced per-request wall-clock timeout (also applies to `safeFetch`)." },
        "methods": { "type": "array", "items": { "type": "string" }, "description": "HTTP methods the client surface accepts (e.g. `GET`, `POST`)." },
        "safeFetch": {
          "type": "object",
          "description": "RFC 0076 §B. Host-provided `ctx.http.safeFetch(url, init?)` for pack runtime code — the pack-facing exposure of the SSRF-guarded client. When advertised, the host MUST apply the §host.http SSRF defense + clamps (incl. refusing `Connection: upgrade`), and — when `toolHooks.prePostEvents` is also advertised — MUST emit the `agent.toolCalled`/`agent.toolReturned` pair (`transport: 'http'`) for each call. A pack that uses `safeFetch` need not declare `net.dns` in `runtime.requires` (the host owns resolution; RFC 0076 §A).",
          "required": ["supported"],
          "additionalProperties": false,
          "properties": {
            "supported": { "type": "boolean" }
          }
        },
        "egressPolicy": {
          "type": "object",
          "additionalProperties": false,
          "description": "RFC 0079. The host evaluates credential provenance (`credential-provenance.schema.json`) + the audience-binding MUST (§C) on credentialed egress and emits `egress.decided` (§B). Requires `httpClient.safeFetch` (the egress mechanism). Absent ⇒ the host does not perform provenance binding (the RFC 0076 §B SSRF guard still applies); the conformance behavioral scenarios skip cleanly. Closes the credential↔destination-binding question RFC 0076 §B parked.",
          "required": ["supported"],
          "properties": {
            "supported": { "type": "boolean", "description": "REQUIRED when present. `true` ⇒ the §C audience-binding MUST is enforced + `egress.decided` is emitted." },
            "decisions": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["allowed", "denied", "downgraded", "approval-required"] }, "description": "MAY — which decision outcomes the host implements. Absent ⇒ at least `allowed` + `denied`." }
          }
        }
      }
    },
    "deadLetter": {
      "type": "object",
      "description": "RFC 0053 (`Draft`). Run-level dead-letter sink for terminally-failed runs/nodes. On retry exhaustion (RFC 0009), the run is routed to a durable, inspectable sink and a `run.dead_lettered` event is emitted; dead-lettered runs remain fork-eligible (RFC 0011) for the retention window. Distinct from `queueBus.deadLetterSupported`, which dead-letters transport *messages*, not *runs*.",
      "required": ["supported"],
      "properties": {
        "supported": { "type": "boolean" },
        "retentionDays": { "type": "integer", "minimum": 1, "description": "Days a dead-lettered run is retained for inspection/fork before purge." }
      },
      "additionalProperties": false
    },
    "webhooks": {
      "type": "object",
      "description": "Webhook delivery surface (`webhooks.md`). The base contract is best-effort (5s per-attempt timeout, a circuit breaker, no durable retry). RFC 0083 adds the OPTIONAL `durable` mode: when `true`, webhook delivery participates in the trigger-bridge durable model (subscription states + retry policy + dead-letter on exhaustion) instead of the best-effort circuit-breaker-then-drop. Absent `durable` ⇒ the best-effort default, explicitly unchanged.",
      "additionalProperties": true,
      "properties": {
        "supported": { "type": "boolean", "description": "Host serves the `webhooks.md` signed-delivery surface." },
        "signatureAlgorithms": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "description": "HMAC signature algorithm ids the host supports (e.g. `v1`)." },
        "durable": { "type": "boolean", "description": "RFC 0083 §A — OPTIONAL opt-in. `true` ⇒ webhook delivery is durable (a trigger-bridge source); absent/`false` ⇒ the best-effort `webhooks.md` contract, unchanged. The best-effort default is NOT relaxed." }
      }
    },
    "triggerBridge": {
      "type": "object",
      "description": "RFC 0083 (`Active`). Composes the existing scheduling (RFC 0052), dead-letter (RFC 0053), queue-bus (RFC 0017), webhook, and cross-host-causation (RFC 0040) primitives into one uniform durable inbound-work contract: standardized subscription states + a delivery-attempt/dedup/retry model + trigger→run causation. Backs the derived `openwop-trigger-bridge` profile. Channels (Slack/email/SMS) stay vendor extensions (§E) — only their bridge into a run is uniform. Hosts that omit it have no uniform trigger contract (today's behavior); the conformance behavioral scenarios skip cleanly.",
      "required": ["supported"],
      "additionalProperties": false,
      "properties": {
        "supported": { "type": "boolean", "description": "REQUIRED when present. `true` ⇒ the host implements the §B state machine + §C delivery model + emits the two `trigger.*` events." },
        "subscriptionStates": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["active", "paused", "failed", "dead-lettered"] }, "description": "The subscription states the host implements (the §B four-state vocabulary). Absent ⇒ at least `active` + `dead-lettered`." },
        "dedup": { "type": "boolean", "description": "`true` ⇒ the host de-duplicates inbound events by `dedupKey` within the retention window (§C-1; at-least-once becomes effectively-once)." },
        "retryPolicy": {
          "type": "object",
          "additionalProperties": false,
          "description": "The host's default delivery retry policy (§C-2).",
          "properties": {
            "maxAttempts": { "type": "integer", "minimum": 1, "description": "Max delivery attempts before dead-lettering." },
            "backoff": { "type": "string", "enum": ["none", "fixed", "exponential"], "description": "Backoff strategy between attempts." }
          }
        },
        "sources": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["webhook", "schedule", "queue", "email", "form"] }, "description": "Which trigger sources bridge uniformly. A source listed here MUST have a registerable `TriggerSubscription` driven through the four-state machine AND emit the two `trigger.*` events for that source — the list MUST NOT over-claim a source the host has as a feature but does not wire as a durable trigger subscription. A consumer MUST tolerate any subset." },
        "ingestion": {
          "type": "object",
          "additionalProperties": false,
          "description": "RFC 0099 §F.3 (additive). External-event ingestion advertisement — which of `sources[]` the host actually ingests from EXTERNALLY-originated events (`webhook`/`email`/`form`), normalizing each to a `TriggerEvent` (`trigger-event.schema.json`) and starting a run. Absent ⇒ the host does NOT externally-ingest (today's behavior — schedule/queue only). A source in `externalSources[]` MUST actually accept an external event, normalize it, and start a run — over-claiming is a dishonest advertisement. A consumer MUST tolerate any subset.",
          "properties": {
            "externalSources": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["webhook", "email", "form"] }, "description": "Which of `sources[]` are EXTERNALLY ingested per RFC 0099. The honesty gate — each MUST normalize to a `TriggerEvent` and start a run." },
            "maxBodyBytes": { "type": "integer", "minimum": 1, "description": "Inbound body cap (webhook body / email / form), reusing the RFC 0076 §B response-cap discipline." },
            "verification": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["webhook-signature", "email-dmarc", "form-origin"] }, "description": "Which source-authenticity checks the host performs. An advertised check MUST actually be performed (RFC 0099 §F.3 / UQ1)." },
            "registrationEndpoint": { "type": "boolean", "description": "`true` ⇒ the host serves `POST /v1/trigger-subscriptions` (RFC 0099 §F.2) for portable external-event subscription creation." }
          }
        }
      }
    },
    "a2a": {
      "type": "object",
      "additionalProperties": false,
      "description": "RFC 0100 (`Active`). The host exposes itself as an A2A (Agent2Agent) agent. `supported: true` alone ⇒ the SYNCHRONOUS `message/send` → poll `tasks/get` round-trip already specified by `a2a-integration.md` (today's behavior — no regression). The optional `streaming`/`pushNotifications`/`durableTasks` flags gate the RFC 0100 async/durable additions (resubscribe re-attach, push config, persisted `A2ATaskState` so `tasks/get` returns live state after disconnect). Absent block ⇒ no A2A advertisement.",
      "required": ["supported", "agentCardUrl"],
      "properties": {
        "supported": { "type": "boolean", "description": "Host exposes itself as an A2A agent." },
        "agentCardUrl": { "type": "string", "format": "uri", "description": "The A2A 0.3 well-known agent card URL (`/.well-known/agent-card.json`)." },
        "streaming": { "type": "boolean", "description": "Host supports `message/stream` + `tasks/resubscribe` (A2A `capabilities.streaming`). Gates the RFC 0100 §3 resubscribe re-attach." },
        "pushNotifications": { "type": "boolean", "description": "Host supports A2A push-notification config (A2A `capabilities.push_notifications`). Gates the RFC 0100 §4 push contract; a caller-supplied `pushConfig.url` is SSRF-validated (`a2a-push-egress-ssrf`)." },
        "durableTasks": { "type": "boolean", "description": "RFC 0100 §2. Host PERSISTS the projected Task (`A2ATaskState`) per backing run; `tasks/get` returns live state after disconnect. Absent/false ⇒ synchronous round-trip only." }
      }
    },
    "budget": {
      "type": "object",
      "description": "RFC 0084 (`Active`). Enforceable per-run SPEND governance — the reserved `budget` run-options key (`budget-policy.schema.json`), the content-free `budget.{reserved,consumed,threshold.crossed,exhausted}` events, and hard-stop enforcement via `cap.breached{kind:\"budget-*\"}`. Orthogonal to RFC 0058 (which owns wall-time + loop-iterations via `limits.maxRunDurationMs`/`maxLoopIterations`); they share only the `cap.breached` overflow event. Hosts that omit it perform no spend enforcement (today's behavior); the conformance behavioral scenarios skip cleanly.",
      "required": ["supported"],
      "additionalProperties": false,
      "properties": {
        "supported": { "type": "boolean", "description": "REQUIRED when present. `true` ⇒ the host resolves the `budget` policy, emits the `budget.*` events, and (when `enforce: \"hard\"`) stops the run on exhaustion." },
        "dimensions": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["tokens", "cost", "toolCalls", "retries", "model"] }, "description": "Which budget dimensions the host actually enforces (truthful — advertise only what it honors). A consumer MUST tolerate any subset." },
        "enforce": { "type": "string", "enum": ["hard", "advisory"], "description": "`hard`: exhaustion emits `cap.breached` + stops the run. `advisory`: emits the events but MUST NOT stop the run (honest advertisement of observe-only)." },
        "scopes": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["run", "workflow", "agent", "project"] }, "description": "Which budget scopes the host honors (§B). Absent ⇒ at least `run`." }
      }
    },
    "nondeterminismPolicy": {
      "type": "object",
      "description": "RFC 0085. A host that does NOT support replay/fork (`replay.supported`) MAY instead DECLARE that it is honestly nondeterministic — satisfying the `openwop-agent-platform` floor's replay-OR-policy term (`agent-platform-profile.md` §B) without claiming a replay capability it lacks. Absent ⇒ the floor's replay term must be met by `replay.supported`.",
      "required": ["declared"],
      "additionalProperties": false,
      "properties": {
        "declared": { "type": "boolean", "description": "When `true`, the host documents its nondeterminism (it does not guarantee deterministic replay). A bare flag for v1.x (RFC 0085 §UQ2); a structured per-source policy is a future refinement." }
      }
    },
    "workspace": {
      "type": "object",
      "description": "RFC 0059 (`Active`). Versioned, tenant·workspace-scoped ground-truth file store (the `host.workspace` capability). Scopes to the RFC 0048 owner triple. Atomic, optimistically-concurrent writes (`If-Match` ETag); a read snapshot is exposed to every run at `run.started` (deterministic for replay). Complements the transactional `MemoryAdapter` (RFC 0004) with a durable, path-addressable file layer. Endpoints (`/v1/host/workspace/files[/{path}]`) are gated on `supported: true`; unsupported hosts return `501 capability_not_provided`. SECURITY invariants `workspace-cross-tenant-isolation` (WCT-1) + the WSR-1 secret-redaction MUST land with their conformance tests at implementation (RFC 0059 §E).",
      "required": ["supported"],
      "properties": {
        "supported": { "type": "boolean", "description": "Host implements the RFC 0059 workspace file store + endpoints + `workspace.updated` event." },
        "versioned": { "type": "boolean", "description": "Each write bumps a monotonic `version`; prior versions are retrievable via `GET …/files/{path}?version=N`. Latest-version retrieval is the MUST regardless; history is best-effort up to `maxVersions`." },
        "maxFileBytes": { "type": "integer", "minimum": 1, "description": "Per-file byte ceiling; writes beyond it return `workspace_too_large`." },
        "maxFiles": { "type": "integer", "minimum": 1, "description": "Per-workspace file-count ceiling." },
        "maxVersions": { "type": "integer", "minimum": 1, "description": "When `versioned: true`, the number of historical versions a host advertises it will retain (history best-effort beyond the mandatory latest)." }
      },
      "additionalProperties": false
    },
    "sql": {
      "type": "object",
      "description": "RFC 0018 (`Active`). SQL database adapter with parametric-only enforcement. Hosts MUST reject non-parametric queries that inline user input (`sql-parametric-only` invariant — guards against SQL injection across every workflow).",
      "properties": {
        "supported": { "type": "boolean" },
        "datasources": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
        "transactions": { "type": "boolean" },
        "drivers": {
          "type": "array",
          "items": { "type": "string", "enum": ["postgres", "mysql", "mariadb", "sqlite", "mssql", "clickhouse", "snowflake", "bigquery", "duckdb"] }
        }
      },
      "additionalProperties": false
    },
    "nosql": {
      "type": "object",
      "description": "RFC 0018 (`Active`). MongoDB-shape document store adapter.",
      "properties": {
        "supported": { "type": "boolean" },
        "datasources": { "type": "array" },
        "drivers": { "type": "array", "items": { "type": "string", "enum": ["mongodb", "dynamodb", "cosmosdb", "firestore"] } }
      },
      "additionalProperties": false
    },
    "vectorStore": {
      "type": "object",
      "description": "RFC 0018 (`Active`). Vector-DB capability for k-NN search.",
      "properties": {
        "supported": { "type": "boolean" },
        "collections": { "type": "array" },
        "backends": {
          "type": "array",
          "items": { "type": "string", "enum": ["pinecone", "qdrant", "weaviate", "milvus", "pgvector", "redis", "mongodb-atlas", "chroma", "azure-ai-search", "in-memory"] }
        }
      },
      "additionalProperties": false
    },
    "searchIndex": {
      "type": "object",
      "description": "RFC 0018 (`Active`). Full-text search index adapter.",
      "properties": {
        "supported": { "type": "boolean" },
        "indexes": { "type": "array" },
        "backends": {
          "type": "array",
          "items": { "type": "string", "enum": ["elasticsearch", "opensearch", "meilisearch", "typesense", "algolia"] }
        }
      },
      "additionalProperties": false
    },
    "blobStorage": {
      "type": "object",
      "description": "RFC 0019 (`Active`). Binary artifact store with presigned URLs. Per-bucket tenant isolation. Presigned URLs MUST expire at the advertised TTL.",
      "properties": {
        "supported": { "type": "boolean" },
        "buckets": { "type": "array" },
        "presignSupported": { "type": "boolean" },
        "maxObjectBytes": { "type": "integer", "minimum": 0 }
      },
      "additionalProperties": false
    },
    "cache": {
      "type": "object",
      "description": "RFC 0019 (`Active`). TTL cache for HTTP / AI response memoization. Per-tenant scoping; TTL drift ≤ 1s.",
      "properties": {
        "supported": { "type": "boolean" },
        "maxValueBytes": { "type": "integer", "minimum": 0 },
        "maxTtlSeconds": { "type": "integer", "minimum": 0 }
      },
      "additionalProperties": false
    },
    "workflowChainPacks": {
      "type": "object",
      "description": "RFC 0013 (Phase 1, `Draft`). When `supported: true`, the host's workflow editor implements workflow-chain pack expansion per `workflow-chain-packs.md` — author drops a chain tile, host resolves the pack, prompts for `parameters`, substitutes `{{params.<name>}}` placeholders, rewrites node ids, splices the resulting DAG into the parent workflow. Hosts that don't implement expansion omit this block (or set `supported: false`); conformance scenarios under `conformance/src/scenarios/workflow-chain-*.test.ts` skip cleanly against those hosts.",
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Whether the host's workflow editor implements chain expansion at author time. `false` (or omission) signals the host does NOT consume workflow-chain packs."
        }
      },
      "required": ["supported"],
      "additionalProperties": false
    },
    "packs": {
      "type": "object",
      "description": "RFC 0025 (`Active`). Pack-registry surface advertisement. The baseline `/v1/packs/*` read surface (per `spec/v1/node-packs.md` §\"Registry HTTP API\") is unconditional for hosts that ship a pack catalog and does NOT require a capability flag; this object carries optional sub-blocks (currently the test-mode mirror namespace). Hosts that don't expose any optional pack-registry sub-block MAY omit this block entirely.",
      "properties": {
        "testMode": {
          "type": "object",
          "description": "RFC 0025 §A. Optional `/v1/packs-test/*` mirror surface that exposes the production publish/get/delete/sig contract against an isolated catalog. Lets the conformance suite (`pack-registry-publish.test.ts`) exercise the documented 19-code publish error catalog without `packs:publish` scope on the real registry. Hosts that advertise `supported: true` MUST honor the §C isolation guarantees and MUST surface the same error envelopes and HTTP statuses as the production `/v1/packs/*` surface.",
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host exposes `/v1/packs-test/*` per RFC 0025 §B. When `true`, the conformance suite drives publish-error-catalog assertions through the test namespace; when `false` or absent, the 26 scenarios in `pack-registry-publish.test.ts` soft-skip cleanly."
            },
            "isolated": {
              "type": "boolean",
              "description": "RFC 0025 §C point 1. MUST be `true` when `supported` is `true` — guarantees the test catalog is persisted distinctly from the production catalog and that a pack PUT'd via `/v1/packs-test/*` MUST NOT appear in `/v1/packs/*` listings."
            },
            "catalogResetEndpoint": {
              "type": "string",
              "description": "RFC 0025 §C point 4. Optional URL path (e.g. `/v1/packs-test/reset`) that clears the entire test catalog. When advertised, conformance-suite teardown SHOULD call it; the endpoint MUST be idempotent. Hosts MAY omit; in that case the suite leaves disposable timestamped pack names in place and relies on the next host restart to clear in-memory state.",
              "pattern": "^/"
            },
            "scopes": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": ["core", "vendor", "community", "private", "local"]
              },
              "uniqueItems": true,
              "minItems": 1,
              "description": "RFC 0025 §A. Which namespace scopes the test catalog accepts in pack names. Public test catalogs SHOULD refuse `private` and `local` (matching the production-registry rule for `packs.openwop.dev`); private dev catalogs MAY accept all five. When omitted, the test catalog defaults to the same scope set as the production namespace it mirrors."
            }
          },
          "required": ["supported"],
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "mcp": {
      "type": "object",
      "description": "RFC 0020 (`Active`). MCP (Model Context Protocol) composition surface. The client half is consumed implicitly via `host.mcp` host-surface; this block adds the optional server half — workflow exposed AS an MCP server with bidirectional sampling/elicitation bridges.",
      "properties": {
        "supported": { "type": "boolean", "description": "Host advertises a client-side MCP surface (ctx.mcp.*). See spec/v1/mcp-integration.md." },
        "serverMount": {
          "type": "object",
          "description": "Server-side MCP composition (workflow IS an MCP server). When supported, the host mounts an MCP endpoint and routes inbound tools/call, resources/read, prompts/get into workflows. Inbound MUST be treated as `trustBoundary: 'untrusted'`.",
          "properties": {
            "supported": { "type": "boolean" },
            "transports": {
              "type": "array",
              "items": { "type": "string", "enum": ["stdio", "streamable-http"] }
            },
            "samplingBridge": { "type": "boolean", "description": "Inbound sampling/createMessage bridges to the workflow's ctx.callAI." },
            "elicitationBridge": { "type": "boolean", "description": "Inbound elicitation/create bridges to ctx.suspend." }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "sandbox": {
      "type": "object",
      "description": "RFC 0035 — Sandbox execution contract for pack-loaded typeIds. Hosts that advertise execute pack-loaded code inside an isolation boundary meeting the 8 failure-mode invariants in spec/v1/host-capabilities.md §'Sandbox execution contract'. Absent block = host does NOT sandbox pack-loaded code and MUST refuse to load any pack whose manifest declares `peerDependencies.host.sandbox: required`.",
      "additionalProperties": false,
      "required": ["supported", "isolationModel"],
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Host enforces the 8 sandbox failure-mode invariants per spec/v1/host-capabilities.md §'Sandbox execution contract'. When false, host MUST refuse to load packs declaring required sandbox isolation."
        },
        "isolationModel": {
          "type": "string",
          "anyOf": [
            { "enum": ["wasm", "process", "container", "vm"] },
            { "pattern": "^x-host-[a-z][a-z0-9-]*-[a-z][a-z0-9-]*$" }
          ],
          "description": "Categorical isolation model. `wasm` = WebAssembly sandbox with explicit host imports (e.g., Wasmtime, Wasmer). `process` = OS process boundary with restricted syscalls (e.g., gVisor, seccomp, Landlock). `container` = container runtime boundary (e.g., Firecracker microVM). `vm` = full VM. Vendor-specific isolation models advertise `^x-host-<host>-<key>$` per spec/v1/host-extensions.md §'Canonical prefixes'; documentation lives at the host's discovery doc."
        },
        "allowedHostCalls": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Whitelist of host-call surfaces sandboxed code MAY invoke. Identifiers from the spec-reserved `host.*` capability set or `^x-host-<host>-<key>$` extension namespace. Empty array = pure compute only (no host I/O). Conformance verifies the sandbox refuses unlisted calls (`sandbox_capability_denied`)."
        },
        "memoryLimitBytes": {
          "type": "integer",
          "minimum": 1048576,
          "description": "Per-invocation memory cap (≥ 1 MiB). Host MUST enforce; exceeding fails the node with `sandbox_memory_exceeded`."
        },
        "wallClockLimitMs": {
          "type": "integer",
          "minimum": 100,
          "description": "Per-invocation wall-clock cap (≥ 100 ms). Host MUST enforce; exceeding fails the node with `sandbox_timeout`."
        }
      }
    },
    "idempotency": {
      "type": "object",
      "description": "RFC 0036 — Multi-region idempotency contract. Optional v1 advertisement. The existing `crossRegion: 'single-region'|'best-effort'|'strict'` categorical claim lives under `capabilities.idempotency.crossRegion` per spec/v1/idempotency.md §'Multi-region idempotency (annex)'. The `multiRegion` sub-block here gives a granular advertisement that hosts SHOULD pair with the categorical `crossRegion` claim.",
      "additionalProperties": true,
      "properties": {
        "multiRegion": {
          "type": "object",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host implements cross-region idempotency reconciliation per spec/v1/idempotency.md §'Multi-region reconciliation'. When `true`, an Idempotency-Key write succeeding in region A is read-visible in region B within `replicationLagBoundMs + safetyMargin`."
            },
            "replicationLagBoundMs": {
              "type": "integer",
              "minimum": 0,
              "maximum": 60000,
              "description": "Conservative upper bound on cross-region replication lag (≤ 60s ceiling chosen to keep operator advertisement honest; production cross-region deployments typically run < 10s). Conformance asserts read-visibility after the bound."
            },
            "partitionRecoveryStrategy": {
              "type": "string",
              "anyOf": [
                { "enum": ["last-writer-wins", "first-writer-wins"] },
                { "pattern": "^x-host-[a-z][a-z0-9-]*-[a-z][a-z0-9-]*$" }
              ],
              "description": "Deterministic resolution rule for conflicting idempotency-key records when a partition healed. `last-writer-wins` / `first-writer-wins` are spec-reserved categorical rules; vendor strategies use `^x-host-<host>-<key>$`. Conformance asserts deterministic resolution actually applies; it does NOT prescribe which strategy a host picks."
            }
          }
        },
        "crossRegion": {
          "type": "string",
          "enum": ["single-region", "best-effort", "strict"],
          "description": "RFC 0036 — categorical multi-region idempotency posture (the canonical conformance-checked surface per spec/v1/idempotency.md §'Multi-region idempotency (annex)'). `single-region`: the host runs in one region and makes no cross-region claim (it MAY still implement the convergence resolver, demonstrable via the multi-region simulator seam). `best-effort`: cross-region reconciliation converges eventually under the annex's lex-min(runId) rule. `strict`: cross-region read-visibility is bounded by `multiRegion.replicationLagBoundMs`. When `best-effort` or `strict`, the host MUST emit the `openwop.idempotency.cross_region_conflicts_total` operator metric. The `multiRegion` sub-block above is the optional granular companion."
        }
      }
    },
    "eventLog": {
      "type": "object",
      "description": "RFC 0036 — Event-log multi-engine advertisement. Optional v1.",
      "additionalProperties": false,
      "properties": {
        "crossEngineOrdering": {
          "type": "object",
          "additionalProperties": false,
          "required": ["supported"],
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host implements append-ordering guarantees across multiple engine instances writing to the same run's event log. When `true`, two engines appending concurrently converge on a total order any reader observes consistently."
            },
            "orderingModel": {
              "type": "string",
              "enum": ["lamport", "vector-clock", "global-sequencer"],
              "description": "Mechanism the host uses to derive the total order. `lamport` = Lamport timestamps on each append. `vector-clock` = per-engine vector counters merged at read. `global-sequencer` = single sequencer assigns monotonic seq numbers (the Postgres reference host's posture)."
            }
          }
        }
      }
    },
    "compliance": {
      "type": "object",
      "description": "Privacy / compliance behavior advertised to clients (closes O5). Lets consumers know what masking mode is in effect so a `\"[REDACTED]\"` value in event log payloads is recognizable as a server-side mask vs an upstream null.",
      "properties": {
        "defaultMode": {
          "type": "string",
          "enum": ["mask", "omit", "hash", "passthrough"],
          "description": "Server's default masking mode for fields marked sensitive. `mask` (default): replace with `\"[REDACTED]\"`. `omit`: drop the field entirely. `hash`: replace with `\"sha256:<hex>\"` for audit-only equality. `passthrough`: record as-is (NOT recommended for production). Workflow authors MAY override per-workflow via `metadata.complianceConfig.maskingMode`. See observability.md §Privacy classification."
        },
        "supportedClasses": {
          "type": "array",
          "items": { "type": "string", "enum": ["public", "pii", "phi", "pci", "regulated"] },
          "uniqueItems": true,
          "description": "Compliance classes this server applies special handling for. A server MAY accept all five classes but only enforce stricter retention on a subset; this field declares the operational reality so workflow authors can choose a server appropriately."
        }
      },
      "additionalProperties": true
    },
    "production": {
      "type": "object",
      "description": "Production-profile advertisement (see production-profile.md). Optional in v1; absence means the host does not claim the openwop-production profile. When `supported: true`, the host claims every MUST in production-profile.md and conformance scenarios gated on this block MUST run. Landed by RFC 0009.",
      "required": ["supported"],
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Host claims the openwop-production profile end-to-end (production-profile.md)."
        },
        "backpressure": {
          "type": "object",
          "description": "Backpressure envelope advertisement (production-profile.md §Backpressure).",
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host returns 503 + Retry-After + canonical envelope under load per production-profile.md §Backpressure."
            },
            "inflightCap": {
              "type": "integer",
              "minimum": 1,
              "description": "Optional host-side concurrent-inflight cap the conformance suite can saturate. When advertised, the production-backpressure scenario issues `inflightCap + 1` concurrent long-lived requests to deterministically force a 503. When absent, the scenario soft-skips the saturation step (envelope assertion still runs if a 503 happens to fire)."
            },
            "retryAfterSeconds": {
              "type": "integer",
              "minimum": 0,
              "maximum": 86400,
              "description": "Optional advertised Retry-After value in seconds the host returns on 503. When present, MUST equal both the `Retry-After` header and the `details.retryAfter` body field per production-profile.md. Upper bound 86400 (24h) — values beyond that are operationally indistinguishable from 'permanently denied'. Hosts needing longer holds SHOULD omit `Retry-After` entirely (RFC 0009 Q#2)."
            }
          },
          "additionalProperties": false
        },
        "retention": {
          "type": "object",
          "description": "Event-retention advertisement (production-profile.md §\"Event retention\").",
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host enforces event-log retention with a documented minimum window."
            },
            "minWindowSeconds": {
              "type": "integer",
              "minimum": 604800,
              "description": "Documented minimum retention window in seconds. Per production-profile.md §\"Event retention\", MUST be ≥ 604800 (7 days) for public hosts; development-only hosts MAY advertise a smaller window but MUST NOT claim `supported: true` while doing so."
            },
            "testForceExpire": {
              "type": "boolean",
              "description": "Host exposes a test-only force-expire hook the conformance suite can call (URL/method supplied via `OPENWOP_TEST_FORCE_EXPIRE_URL` / `OPENWOP_TEST_FORCE_EXPIRE_METHOD` env vars). When `false`, the production-retention-expiry scenario asserts only the 410/404 envelope shape on an operator-supplied already-expired run id (via `OPENWOP_TEST_EXPIRED_RUN_ID`); otherwise it soft-skips. RFC 0009 unresolved question #1 — endpoint normation is deferred."
            }
          },
          "additionalProperties": false
        },
        "debugBundle": {
          "type": "object",
          "description": "Debug-bundle truncation advertisement (production-profile.md §\"Debug bundle behavior\"). Stricter than the existing `capabilities.debugBundle.supported` advertised per debug-bundle.md — this block adds the production-profile MUSTs (truncation metadata, redaction).",
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host claims production-profile debug-bundle behavior end-to-end."
            },
            "truncationMetadata": {
              "type": "boolean",
              "description": "When `true`, host surfaces `truncated: true` + non-empty `truncatedReason` per debug-bundle.md §\"Bundle size limits\" when caps are reached."
            }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "auth": {
      "type": "object",
      "description": "Auth-profile advertisement (auth-profiles.md). Optional in v1; absence preserves the baseline bearer-token contract from auth.md. RFC 0010 formalized this block; `additionalProperties: true` permits existing informal usages (`auth.auditLogIntegrity` from the audit-log-integrity profile) to continue alongside the formal sub-blocks defined here.",
      "properties": {
        "profiles": {
          "type": "array",
          "items": { "type": "string", "minLength": 1 },
          "uniqueItems": true,
          "description": "Auth profiles the host claims. Canonical ids: `openwop-audit-log-integrity` (auth-profiles.md §Audit-log integrity), `openwop-auth-api-key-rotation`, `openwop-auth-oauth2-client-credentials`, `openwop-auth-oidc-user-bearer`, `openwop-auth-mtls`, `openwop-auth-saml` + `openwop-auth-scim` + `openwop-auth-ldap` (RFC 0050 enterprise identity). Clients SHOULD tolerate unknown profile ids."
        },
        "rotation": {
          "type": "object",
          "description": "API-key rotation advertisement (auth-profiles.md §`openwop-auth-api-key-rotation`).",
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host claims the openwop-auth-api-key-rotation profile (old+new key overlap during a documented grace window)."
            },
            "minGraceSeconds": {
              "type": "integer",
              "minimum": 0,
              "description": "Minimum rotation grace window in seconds. auth-profiles.md SHOULDs production-profile hosts to ≥ 86400 (24h). The conformance scenario tolerates any non-negative value but warns in behavior mode when < 86400."
            }
          },
          "additionalProperties": false
        },
        "oauth2": {
          "type": "object",
          "description": "OAuth2 client-credentials advertisement (auth-profiles.md §`openwop-auth-oauth2-client-credentials`).",
          "properties": {
            "supported": { "type": "boolean" },
            "issuer": {
              "type": "string",
              "format": "uri",
              "description": "Token issuer URL the host trusts. Tokens MUST carry a matching `iss` claim."
            },
            "audience": {
              "type": "string",
              "description": "Audience the host requires. Tokens MUST carry a matching `aud` claim."
            },
            "supportedAlgorithms": {
              "type": "array",
              "items": { "type": "string", "minLength": 1 },
              "uniqueItems": true,
              "description": "JWS signing algorithms the host accepts (canonical: RS256, ES256). Hosts MUST reject tokens signed with algorithms outside this list."
            }
          },
          "additionalProperties": false
        },
        "oidc": {
          "type": "object",
          "description": "OIDC user-bearer advertisement (auth-profiles.md §`openwop-auth-oidc-user-bearer`).",
          "properties": {
            "supported": { "type": "boolean" },
            "issuers": {
              "type": "array",
              "items": { "type": "string", "format": "uri" },
              "minItems": 1,
              "uniqueItems": true,
              "description": "Trusted OIDC issuer URLs. The host accepts tokens whose `iss` claim matches any entry."
            },
            "audience": {
              "type": "string",
              "description": "Audience identifier the host requires in OIDC tokens."
            },
            "supportedScopeMapping": {
              "type": "string",
              "enum": ["group-claim", "scope-claim", "host-acl"],
              "description": "How the host derives openwop scopes from the OIDC token. `group-claim`: from `groups` claim via host config. `scope-claim`: from `scope` claim directly. `host-acl`: from a host-side mapping table (sub → scope)."
            },
            "introspectionIntervalSeconds": {
              "type": "integer",
              "minimum": 0,
              "description": "Maximum interval at which the host SHOULD re-introspect a cached token to detect IdP revocation. auth-profiles.md recommends `min(exp - now, 300)`."
            }
          },
          "additionalProperties": false
        },
        "mtls": {
          "type": "object",
          "description": "mTLS advertisement (auth-profiles.md §`openwop-auth-mtls`).",
          "properties": {
            "supported": { "type": "boolean" },
            "required": {
              "type": "boolean",
              "description": "When `true`, the host rejects bearer-only requests (mTLS required for all authenticated calls). When `false`, mTLS is optional and complements bearer auth."
            },
            "subjectMapping": {
              "type": "string",
              "enum": ["cn", "san-dns", "san-uri"],
              "description": "How the host derives the transport principal from the client certificate. `cn`: subject CN. `san-dns`: subjectAltName DNS entry. `san-uri`: subjectAltName URI entry."
            }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": true
    },
    "discovery": {
      "type": "object",
      "description": "Discovery advertisement (capabilities-change-detection.md). Optional in v1; absence means the host serves only the public unauthenticated payload at /.well-known/openwop. Landed by RFC 0011.",
      "properties": {
        "authScoped": {
          "type": "object",
          "description": "Auth-scoped discovery advertisement (capabilities-change-detection.md §\"Scoped capability views\"). Hosts that return a different payload when called with Authorization than when called anonymously declare it here. The authenticated view MUST still satisfy the base capabilities.schema.json shape per the spec annex.",
          "properties": {
            "supported": {
              "type": "boolean",
              "description": "Host returns an authenticated/tenant-scoped capability view when the discovery endpoint is called with valid bearer credentials."
            },
            "mode": {
              "type": "string",
              "enum": ["same-endpoint", "extension-endpoint"],
              "description": "How the host exposes the auth-scoped view. `same-endpoint`: the canonical /.well-known/openwop returns a narrowed/enriched view when authenticated. `extension-endpoint`: a separate host route carries the scoped view (path advertised via `endpointPath`). Hosts using the third pattern from the spec annex (documentation pointer in the public payload) MAY omit this field."
            },
            "endpointPath": {
              "type": "string",
              "pattern": "^/",
              "description": "When `mode: \"extension-endpoint\"` is advertised, the leading-slash relative path the host serves the scoped view from. The conformance scenario hits this path with bearer credentials. Absolute URLs are rejected at the schema level."
            }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "i18n": {
      "type": "object",
      "description": "Locale-negotiation advertisement per spec/v1/i18n.md. Optional in v1; absence means the host serves a single locale always (typically `en`). When `supported: true`, the host honors `Accept-Language` on every protected route and emits `Content-Language` on responses carrying localized text.",
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Host honors `Accept-Language` and returns localized human-facing text when negotiable."
        },
        "defaultLocale": {
          "type": "string",
          "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8}){0,3}$",
          "description": "BCP 47 language tag the host falls back to when no `Accept-Language` matches `supportedLocales`. Default: `\"en\"` when omitted."
        },
        "supportedLocales": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "items": {
            "type": "string",
            "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8}){0,3}$"
          },
          "description": "BCP 47 tags of locales the host has validated end-to-end for human-facing text. MUST contain `defaultLocale` when both are present."
        }
      },
      "additionalProperties": false
    },
    "content": {
      "type": "object",
      "description": "RFC 0103 (spec/v1/localized-content.md). Localized authored content (pages → sections) advertisement. Reuses the i18n annex's Accept-Language/Content-Language negotiation; it does NOT redeclare negotiation. Requires `i18n.supported: true`. `baseLocale` MUST equal `capabilities.i18n.defaultLocale`; `({baseLocale} ∪ supportedLocales)` MUST be a subset of `capabilities.i18n.supportedLocales`; `baseLocale` MUST NOT appear in `supportedLocales`. Hosts that omit this block serve no content surface; the conformance scenarios skip cleanly.",
      "additionalProperties": false,
      "required": ["supported", "baseLocale", "supportedLocales"],
      "properties": {
        "supported": {
          "type": "boolean",
          "description": "Host serves the localized-content surface (`GET /v1/content/pages/{slug}` + tenant-scoped admin CRUD). Requires `i18n.supported: true`."
        },
        "baseLocale": {
          "type": "string",
          "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8}){0,3}$",
          "description": "The locale section `data` is authored in. MUST equal `capabilities.i18n.defaultLocale`."
        },
        "supportedLocales": {
          "type": "array",
          "uniqueItems": true,
          "items": {
            "type": "string",
            "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8}){0,3}$"
          },
          "description": "BCP 47 tags the host has authored content translations for. MUST NOT contain `baseLocale`; `({baseLocale} ∪ supportedLocales)` MUST be a subset of `i18n.supportedLocales`."
        }
      }
    },
    "portability": {
      "type": "object",
      "description": "RFC 0098 (`Active`). Export/import of a tenant's reusable estate (agents 0070, packs 0003/0013, prompt templates 0027, connection *refs* 0045/0095, schedules 0052, roster/org-chart 0086/0087). An export bundle carries NO credential values — only refs to be re-bound at the destination (RFC 0046/0079). Import maps the estate onto the destination's RFC 0048 identity, MUST offer a no-write dry-run plan, MUST be idempotent, and is gated by an RFC 0049 scope. A host advertising this serves `/v1/host/sample/{export,import}` (promotable to `/v1/{export,import}`) and emits the content-free `import.applied` event. Hosts that omit this block neither export nor import; the conformance scenarios skip cleanly.",
      "additionalProperties": false,
      "properties": {
        "export": {
          "type": "boolean",
          "default": false,
          "description": "Host can emit an export bundle for the caller's tenant/workspace via `GET /export`."
        },
        "import": {
          "type": "boolean",
          "default": false,
          "description": "Host can import an export bundle via `POST /import`. When `true`, `dryRun` MUST also be `true` (a no-write plan preview is mandatory)."
        },
        "kinds": {
          "type": "array",
          "items": { "type": "string", "enum": ["agent", "pack", "prompt-template", "connection-ref", "schedule", "roster", "org-chart"] },
          "uniqueItems": true,
          "description": "Estate kinds this host can export/import."
        },
        "dryRun": {
          "type": "boolean",
          "default": true,
          "description": "Import supports a no-write plan preview (`POST /import?dryRun=true`). MUST be true if `import` is true."
        }
      },
      "if": { "properties": { "import": { "const": true } }, "required": ["import"] },
      "then": { "properties": { "dryRun": { "const": true } }, "required": ["dryRun"] }
    }
  },
  "additionalProperties": true
}
