OpenWOP openwop.dev
RFC details authors, dates, what it affects, review history
FieldValue
RFC0242
Titlea negotiation record says what was asked for, and an inbound negotiation has a home
StatusAccepted
Author(s)David Tufts (@davidscotttufts)
Created2026-10-10
Updated2026-10-11 — Active → Accepted, provisional pending the RFC 0156 §B retrospective review (STEWARD OVERRIDE of RFC 0147 §A.6, register row not-reviewed). Evidence tier: tier-1 — the v2 reference host (openwop-examples), a reference example and not a production host: its certified public cut on published suite 2.46.0 (evidence/v2-host-bundles/openwop-host-v2-reference-2.46.0.json, build commit:d16cac40, witness 622b2d3b90fd, signed v2-reference-4, 531 pass / 1 fail, all three claimed profiles certified) records all seven openwop.requirement.0242. ids executed-pass. Its one failure, v2-webhook-message-id-stable, is a suite defect (a receiver close() held open by the tunnel; fixed in 2.46.1), not a host or RFC 0242 row. G1 closes; G2 is externally gated on the tripwire RFC 0175 G6 shares. · 2026-10-10 — Draft → Active, comment window waived by the maintainer (2026-10-10: "waive it"), recorded as a STEWARD OVERRIDE of RFC 0147 §A.6 in MAINTAINERS.md. §A–§D are merged (suite 2.46.0). Proven against the v2 reference host carrying the §B implementation (openwop-examples, rfc0242/inbound-negotiation-record): every 0242. leg passes, and fails when the host drops requested. · 2026-10-10 — filed Draft after an /architect design review (2026-10-10) of two defects openwop-app found implementing negotiation.decided (its ADR 0858 P4): the event cannot record a downgrade, and an inbound negotiation has no log to land on.
Affectsschemas/v2/run-event-payloads.schema.json $defs/negotiationDecided (one optional property; seeded, so its seed rule in scripts/derive-v2-schemas.mjs) · schemas/v2/host-event.schema.json (one reserved protocol type) · spec/v2/core/interop.md §The audit event · spec/v2/core/events.md §Host events · conformance/src/scenarios/v2-negotiation-decided-emitted.test.ts and a new v2-negotiation-decided-inbound.test.ts · SECURITY/invariants.yaml (a2a-version-no-silent-downgrade, mcp-version-no-silent-downgrade)
Compatibilityadditive (COMPATIBILITY.md §2.4): one optional property on a closed payload, and one reserved type on the optional hostEvents family. No field is renamed or removed, no enum changes, no MUST relaxes.
Supersedes—
Superseded by—

Summary

interop.md requires every A2A and MCP version negotiation to leave a negotiation.decided record, and names that record as the witness that no host downgrades silently. Two things stop it doing that job. The closed payload has no seat for the version the peer asked for, and admits only accepted or refused, so a downgrade reads exactly like a normal accept; the prose's outcome: downgraded cannot be recorded. And the event is a run event, so a negotiation that happens before any run exists (a peer fetching the host's agent card, an MCP client's initialize) has nowhere to go. This RFC adds an optional requested field and gives a runless negotiation a home as a durable host event (RFC 0236).

Motivation

