OpenWOP openwop.dev
FieldValue
RFC0108
TitleSelf-hosted / OpenAI-compatible provider-class advertisement — aiProviders.selfHosted[], the capability-non-inference rule, and the endpoint-non-disclosure invariant
StatusAccepted
Author(s)David Tufts (@davidscotttufts)
Created2026-06-23
Updated2026-06-24 (Active → Accepted — reference-host (openwop-app, ADR 0121, PR #725 merged) conformance-harness witness, single-witness bootstrap steward waiver; see Status history). 2026-06-24 (Draft → Active — bootstrap-phase steward waiver, 7-day comment window waived)
Affectsspec/v1/capabilities.mdaiProviders — adds the selfHosted[] field + the self-hosted advertisement rules) · spec/v1/host-capabilities.md (cross-reference) · schemas/capabilities.schema.json (adds optional aiProviders.selfHosted) · SECURITY/invariants.yaml (adds self-hosted-endpoint-no-disclosure) · SECURITY/threat-model-secret-leakage.md (cross-reference) · ≥1 new conformance scenario · INTEROP-MATRIX.md · CHANGELOG
Compatibilityadditive per COMPATIBILITY.md §2.1
Supersedes
Superseded by

Summary

Adds an optional aiProviders.selfHosted: string[] advertisement (a subset of aiProviders.supported, mirroring the existing byok[] shape) that marks which advertised provider ids are operator- or tenant-configured OpenAI-compatible endpoints (Ollama, vLLM, LM Studio, or any /v1/chat/completions-compatible server) rather than a host-managed connection to a known public vendor. It pins three normative rules a host MUST follow to advertise such a provider honestly: (1) advertise it only when a real configured + reachable endpoint backs it (the truthful-advertisement honesty rule, enforced by OPENWOP_REQUIRE_BEHAVIOR); (2) never disclose the endpoint's network location (base-URL / host / port) on any wire surface — operator-private infrastructure; (3) a client MUST NOT infer model capabilities from a self-hosted provider id — capability is governed solely by the host's existing RFC 0031 modelCapabilities.advertised[] and RFC 0091 modality advertisement. It is the wire-gate that unblocks openwop-app ADR 0121 (local / OpenAI-compatible model provider support).

Motivation

OpenWOP hosts today advertise the AI providers their proxy can route to in capabilities.aiProviders.supported[] (an array of provider-id strings) and the subset that permits BYOK in aiProviders.byok[] (schemas/capabilities.schema.json §aiProviders, lines 749–868). The conventional provider vocabulary (RFC 0067 §C, advisory, not a closed set) already lists ollama and vllm, and supported[] tolerates arbitrary ids ("clients MUST tolerate unknown ids"). So a host can already put ollama in supported[].

What the wire cannot express today is the distinction that makes such an advertisement honest and safe:

1. Managed-cloud vs operator-configured endpoint is invisible. A client reading supported: ["anthropic", "ollama"] cannot tell that anthropic is a host-managed connection to a known public vendor while ollama is an OpenAI-compatible endpoint someone stood up at a private base-URL. These are materially different trust, capability, and key-custody surfaces. 2. No honesty rule binds the claim to a real endpoint. A host could list ollama with no endpoint configured; a client (or OPENWOP_REQUIRE_BEHAVIOR=true conformance) has no normative hook that says "advertise this class only when a reachable endpoint actually backs it." This is the exact dishonest-capability failure mode RFC 0031 §C ("truthful advertisement") and capabilities.md ("advertise what you implement") guard against for other surfaces, but it has never been stated for endpoint-backed providers. 3. Capability inference from a compat id is unsafe. For a known vendor a client may reasonably assume a vendor capability set. For an opaque OpenAI-compatible endpoint it must not — the operator may run any model, of any capability, behind that id. There is no rule today forbidding that inference. 4. The endpoint URL is operator-private infrastructure with no non-disclosure rule. A tenant's internal endpoint (e.g. https://vllm.internal:8000/v1) leaks network topology and enables SSRF reconnaissance if it surfaces in the capabilities document, a run.* event, an error envelope, or a debug bundle. The corpus has no SSRF/egress invariant; the wire concern here is narrower and belongs on the wire: non-disclosure of the endpoint location.

Every surveyed reference app ships this class of provider — LibreChat (api/app/clients/OllamaClient.js + the custom-endpoint path), Jan (RemoteOAIEngine), AnythingLLM (server/utils/AiProviders/{ollama,lmStudio,localai,...}), Open WebUI (routers/ollama.py + OpenAI-compatible routers/openai.py) — and openwop-app ADR 0121 wants to. The host plumbing (a compat dispatch case, a Connection-stored base-URL, a BYOK key) is non-normative and rides Accepted RFCs (0046 credentials, 0007 dispatch, 0031 capabilities). The advertisement is the normative gate, and it is the right place to put rules (1)–(4) so every host expresses this class the same honest way.

The spec is the right place because the advertisement is read cross-host by clients and the conformance suite; an ad-hoc per-host convention would re-introduce exactly the silent-incompatibility and dishonest-claim problems the capability surface exists to prevent.

Proposal

§A — aiProviders.selfHosted[] (additive optional field)

Add one optional field to the aiProviders object in schemas/capabilities.schema.json:

     "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. …" },
         "byok": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true, "description": "Subset of `supported` for which BYOK is permitted. …" },