The prose and the schema disagree. spec/v2/core/interop.md §The audit event shows { protocol, peer, requested, negotiated, outcome: accepted | downgraded | refused, reason } and says a downgrade above the floor "reports outcome: downgraded". The closed payload (run-event-payloads.schema.json#/$defs/negotiationDecided) is { protocol, outcome: accepted | refused, version?, floor?, peerDigest?, reason?, at }. Both hosts that emit the event follow the schema: the v2 reference host (src/interop.ts, 0175.negotiation-decided-emitted executed-pass on its certified cuts) and openwop-app (src/host/negotiationDecided.ts, which records a downgrade as accepted and reports the disagreement).

So a downgrade is invisible. Without requested, a reader of the log cannot tell "the peer asked for 0.3 and got it" from "the peer asked for 1.0 and was given 0.3". The second is the silent downgrade that a2a-version-no-silent-downgrade and mcp-version-no-silent-downgrade forbid, and this event is their normative witness (RFC 0175 §D.5).

An inbound negotiation cannot comply. interop.md §Negotiation binds both directions: the peer identity is "the caller's Subject … or the host's own outbound identity". A host acting as the A2A server or MCP server negotiates before any run exists, so the run-event home is unreachable, and the MUST cannot be met.

Proposal

§A. What was asked for

  • A.1. negotiationDecided gains an optional property requested (string): the version (A2A) or revision (MCP) the requesting side named. It uses the same grammar as version.
  • A.2. A host MUST set requested whenever the requesting side named a version: inbound, the version the caller named (A2A-Version, or MCP initialize protocolVersion); outbound, the version the host asked the peer for.
  • A.3. A downgrade is an accepted record whose version differs from requested. interop.md's "Downgrade above the floor" sentence is restated in those terms; outcome keeps its two values.

§B. Where a runless negotiation is recorded

  • B.1. A negotiation made inside a run is recorded on that run's log, as today.
  • B.2. Any other negotiation is recorded as a durable host event of reserved type negotiation.decided (host-event.schema.json), whose payload is negotiationDecided and whose delivery is durable. It belongs to the caller's tenant, and to the caller's workspace when the request names one.
  • B.3. A host that serves either protocol inbound (it advertises a2a.agentCardUrl, or mcp.serverUrls or mcp.serverMount) MUST advertise hostEvents and list { "type": "negotiation.decided", "delivery": "durable" } in hostEvents.types[].
  • B.4. An exchange with no authenticated caller has no tenant, and the events.md tenant gate forbids delivering its event to anyone else. Such an exchange cannot lower the version below preferredVersion (interop.md §Authentication), so there is no downgrade for the record to reveal. The host still records it; that record reaches no subscriber, and the obligation is unwitnessable from outside (falsifiability row B.4).

The type name is shared with the run event on purpose. channel.presence is already registered in both run-event.schema.json and host-event.schema.json; the envelope tells them apart, since a host event never carries runId.

§C. Prose and schemas

  • interop.md §The audit event: the inline payload block is replaced by a pointer to the schema, the downgrade sentence restated per A.3, and one sentence added for §B. The core word budget is at its cap (38,000 / 38,000), so the edit is word-neutral or shorter.
  • events.md §Host events: negotiation.decided joins channel.presence as a reserved protocol type.
  • run-event-payloads.schema.json: requested added to negotiationDecided; the seed rule in derive-v2-schemas.mjs carries it so --check stays green.
  • host-event.schema.json: one allOf branch binding negotiation.decided to delivery: durable and payload to negotiationDecided.

§D. Conformance

  • v2-negotiation-decided-emitted additionally asserts that requested is present and equals the version the seam's exchange named.
  • New v2-negotiation-decided-inbound, gated on a2a.agentCardUrl or mcp.serverUrls / mcp.serverMount. It reads discovery for B.3, then as an authenticated client makes one inbound exchange that names preferredVersion and one that names a version below the floor, and reads /host/events for each record. No seam is needed.
  • Both silent-downgrade invariants list the two v2 scenarios in tests, and their witness class is re-reviewed once the inbound leg is certified on a host.

Falsifiability — one row per normative requirement

RequirementObservable — what an outside party seesWho can cause the conditionVerdict
A.2 requested set, outboundrequested on the run's negotiation.decided, equal to the version the exchange namedthe suite, through the §22/§23 invoke seams (openwop.requirement.0242.requested.outbound, openwop.requirement.0242.requested.outbound.mcp)witnessable — gated (seam drives the exchange; the record is read on the normative surface)
A.2 requested set, inboundrequested on the host event, equal to the version the suite namedthe suite, as an inbound A2A or MCP client (openwop.requirement.0242.inbound.recorded, openwop.requirement.0242.inbound.recorded.mcp)witnessable — gated
A.3 a downgrade reads as onean accepted record with version ≠ requestedthe suite, against a host whose versions[] spans floor to preferredwitnessable — gated; no host in INTEROP-MATRIX.md offers such a span today (G2)
B.2 runless homea negotiation.decided frame on /host/events for an inbound exchange, delivery: durable, no runIdthe suite, as an inbound client (openwop.requirement.0242.inbound.recorded, openwop.requirement.0242.inbound.refused, openwop.requirement.0242.inbound.recorded.mcp, openwop.requirement.0242.inbound.refused.mcp)witnessable — gated
B.3 listed when inbound is servedhostEvents.types[] contains the durable type whenever the inbound facet fields are presentthe suite, reading discovery (openwop.requirement.0242.inbound.listed)witnessable — unaided
B.4 unauthenticated exchange recordednothing reaches any tenant—unwitnessable — the record has no tenant audience by construction

Compatibility

additive. requested is an optional property on a closed v2 object (COMPATIBILITY.md §2.4); events recorded before it existed stay valid on replay, fork and poll. negotiation.decided as a host event is a new reserved type on an optional family. A.2 and B.3 apply from the first suite release that ships them (§2.3): they give an existing MUST ("every negotiation outcome … MUST emit") a way to be met, rather than adding a new obligation. Committed bundles stay valid measurements at their suite version.

Alternatives considered

  1. Add downgraded to outcome. A reader pinned to an earlier 2.x schema (versioning.md §4) rejects the event, so for that reader the event's shape changes, which §2.2 forbids within a major. requested carries the same fact as an optional field.
  2. Erratum only: align the prose to the schema and drop the downgrade sentence. It removes the only signal that a downgrade happened, which is the reason the event exists.
  3. Narrow the MUST to in-run negotiation. It relaxes a MUST (§2.2), and leaves the inbound direction, which interop.md §Authentication binds, with no record.
  4. A distinct name, negotiation.inbound-decided. A second name for one fact; channel.presence already lives in both registries, and the envelope disambiguates.
  5. Deliver an unauthenticated record to an operator channel. v2 has no operator-scoped subscription surface, and inventing one is outside this RFC.

Decisions

Settled in the /architect review (2026-10-10):

  1. The schema's field names stand. Both emitters ship them; renaming would break every emitter.
  2. The same type name in both registries, following channel.presence.
  3. An unauthenticated exchange is recorded but delivered to no one (B.4), rather than relaxing the MUST or inventing an audience.

Implementation notes (non-normative)

  • v2 reference host (openwop-examples): set requested in src/interop.ts and src/mcp-client.ts; emit the host event from its agent-card and MCP server routes; list the type in hostEvents.types[]. Its certified cut is the tier-1 evidence.
  • openwop-app (src/host/negotiationDecided.ts): the same; its builder already takes no recorder for a runless caller, which becomes the host-event path.
  • openwop-sdks: requested on the negotiationDecided payload type.

Acceptance criteria

  • [x] Active: the comment window was waived on the record (maintainer, 2026-10-10; MAINTAINERS.md). §A–§C merged; CHANGELOG.md records it.
  • [x] v2-negotiation-decided-emitted asserts requested, and v2-negotiation-decided-inbound ships, each sabotage-proved against a host that omits requested or records nothing inbound.
  • [x] Accepted: a certified major-2 bundle records openwop.requirement.0242. executed-pass for A.2 (inbound), B.2 and B.3. (2026-10-11: the v2 reference host's certified public cut on published suite 2.46.0, build commit:d16cac40; all seven 0242. ids executed-pass, the two outbound requested legs included.)

References

  • RFC 0175 §D.3 and §D.5 (the event and the invariants it witnesses); RFC 0236 (host events); RFC 0241 (the reserved host-event type pattern).
  • spec/v2/core/interop.md §Negotiation is a protocol; spec/v2/core/events.md §Host events; spec/v2/core/conformance.md §Witness class.
  • openwop-app ADR 0858 P4 (the report).

More proposals.

Edit this page on GitHub ↗