+        "selfHosted": {
+          "type": "array",
+          "items": { "type": "string", "minLength": 1 },
+          "uniqueItems": true,
+          "description": "Subset of `supported` whose entries are operator- or tenant-configured OpenAI-compatible endpoints (e.g. an Ollama / vLLM / LM Studio / any `/v1/chat/completions`-compatible server), as opposed to a host-managed connection to a known public vendor. Each entry MUST also appear in `supported`. An entry MAY also appear in `byok` (the endpoint requires a client/tenant-supplied key) or be absent from it (the endpoint needs no key, e.g. a default Ollama). The provider id is an OPAQUE label chosen by the host; it MUST NOT encode the endpoint's network location (per RFC 0108 §A.2). A client MUST NOT infer model capabilities from a `selfHosted` id (per RFC 0108 §B)."
+        },
         "…": "… (authModes, policies, speechSynthesis, input, realtimeVoice, maxInlineMediaBytes unchanged)"
       },
       "additionalProperties": false
     }

Add to spec/v1/capabilities.md §aiProviders, after the byok[] paragraph:

A.1 — Subset constraint. Every entry of aiProviders.selfHosted[] MUST also appear in aiProviders.supported[]. A selfHosted entry MAY additionally appear in aiProviders.byok[] (the endpoint requires a tenant-supplied key) or not (the endpoint needs no key). selfHosted is OPTIONAL; an absent or empty array means the host advertises no self-hosted/compatible endpoints (its supported[] entries are all host-managed or BYOK-cloud connections — today's behavior).

A.2 — Truthful advertisement (normative). A host MUST list a provider id in aiProviders.selfHosted[] only when the host can route to at least one configured, reachable OpenAI-compatible endpoint for that id within the advertising scope. Listing a self-hosted provider with no backing endpoint is a dishonest capability claim per capabilities.md §"Truthful advertisement" and is non-conformant; OPENWOP_REQUIRE_BEHAVIOR=true MUST fail it. (The capabilities document is host-scoped: a host MAY advertise the class when at least one tenant in the advertising scope has a configured endpoint; per-tenant existence is not separately advertised.)

A.3 — Endpoint non-disclosure (normative). The provider id in selfHosted[] is an opaque host-chosen label. A host MUST NOT encode the endpoint's network location (scheme, host, port, path, or base-URL) in the provider id, and MUST NOT disclose that location on any wire surface (the capabilities document, any run.* event payload, error envelopes, the debug bundle, exports, or replay state). See the self-hosted-endpoint-no-disclosure SECURITY invariant (§D).

§B — Capability non-inference for self-hosted providers (normative)

Add to spec/v1/capabilities.md §aiProviders (and cross-reference from host-capabilities.md §"Model-capability declarations"):

For a provider id present in aiProviders.selfHosted[], a client MUST NOT infer model capabilities (e.g. structured-output, discriminator-enum, function-calling, long-context, reasoning per RFC 0031 §C, or input modalities per RFC 0091) from the provider id or from any known-vendor capability mapping. The only authoritative source of a self-hosted provider's capabilities is what the host actually advertises and gates on: capabilities.modelCapabilities.advertised[] (RFC 0031 §E) and aiProviders.input.modalities (RFC 0091). A self-hosted endpoint whose capabilities the host does not advertise is treated as text-only; the host MUST refuse a request for an unadvertised capability or modality per the RFC 0031 §B model-capability gate (capability_not_provided), exactly as for any other provider. This rule prevents a client from assuming, e.g., that a selfHosted id named ollama supports vision merely because some public deployment of that engine does.

How a host derives a self-hosted endpoint's capabilities — a static declaration captured at configuration time, or a runtime probe of the endpoint — is host-internal and out of scope (see Unresolved question 2). The wire surface is the advertised result, identical to RFC 0031 §C ("the protocol does not normate how the host derives the mapping").

§C — Key custody (carry-forward, non-normative restatement)

When a selfHosted endpoint requires a key, that key is a BYOK credential governed by RFC 0046 (transmitted only as an opaque CredentialReference, resolved into the dispatch path, never in plaintext on the wire) and the credential-payload-redaction / SR-1 (secret-leakage-eventlog-payload) invariants. This RFC adds no new credential surface; it relies on RFC 0046 verbatim. The endpoint base-URL is configuration, not a credential — but it is operator-private infrastructure, which §A.3 + §D govern with their own non-disclosure rule.

§D — SECURITY invariant: self-hosted-endpoint-no-disclosure

Add to SECURITY/invariants.yaml:

  - id: self-hosted-endpoint-no-disclosure
    tier: protocol
    severity: medium
    threat_model: SECURITY/threat-model-secret-leakage.md
    tests:
      - conformance/src/scenarios/aiproviders-selfhosted-shape.test.ts
    note: |
      RFC 0108 §A.3: the network location (scheme, host, port, path, base-URL)
      of an `aiProviders.selfHosted[]` endpoint is operator-private
      infrastructure and MUST NOT appear on any wire surface — the capabilities
      document, any run.* event payload, error envelopes, the debug bundle,
      exports, or replay state. The provider id is an opaque host-chosen label
      and MUST NOT encode the endpoint URL. Disclosing the endpoint leaks
      internal network topology and enables SSRF reconnaissance against the
      operator's network. Distinct from credential-payload-redaction (RFC 0046):
      the endpoint URL is not a credential, but it is private infrastructure.

Severity is medium (not critical): the leaked datum is a network location, not key material; the operator chose to expose an OpenAI-compatible endpoint to their host; and the principal harm (SSRF reconnaissance / topology disclosure) is a step toward, not itself, compromise. It pairs with the §E shape scenario, which statically asserts that no selfHosted[] id is URL-shaped.

§E — Conformance

One new scenario lands with this RFC; a second behavioral scenario is sketched for the Active→Accepted gate.

  • aiproviders-selfhosted-shape.test.ts (server-free, always-run shape gate; soft-skips when aiProviders.selfHosted is absent per coverage.md §"Capability-gated scenarios"). Asserts, for the host's published capabilities document:

1. aiProviders.selfHosted (when present) is a string[] with uniqueItems; 2. every entry is a subset of aiProviders.supported (§A.1); 3. no entry is URL-shaped — no entry contains ://, a bare host:port, or a leading / path (a cheap static enforcement of the §A.3 / §D non-disclosure rule that the opaque id does not encode the endpoint). Uses driver.describe('capabilities.md §aiProviders', 'selfHosted is a non-URL subset of supported') so failures cite the requirement. <1s, no LLM.

  • aiproviders-selfhosted-honesty.test.ts (Active→Accepted gate; gated on aiProviders.selfHosted.length &gt; 0 + the existing capabilities fetch). Host-attested behavioral check: drives one chat/dispatch against a selfHosted provider id and asserts (1) the dispatch succeeds or fails with a transport error from a real endpoint (not a "no provider configured" capability_not_provided, which would prove the §A.2 dishonest-advertisement violation), and (2) neither the resulting run.* events nor any error payload contains a URL-shaped string matching the host's (test-supplied, out-of-band) endpoint location (the §D disclosure check). Gated; soft-skips for hosts that don't advertise the class.

The behaviorGate helper gains a requireSelfHostedProviders() predicate, mirroring requireModelCapabilities() (RFC 0031).

Compatibility

Additive per COMPATIBILITY.md §2.1:

  • New optional field in a server-emitted shape (the capabilities document). aiProviders is additionalProperties: false, so selfHosted is added to its properties (the additive schema change); server-emitted shapes are open by convention, so v1.x hosts that don't emit it remain conformant, and clients that don't read it see today's behavior (a selfHosted provider simply appears as an opaque entry in supported[], exactly as a host could already list ollama).
  • No existing field changes. supported[], byok[], authModes, policies are untouched.
  • No event-type, endpoint, or error-code changes. §B reuses RFC 0031's capability_not_provided; §C reuses RFC 0046 verbatim.
  • New MUSTs apply only to hosts that advertise the new field. §A.2/§A.3/§B/§D bind a host once it lists a provider in selfHosted[]. A host that never advertises the class is unaffected.
  • New SECURITY invariant is additive (a new MUST-NOT scoped to the new field), gated by the §E scenario.

Forward-compatibility guarantees: a client on an older spix that ignores selfHosted[] treats every supported[] entry as it does today (managed or BYOK per byok[]); a host that omits selfHosted[] makes no new claim. There is no migration; nothing existing must change.

Conformance

  • Existing coverage: aiproviders-input-shape.test.ts (RFC 0091 modality shape), aiproviders-speechsynth-shape.test.ts (RFC 0105), providerPolicyEnforcement.test.ts (RFC 0067 policies), provider-usage.test.ts (RFC 0026) — all capability-gated on an aiProviders.* sub-field. This RFC follows the same server-free-shape-first pattern.
  • New scenarios: §E above (aiproviders-selfhosted-shape.test.ts at Draft→Active; aiproviders-selfhosted-honesty.test.ts at Active→Accepted).
  • Capability gating: both soft-skip (SKIPPED, not FAILED) when aiProviders.selfHosted is absent/empty, per coverage.md.

Alternatives considered

1. Do nothing — rely on the advisory vocabulary. supported[] already tolerates ollama/vllm. Rejected: it leaves the four motivating gaps open — managed vs self-hosted is invisible, no honesty rule, no capability-inference ban, no endpoint-non-disclosure invariant. A host listing ollama today is making an ambiguous, ungoverned claim; that is exactly the dishonest-capability failure the surface exists to prevent. 2. A richer aiProviders.providerClasses map ({ "&lt;id&gt;": { class: "managed-cloud" | "byok-cloud" | "byok-self-hosted", capabilitySource: "advertised" | "probe" } }). Deferred (Unresolved question 1). A boolean-style selfHosted[] subset mirrors the existing byok[] minimalism and closes the gate; the richer taxonomy is itself additive and can land later without breaking selfHosted[] (a class map would make selfHosted derivable). Shipping the map now over-specifies a taxonomy before any host needs the byok-cloud vs managed-cloud distinction on the wire. 3. A new top-level capability block (capabilities.selfHostedProviders) separate from aiProviders. Rejected — this is intrinsically an aiProviders refinement (a subset-of-supported classifier); a sibling block fragments the provider surface and forces clients to reconcile two provider lists. 4. Define a normative SSRF / egress requirement on the wire (the host MUST SSRF-guard the configured base-URL). Rejected — egress enforcement is host-internal (CONTRIBUTING.md §"What's in scope" excludes internal data structures + implementation); the corpus has no egress invariant for good reason. The wire-observable concern is non-disclosure of the endpoint (§A.3/§D), not how the host fetches it. SSRF-guarding the configured endpoint remains the host's responsibility (openwop-app ADR 0121 §egress; the LibreChat #13919 lesson) and is called out as an implementation note, not normated here. 5. "do nothing" / defer to per-host READMEs. Rejected — cross-host clients and the conformance suite read the capabilities document, not host READMEs; an unspecified convention reproduces silent incompatibility.

Unresolved questions

1. Flag vs taxonomy. Ship the minimal selfHosted[] subset (this RFC) or the richer providerClasses map (managed-cloud / byok-cloud / byok-self-hosted) now? Recommendation: flag now; promote to a map additively when a host needs the managed-vs-byok-cloud distinction on the wire. (Blocks nothing — the map is a future additive bump.) 2. Declared vs probed capability source. Should the wire advertise how a self-hosted provider's capabilities were derived (operator declaration vs runtime probe)? Recommendation: no — capability advertisement is RFC 0031's job and the source is host-internal (RFC 0031 §C precedent); a client cares about the advertised result, not its provenance. (openwop-app ADR 0121 OQ-2.) 3. Native-protocol vs OpenAI-wire endpoints. A few engines (e.g. Ollama's native /api/) speak a non-OpenAI protocol. Does the wire need to distinguish "OpenAI-compatible" from "native"? Recommendation: no — the provider id + the host's modelCapabilities/modality advertisement fully describe what a client may send; the transport is host-internal. The field name says "OpenAI-compatible" as the common case but the rules are protocol-agnostic. 4. Loopback vs internal-network self-hosted. ADR 0121 OQ-3 distinguishes true-local (http://localhost:11434) from internal-network endpoints for the host's SSRF policy. Does the wire need to express that? Recommendation: no — that is an egress-policy detail (host-internal, see Alternative 4); the wire only requires that the location is not disclosed. 5. Per-scope honesty semantics. §A.2 makes the capabilities document host-scoped ("advertise the class when ≥1 tenant has a configured endpoint"). Confirm clients do not read selfHosted[] as "you have an endpoint" but as "this host supports* configuring one." Recommendation: document the host-scope reading explicitly in capabilities.md (the same scope semantics byok[] already has).

Implementation notes (non-normative)

  • Reference host: openwop-app ADR 0121 (docs/adr/0121-local-model-provider-support.md). The host adds a compat dispatch case (reusing its existing OpenAI-compatible chat-completions path with a per-connection base-URL — the minimax precedent), stores {baseUrl, apiKey?, capabilities?} as an ADR 0024 Connection, and advertises the provider id in aiProviders.supported[] and aiProviders.selfHosted[] only when a reachable endpoint is configured for the advertising scope (the §A.2 honesty gate, enforced under OPENWOP_REQUIRE_BEHAVIOR). Its routes/discovery.ts populates selfHosted[] from the configured compat Connections; its modelCapabilityProbe contributes the endpoint's declared capabilities to modelCapabilities.advertised[] (§B).
  • Endpoint non-disclosure (§D) is satisfied by choosing a stable opaque id (e.g. compat or compat:&lt;connectionLabel&gt; with no URL) and never serialising the base-URL into discovery, events, or errors. The §E shape scenario statically catches a URL-shaped id; the host is responsible for keeping the base-URL out of event/error payloads (the same redaction harness as RFC 0046).
  • Egress (SSRF-guarding the operator-supplied base-URL: private-IP block, https-only except an explicitly-enabled loopback, redirect re-validation) is host-internal per Alternative 4 and lives in ADR 0121 §egress — not normated here.
  • Effort: schema field + spec text ~0.5 day; the SECURITY invariant ~0.25 day; the shape conformance scenario ~0.5 day; reference-host advertisement + the honesty gate (mostly ADR 0121 work) ~1 day; CHANGELOG + INTEROP-MATRIX ~30 min.

Acceptance criteria

Promotion Draft → Active (✅ met 2026-06-24 — bootstrap-phase steward waiver; see Status history):

  • [x] Comment window: waived under the bootstrap-phase steward waiver (RFC 0031 / 0046 / 0095 precedent) — single-steward repo, zero external reviewers.
  • [x] Unresolved questions 1–5 each have a recommended resolution recorded (§Unresolved questions); none is critical or blocking.
  • [x] Compatibility classification firm: additive (§Compatibility).
  • [x] Wire-shape frozen: the aiProviders.selfHosted[] field shape + the §A/§B/§D normative rules are locked — no shape changes before Accepted.
  • [x] All five architect passes complete; conformance scenarios sketched (§Conformance); threat model clear (§D).
  • ↪ The spec/v1/capabilities.md §A/§B prose + schemas/capabilities.schema.json field edits land at Accepted (below), per the RFC 0031 Draft→Active precedent (shape locked at Active; text + conformance + reference-host at Accepted).

Promotion Active → Accepted (✅ met 2026-06-24 — see Status history):

  • [x] spec/v1/capabilities.md + host-capabilities.md cross-reference text merged (#757).
  • [x] schemas/capabilities.schema.json merged with aiProviders.selfHosted (#757).
  • [x] SECURITY/invariants.yaml gains self-hosted-endpoint-no-disclosure (§D) (#757; protocol-tier, total 138 / protocol 108).
  • [x] aiproviders-selfhosted-shape.test.ts lands in @openwop/openwop-conformance; suite minor-version bumps (#757, suite 1.36.0; the gated aiproviders-selfhosted-honesty.test.ts landed #759, suite 1.37.0).
  • [x] CHANGELOG entry under [Unreleased].
  • [x] INTEROP-MATRIX.md gains the aiProviders.selfHosted advertisement column/note.
  • [x] Reference host (openwop-app, ADR 0121, PR #725 merged) advertises aiProviders.selfHosted[] for a configured compat endpoint, passes aiproviders-selfhosted-shape.test.ts + the honesty scenario non-vacuously under OPENWOP_REQUIRE_BEHAVIOR=true, and keeps the endpoint URL off the wire (§D). Closed under the single-witness bootstrap-phase steward waiver (the RFC 0031 / 0046 / 0095 precedent). Witness is the host's conformance harness (a real loopback OpenAI-compatible mock endpoint stood up by conformance/run.ts, production-dark) — the appropriate evidence tier for an intrinsically operator-private surface that §A.3/§D + the Motivation §4 SSRF-reconnaissance threat say MUST NOT be publicly disclosed, mirroring the RFC 0035 harness-graduation precedent (a deployed publicly-curl-able witness would contradict the surface's own non-disclosure model).

References

  • RFCS/0031-envelope-variants-and-model-capabilities.md — the capabilities.modelCapabilities.advertised[] surface §B relies on for capability non-inference; the "truthful advertisement" normation §A.2 mirrors.
  • RFCS/0046-host-credentials-capability.md — the BYOK CredentialReference + credential-payload-redaction invariant §C carries forward for the optional endpoint key.
  • RFCS/0067-* (provider auth modes / advisory vocabulary) — aiProviders.supported/byok/authModes/policies shape and the advisory provider vocabulary that already lists ollama/vllm.
  • RFCS/0007-dispatch.md, RFCS/0026-provider-usage-event.md — adjacent provider-dispatch + usage surfaces (no change here).
  • RFCS/0091-*aiProviders.input.modalities, the other authoritative capability source named in §B.
  • spec/v1/capabilities.md §aiProviders — the surface extended.
  • SECURITY/threat-model-secret-leakage.md, SECURITY/threat-model-provider-policy.md — the threat models §D/§B reference.
  • schemas/capabilities.schema.json §aiProviders (lines 749–868) — the object the selfHosted field is added to.
  • openwop-app docs/adr/0121-local-model-provider-support.md — the reference-host consumer this RFC unblocks; docs/research/2026-06-23-ai-chat-competitive-analysis.md §9 (B12), §11 — the cross-competitor motivation (LibreChat OllamaClient.js, Jan RemoteOAIEngine, AnythingLLM local providers, Open WebUI routers/ollama.py).

Status history

Active → Accepted (2026-06-24)

Graduated on the reference-host (openwop-app, ADR 0121) conformance-harness witness, closed under the single-witness bootstrap-phase steward waiver (the RFC 0031 / 0046 / 0095 precedent named in the Draft → Active evidence).

Evidence at promotion:

  • Spec/schema/security/conformance landed (PR #757, origin/main 7d5367cd): aiProviders.selfHosted in schemas/capabilities.schema.json; §A.1/A.2/A.3 + §B prose in spec/v1/capabilities.md (+ host-capabilities.md cross-ref); the self-hosted-endpoint-no-disclosure SECURITY invariant (protocol-tier, medium — total 138 / protocol 108); the always-on aiproviders-selfhosted-shape.test.ts. The gated behavioral aiproviders-selfhosted-honesty.test.ts landed in PR #759 (9b12bd95).
  • Conformance published: @openwop/openwop-conformance@1.36.0 (shape leg, tag openwop-conformance/v1.36.0) and 1.37.0 (honesty leg, tag openwop-conformance/v1.37.0), both live on npm.
  • Reference-host witness (openwop-app, ADR 0121, PR #725 merged): the host advertises aiProviders { supported:[…,"compat"], selfHosted:["compat"] } and passes both aiproviders-selfhosted-shape (always-on) and aiproviders-selfhosted-honesty (gated) non-vacuously under OPENWOP_REQUIRE_BEHAVIOR=true vs conformance 1.37.0. The honesty leg dispatches against the advertised compat id and reaches a real configured endpoint (HTTP 200 from a loopback OpenAI-compatible mock — not capability_not_provided, proving §A.2 truthful advertisement), the id is non-URL (compat, §A.3), and the endpoint location appears in neither the response body nor any error payload (§D, via the compat_transport_error scrub). Production stays dark (OPENWOP_COMPAT_PROVIDER_ENABLED unset ⇒ selfHosted: []) — no dishonest live claim.
  • Evidence-tier rationale (steward + architect): the witness is the host's conformance harness (conformance/run.ts stands up the real loopback mock + sets OPENWOP_TEST_COMPAT_ENDPOINT + OPENWOP_REQUIRE_BEHAVIOR), not a deployed publicly-curl-able discovery doc as the RFC 0100 / 0105 / 0106 managed-cloud graduations used. This is the appropriate tier for this surface: a self-hosted / OpenAI-compatible endpoint is intrinsically operator-private infrastructure that §A.3 / §D + the Motivation §4 SSRF-reconnaissance threat say MUST NOT be publicly disclosed, so a public deployed witness would contradict the surface's own non-disclosure model. It mirrors the RFC 0035 sandbox harness-graduation precedent (behavioral invariants proven by a conformance probe driving a seam, not a public deployment). The steward independently verified the witness by code-inspecting the honesty-gated emission (discovery.ts) + the real-mock setup (conformance/run.ts), confirming it is non-vacuous and not a fabricated result.
  • Single-witness, no dual-witness required: per the RFC's own §Acceptance text, the bootstrap-phase steward waiver permits closing on the single openwop-app reference-host witness; MyndHyve dual-witness is not required for this operator-config surface (unlike the 0105 / 0106 dual-witness path).

Compatibility: this promotion is non-normative — a status change only; no wire, schema, or behavior change in this commit. additive per COMPATIBILITY.md §2.1; RFC counts Accepted +1 / Active −1.

Draft → Active (2026-06-24)

Promoted under the bootstrap-phase steward waiver per CONTRIBUTING.md §"Bootstrap-phase notes" + MAINTAINERS.md §"Bootstrap-phase RFC waivers" — the same path RFC 0031, RFC 0046, and RFC 0095 used. The 7-day comment window is waived (single-steward bootstrap repo; zero external reviewers). The wire-shape is locked; the spec/schema text, the self-hosted-endpoint-no-disclosure SECURITY invariant, the aiproviders-selfhosted-shape.test.ts conformance scenario, and the reference-host (openwop-app ADR 0121) advertisement remain the path to Accepted.

Evidence at promotion:

  • Classification firm — additive per COMPATIBILITY.md §2.1: one optional field on a server-emitted shape; no existing field / event / error changes; new MUSTs bind only hosts that advertise aiProviders.selfHosted[]; no migration.
  • Wire-shape locked: aiProviders.selfHosted: string[] (subset of supported, mirroring byok[]) + §A.1/A.2/A.3 (subset constraint, truthful advertisement, endpoint non-disclosure) + §B (capability non-inference) + the §D self-hosted-endpoint-no-disclosure invariant. No shape changes expected before Accepted.
  • Five architect passes complete; no CRITICAL gaps. The gap register (G1–G7) and risk register (R1–R6) carry only Active/Accepted-stage items; the §Unresolved-questions recommendations stand as the working decisions (flag-now over a providerClasses map; declared-or-probed capability source host-internal; egress host-internal).
  • Path to Active → Accepted: land the §A/§B prose in spec/v1/capabilities.md (+ host-capabilities.md cross-ref) and the aiProviders.selfHosted field in schemas/capabilities.schema.json; add the SECURITY invariant + the shape conformance scenario; openwop-app (ADR 0121) advertises selfHosted[] for a configured compat endpoint and passes — closable under the steward waiver or via dual-witness with a second advertising host.

Compatibility: this promotion is non-normative — a status change only; no wire, schema, or behavior change in this commit.

Draft (2026-06-23)

Authored via the five-architect pass (/prd). RFC + gap (G1–G7) + risk (R1–R6) registers landed (PR #755). The wire-gate for openwop-app ADR 0121 (local / OpenAI-compatible model provider).