npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@openwop/openwop-conformance

v1.141.0

Published

Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.

Readme

@openwop/openwop-conformance — Conformance Suite for the Multi-Agent Workflow Orchestration Protocol

openwop is an open, wire-level protocol for multi-agent workflow orchestration — a single contract for runs in which LLM agents, deterministic tools, sub-workflows, and human reviewers collaborate, with durable suspend / resume, replay, version negotiation, and observability owned by the protocol itself. This package is the black-box conformance suite: point it at any OpenWOP-compliant server (your own or a third party's) and it issues real HTTP requests against the spec'd endpoints and asserts that responses match.

npm install @openwop/openwop-conformance
# or run without install:
npx @openwop/openwop-conformance --base-url https://api.example.com --api-key hk_test_...

Spec: github.com/openwop/openwop · See CHANGELOG.md below for release history.

The suite is intentionally self-contained — it does NOT depend on the reference implementation. A spec-compliant server written in any language can run this suite against itself by spinning up its server, exporting the env vars, and running npx vitest run.

Status: Tracks the FINAL v1 protocol contract. The suite version evolves independently as new scenarios ship (vendor-neutral redaction, cost attribution, post-v1 ecosystem triggers); see CHANGELOG.md for the current release.


Quickstart

Two ways to run: the friendly openwop-conformance CLI (recommended for operators) or vitest directly (recommended for CI).

CLI

cd conformance
npm install

# Build the CLI binary
npm run build:cli

# Server-free subset (no deployment target needed)
./dist/cli.js --offline

# Full suite against a deployed server
./dist/cli.js \
  --base-url https://api.example.com \
  --api-key hk_test_abc123 \
  --impl acme-openwop-server --impl-version 1.0

# Filter by test-name pattern
./dist/cli.js --base-url ... --api-key ... --filter "discovery|errors"

./dist/cli.js --help for the full flag reference. Env vars (OPENWOP_BASE_URL, OPENWOP_API_KEY, OPENWOP_IMPLEMENTATION_*) override CLI flags only when the flag is unset.

Direct vitest

cd conformance
npm install

export OPENWOP_BASE_URL="https://api.example.com"
export OPENWOP_API_KEY="hk_test_..."

npx vitest run                                 # full suite (parallel files, ~95s)
npx vitest run src/scenarios/discovery.test.ts # single file
npm run test:strict                            # full suite, no-file-parallelism

test:strict vs test. The default test script runs files in parallel (vitest default, ~3-5× faster). Most scenarios are isolation-safe at that level. Two exceptions document --no-file-parallelism as their canonical execution mode:

  • production-backpressure.test.ts — saturates the host's inflightCap; under parallel execution, neighbor tests posting /v1/runs during the saturation window see a 503 from the cap (cap-collateral). The scenario soft-skips its envelope assertions when this happens (logs a warning); test:strict exercises the full envelope contract.
  • OTel scenarios (otel-emission.test.ts, otel-trace-propagation.test.ts, metric-emission.test.ts) — each vitest worker spawns its own collector and only one can bind the configured OTLP port; concurrent file execution causes ephemeral-port fallbacks that don't receive the host's traffic.

Run npm run test for normal CI cadence; npm run test:strict when claiming full envelope coverage for production-profile + OTel claims.

Optional environment flags

| Variable | Effect | | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | OPENWOP_REQUIRE_BEHAVIOR=true | Capability-gated scenarios (audit-log integrity, rate-limit envelope, multi-region idempotency, configurableSchema, webhook sig versioning, etc.) FAIL instead of skipping when the host doesn't advertise the profile. Lets a host claim "full coverage" mechanically. See coverage.md §"Capability-gated scenarios". | | OPENWOP_TEST_PUBLIC_REGISTRY=true | Runs registry-public.test.ts against the hosted registry at packs.openwop.dev. Skipped by default so the suite doesn't depend on outbound connectivity. | | OPENWOP_OTEL_COLLECTOR=true | Boots the in-suite OTLP collector for otel-emission.test.ts, otel-trace-propagation.test.ts, metric-emission.test.ts, and otel-emission-grpc.test.ts. The collector accepts OTLP/HTTP-JSON (application/json), OTLP/HTTP-protobuf (application/x-protobuf — hand-rolled decoder at src/lib/otlp-protobuf.ts), and OTLP/gRPC (when OPENWOP_OTEL_COLLECTOR_GRPC=true — h2c HTTP/2 + hand-rolled framing at src/lib/grpc-framing.ts). Zero new npm deps. Hosts may emit via OTEL_EXPORTER_OTLP_PROTOCOL=http/json, http/protobuf, or grpc. Skipped by default. Run OTel scenarios with --no-file-parallelism — each vitest worker spawns its own collector and only one can bind the same port, so concurrent file execution causes ephemeral-port fallbacks that don't receive the host's OTLP traffic. | | OPENWOP_OTEL_COLLECTOR_GRPC=true | Boots the parallel OTLP/gRPC collector alongside the HTTP one (h2c HTTP/2 on a separate port). Shares the same spans() + metrics() store; spans captured over either transport surface in getCollector().spans(). Requires OPENWOP_OTEL_COLLECTOR=true. Same --no-file-parallelism requirement applies. | | OPENWOP_OTEL_COLLECTOR_GRPC_PORT=4317 | Bind the OTLP/gRPC collector on a specific port (default 4317, OTLP/gRPC convention). The host MUST be configured with OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:<port> AND OTEL_EXPORTER_OTLP_PROTOCOL=grpc. | | OPENWOP_OTEL_COLLECTOR_PORT=14318 | Bind the OTel collector on a specific port (default 4318). The host MUST be configured with OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:<port>. | | OPENWOP_WEBHOOK_ALLOW_PRIVATE=true | Relaxes the webhook egress guard for the loopback test receiver used by webhook-signed-delivery.test.ts, webhook-negative.test.ts, and replay-fanout-suppression.test.ts. The receiver is http://127.0.0.1:{port}/, which webhooks.md forbids three separate times, and the opt-in MUST relax all three to be witnessable: (1) the scheme check — §"SSRF protection" bullet 1 rejects non-https://, and this gate fires FIRST on a host that validates scheme before address; (2) the registration-time address check — §"SSRF protection"; and (3) delivery-time re-resolution — §"Delivery-time egress validation (RFC 0093)". Gates 2 and 3 are independent MUSTs at different layers, so an opt-in reaching only one layer cannot produce a witness: delivery-only leaves registration returning 400 webhook_url_rejected, registration-only leaves the dispatcher refusing to connect. A host whose opt-in reaches one layer is not non-conformant — it cannot witness these scenarios, which is a property of the test posture and not of its webhook signing. The scheme gate went unstated here until 2026-08-25; a tier-2 host validating scheme-then-address was blocked by a gate the documented contract never named. Relaxing gates 2 and 3 is test-only posture — see SECURITY/threat-model-secret-leakage.md §4.9 for why a registration-time relaxation is the more dangerous of the two. When the host rejects, the scenario records blocked (RFC 0148 §A), not a pass. | | OPENWOP_WEBHOOK_RECEIVER_URL=<https-url> | The route to a webhook witness that relaxes nothing. A host refusing the loopback receiver on two independent grounds — private address and non-https: — cannot be unblocked by OPENWOP_WEBHOOK_ALLOW_PRIVATE, because the scheme arm still stands. This supplies the public https: front for THIS SUITE'S OWN receiver (tunnel / TLS-terminating proxy), never an arbitrary endpoint: the scenario asserts on what this process received, so pointing registration elsewhere makes every header assertion vacuous while the row turns green. Zero deliveries with it set is a hard failure, never a skip. Preferred over the flag — it waives nothing and writes no durable subscription row aimed at a private address (SECURITY/threat-model-secret-leakage.md §4.9). Pair with OPENWOP_WEBHOOK_RECEIVER_PORT. | | OPENWOP_WEBHOOK_RECEIVER_PORT=<port> | Pins the in-process webhook receiver to a known port instead of an ephemeral one. Required in practice by OPENWOP_WEBHOOK_RECEIVER_URL: a tunnel must be aimed at a port known in advance, and the receiver binds 0 by default. Unset ⇒ ephemeral, as before. | | OPENWOP_MCP_FAKE_SERVER=true | Boots the synthetic MCP peer for mcp-tool-roundtrip.test.ts. | | OPENWOP_MCP_REAL_SERVER_URL=<base-url> | Points the MCP wire-shape probe at a real MCP server. The probe POSTs JSON-RPC and reads a single-JSON response — matches MCP's streamable-http transport in single-response mode. Does NOT support stdio transport (which is what most modelcontextprotocol/servers references default to) or SSE-streamed responses; an operator collecting interop evidence today runs a custom StreamableHTTPServerTransport-style server that returns a single JSON body per request. Adding SSE-frame parsing is tracked in docs/PROTOCOL-GAP-CLOSURE-PLAN.md Track 6. Assertions relax to shape-only. When both this and OPENWOP_MCP_FAKE_SERVER are set, the real URL wins. Phase 3 T3.4 interop-evidence path. | | OPENWOP_A2A_FAKE_PEER=true | Boots the synthetic A2A peer for a2a-task-roundtrip.test.ts. | | OPENWOP_A2A_REAL_PEER_URL=<base-url> | Points the A2A AgentCard + task-lifecycle probe at a real reference A2A peer. Drift-point subtests (AUTH_REQUIRED / REJECTED) stay fake-peer-only — real peers don't expose a state-forcing API. Phase 3 T3.4 interop-evidence path. | | OPENWOP_FORCE_RATE_LIMIT=true | Signals the host (test-only key) to fabricate a 429 so rate-limit-envelope.test.ts can exercise envelope shape deterministically. | | OPENWOP_TEST_PROMPT_PACK_INSTALLED=true | Promotes the prompt-pack-install.test.ts existence claim from soft-skip to hard assertion (RFC 0028 §B). Set when the target host is known to have at least one prompt pack installed at boot — RFC 0028 §B does NOT require any host with endpointsSupported: true to have packs installed, so the suite stays conformant against a fresh production host with no pack subscriptions when the flag is unset. The in-tree workflow-engine sample auto-installs vendor.openwop.prompt-sample via promptPackLoader.ts, so operators running against it should set the flag. |

Exit code is non-zero on any failed assertion. --certify distinguishes: 0 — every claimed profile certifiable; 1 — assertion failures; 3rejected (RFC 0148 §A): a claimed profile has an unclassified return — a floor/prefix requirement with no ledger row while the ledger is present, or an executed-pass with assertionCount: 0. The bundle is still written on 3 so the rejection is auditable; the reasons are printed per profile. Every scenario file records its file-level disposition (with assertion count) into <report-dir>/requirement-ledger.jsonl via OPENWOP_LEDGER_PATH, which the runner sets for you. A zero-assertion file that noted no reason (softSkip / seamAbsent / behaviorGate) is recorded blocked with a fixed marker rather than as a pass — RFC 0148 §A — and stays unclassified for a claimed floor. A runtime-derived profile (PROFILE_FLOOR_SCENARIOS[p].runtimeDerived; today openwop-node-packs, whose discovery predicate is openwop-core) is listed in claimedProfiles only when the host holds it — every floor row a witnessed pass; otherwise the summary prints not held and the claim is dropped rather than rejected.


What's Covered

The current suite has 465 scenario files under src/scenarios/.

  • 2026-08-19 (suite 1.137.0 → 1.138.0): NEW durability-poison-exhaustion.test.ts — RFC 0158 §C.8, the FIRST row of that RFC's conformance table to land. Asserts what failure-path.test.ts cannot: not just that deterministically failing work reaches terminal, but that attempts STOP — counted on the log, re-counted after a scaled quiet window, asserted unchanged. A host still redelivering records more. Seam-gated on the existing event-log seam (blocked = unobservable, not unmet) and outside every profile floor.
  • 2026-08-19 (suite 1.136.15 → 1.137.0): NEW replay-fanout-suppression.test.ts — capability-gated on webhooks.supported, outside every profile floor; witnesses replay.md §"Host-initiated fan-out is an external effect", which was the largest normative MUST NOT on the replay surface with no scenario and no SECURITY invariant. Three legs in ONE it against ONE receiver and ONE subscription — a positive control, the MUST NOT, and a branch boundary leg — because "no delivery arrived" passes identically when delivery never worked, so absence is asserted only after presence is proven on that exact wiring. A host with an SSRF guard correctly refuses the loopback receiver and records blocked: unobservable, not unmet.
  • 2026-08-18 (suite 1.136.7 → 1.136.8, SP-04): NEW spec-section-citations.test.ts — server-free; a <doc>.md §"<Section>" citation of a checked doc MUST resolve to a heading that exists. storage-adapters.md §"Claim acquisition" was cited by four artifacts, including a normative MUST in production-profile.md §Durability, and the section did not exist for the life of RFC 0009. Scoped to the docs whose section citations carry the durability contract; a corpus-wide sweep finds ~260 unresolved citations that need triage before they can gate.
  • 2026-08-17 (suite 1.135.4 → 1.136.0, S34): NEW node-failed-payload-shape.test.ts — fixture-gated on conformance-failure; every node.failed on the run's log MUST validate against run-event-payloads.schema.json §nodeFailed (error is a REQUIRED object { code, message }, not a string) — the second sibling host measured that reference hosts emitted data.code / a bare string for years and nothing on the wire checked.
  • 2026-08-17 (suite 1.134.0 → 1.135.0, S26): NEW discovery-families-view.test.ts — server-free pin for discoveryFamilies (root-first, deprecated capabilities wrapper as fallback), the view 38 wrapper-only readers now go through.
  • 2026-08-16 (suite 1.128.0 → 1.129.0): NEW compensation-recovery.test.ts — RFC 0151 §C retry-stable identity, §E tenant-bound operator authority, §B/§F recorded-facts replay, through the §21 recovery extension (unwind failFirstInverseAttempts/hold + inverseActions[], replay source[]/replayed[], NEW operator seam); gated on compensation.supported, blocked per sub-feature when the extension is absent.
  • 2026-08-16 (suite 1.127.0 → 1.128.0): NEW workload-identity-chain-bounds.test.ts — RFC 0154 §B chain bounds through the §20 seam (too-long / cyclic / scope-amplified refusals + a narrowing chain resolves), gated on auth.workloadIdentity.delegation.supported.
  • 2026-08-16 (suite 1.123.0 → 1.124.0, S22): NEW error-envelope-canonical-shape.test.ts (server-free) pins the decision that the HTTP error envelope is flat{ error: <code>, message, details? }, details.retriable — against the schema, the new lib/error-envelope.ts helper (readErrorCode / readRetriable, tolerating the legacy nested { error: { code } } seam shape through the first minor after 2026-11-10) and the prose; 15 HTTP-envelope legs read through the helper.
  • 2026-08-16 (suite 1.122.0 → 1.123.0) added two, both server-free and always-on: conformance-execution-witness.test.ts (an early return can never become a pass — the runner's resolveFileRecord, the emitter derivation and the consumer verifier each pinned with sabotage vectors) and conformance-advertised-seam-required.test.ts (under OPENWOP_REQUIRE_BEHAVIOR=true an advertised profile with a missing seam FAILS — behaviorGate, behaviorGatePresent, seamAbsent — and the only escape is an explicit opt-out that records skipped); mcp-stateless-request.test.ts gained the Mcp-Method/Mcp-Name half of mcp-header-body-consistent. Every scenario RFC 0148 §Conformance names now exists.
  • 2026-08-16 (suite 1.119.0 → 1.120.0, no new files): RFC 0148 §C gap G7 closed — PROFILE_FLOOR_SCENARIOS gained a discovery-conditional model (conditional: [{ path, includes, required }], evaluated against the captured discovery document) for openwop-replay-fork, and openwop-interrupts / openwop-secrets / openwop-provider-policy / openwop-memory / openwop-trigger-bridge received floors transcribed into profiles.md; every catalog profile now has a floor and unprovable is reserved for names outside the catalog. Against the tier-1 reference host: interrupts, secrets and provider-policy now certify on witnessed floors; replay-fork does not (its replay branch carries the unconditional no-re-fire obligation, blocked while the host advertises sideEffectSuppression: none); trigger-bridge does not (delivery seam unwired).
  • 2026-08-16 (suite 1.118.0 → 1.119.0, no new files): 84 scenarios that returned early in silence now say why — 256 early-return sites converted to softSkip(kind, reason) (inapplicable for a capability/profile/fixture the host does not advertise, blocked for a seam, prior step, or precondition that was unavailable); against the tier-1 reference host the RFC 0148 §A marker rows fell from 84 to 8 (all describe.skipIf/it.skip files) and inapplicable rose from 47 to 97.
  • 2026-08-16 (suite 1.117.0 → 1.118.0, no new files): the runner resolves a silent zero-assertion file to blocked (RFC 0148 §A) instead of a vacuous pass; NEW lib/soft-skip.ts (softSkip, seamAbsent) lets a scenario say why it returned early, and ten scenarios were converted as exemplars.
  • 2026-08-16 (suite 1.116.0 → 1.117.0, no new files): openwop-node-packs became runtime-derived in --certify (claimed only when held) and its two floor scenarios record inapplicable/blocked with reasons instead of returning early into a vacuous pass.
  • 2026-08-16 (S7 — RFC 0149 §D, suite 1.115.0 → 1.116.0) added one, server-free: normative-example-extraction.test.ts (a <!-- normative-example: <name>.schema.json --> marker above a fenced example declares it a whole instance; every declared example is extracted at test time and validated against schemas/<name>, and every undeclared whole-instance example fails — 26 declared across spec/v1).
  • 2026-08-16 (S12 — RFC 0148 §Conformance + RFC 0149 §A coverage residue, suite 1.114.0 → 1.115.0) added three, all server-free and always-on: certification-bundle-non-vacuous.test.ts (the consumer re-derivation of a v2 bundle — verifyBundleV2: missing witness, vacuous pass, duplicate requirement, tampered totals, invented disposition, missing reason all REJECT the evidence; an honest blocked/executed-fail on a required row is valid evidence that does not certify), certification-bundle-redaction.test.ts (secret canaries never enter evidence — the emitter scrubs the finished document with the handed credential + OPENWOP_* secrets + the conformance canary, and the verifier rejects the canary anywhere), and openapi-asyncapi-sdk-parity.test.ts (the generated spec/v1/operation-path-manifest.json re-derives from api/openapi.yaml; AsyncAPI addresses resolve to OpenAPI GET paths with exactly one /v1; the SDK half witnesses openwop-sdks when reachable and records blocked with a reason when not).
  • 2026-08-16 (RFC 0148 acceptance item 2 — S6, suite 1.113.0 → 1.114.0) added one: runner-ledger.test.ts (server-free — the §A ledger file sink, the reader's conflict rank, file-level disposition derivation, and --certify's rejection of silent and vacuous returns). Every scenario file now records its own disposition (with assertionCount) into the ledger from setup.ts, and --certify exits 3 when a claimed profile has an unclassified return. The first end-to-end run found that the openwop-core-standard floor had named audit-log-verification.test.ts — a file that never existed — since RFC 0088; the scenario it meant (audit-log-integrity.test.ts) gates on the optional annex profile openwop-audit-log-integrity, so by the floor's own no-soft-skip rule the row was never a floor scenario and is removed (core-standard-profile.md §C).
  • 2026-08-16 (RFC 0153 §B/§C/§D/§E — S16, suite 1.112.0 → 1.113.0) added six: mcp-2026-07-28-discover.test.ts (peer pin of the now dual-era McpFakeServer — stateless _meta, header/body agreement -32020, -32022 + data.supported[], server/discover, resultType, CacheableResult, MRTR input_required → retry with echoed requestState, -32021 without the elicitation capability — plus the host server/discoverprotocolVersions leg), mcp-stateless-request, mcp-mrtr-roundtrip (client half cross-checked against the wire; server half against an exposed suspending tool), mcp-cache-tenant-scope, mcp-extension-opacity, mcp-current-auth-boundary — all gated on mcp.profiles ∋ mcp-2026-07-28 / the §B advert + additive seam blocks (mrtr, extensionAuthority), blocked on today's 2025-06-18 hosts. Header-less requests to the fake server still get 2025-06-18 semantics, so mcp-tool-roundtrip and the legacy mcp-server-* legs are unchanged.
  • 2026-08-16 (RFC 0152 §C/§D/§E — S15, suite 1.111.0 → 1.112.0) added four: a2a-1-0-agent-card.test.ts (server-free — the suite's own A2AFakePeer, now dual-era, pinned at 1.0 from the wire: supportedInterfaces[] card, SendMessage/GetTask/CancelTask/ListTasks, TASK_STATE_*, Part oneof, -32009 on an unsupported A2A-Version, header-less = 0.3), a2a-card-runtime-consistency.test.ts (black-box, gated on a2a.profiles ∋ a2a-1.0), a2a-1-0-task-roundtrip.test.ts (host as 1.0 server, same gate), a2a-peer-authority.test.ts (host as client; additive seam scenario, blocked until the host reports). Also (S14) fixtures-valid.test.ts and workflow-primary-output-annotation.test.ts now register schemas by enumerating the directory instead of a fixed list — the fourth cross-file $ref (#1009) had broken the list at describe time, here and in every downstream runner resolving the sibling corpus with a pinned suite. Correction recorded the same day: openwop-app's live origin passes the RFC 0152/0153 §A legs and has NOT wired the invoke seams, so its §B legs are blocked (RFC 0148 §A) — an earlier note here said they pass.
  • 2026-08-16 (RFC 0157 — chain fragments carry compensation, suite 1.110.0 → 1.111.0) added one: chain-compensation-expansion.test.ts — the FragmentNode.compensation / WorkflowChain.compensation mirrors are byte-equal to their sources, the manifest stays self-contained (no cross-file $ref — the S14 lesson), and carryCompensation (composed after the byte-mirrored expansion core, not inside it) carries the declaration verbatim with params substituted and node-id refs rewritten, refuses an unresolvable compensator before any node is emitted, and copies / accepts / conflicts the chain policy into settings.compensation.
  • 2026-08-16 (RFC 0155 §A — openwop-discovery-core canonical / openwop-core deprecated alias, suite 1.109.0 → 1.110.0) added one: profile-discovery-core-alias.test.ts — the alias MUST derive exactly when the canonical name derives, checked over payloads on both sides of the predicate (an equivalence over payloads that all derive proves nothing about the other side), the doc's rename and claim vocabulary (an unqualified claim means openwop-core-standard), and a gated live-host leg; PROFILE_NAMES gained the canonical name and deriveProfiles emits both or neither. Same release: spec/v1/extensions.json gained a DERIVED coverage block — 5 covered / 81 uncovered / 4 core of 90 capability families — gated by generate-extension-registry-coverage.mjs --check in openwop:check, so "unlisted means uncovered" is a checked list rather than a sentence.
  • 2026-08-13 (host-callback declaration — every scenario needing the HOST to reach the harness declares it, so an off-process consumer can enumerate the unwitnessable set before running).
  • 2026-08-13 (RFC 0153 §A/§B — MCP revision negotiation witness, suite 1.96.0 → 1.97.0) added one: mcp-version-negotiation.test.ts (four capability-gated legs) plus header capture on McpFakeServer. Both fake peers had the identical gap — recording the call body but not its headers — which is worth naming as a shared assumption rather than two oversights: the interesting part of a call is its body. Version negotiation is precisely the case where that is wrong. MCP revisions are dates, so the wire header is checked for date form and not merely for presence: a host sending latest or 2026-7-28 has sent something unmatchable against a pinned peer, and the failure then surfaces at the peer rather than at the handshake meant to prevent it. A further leg requires the negotiated revision to appear in protocolVersions, because a host that negotiates a revision it never advertised has made its own discovery document unreliable — worse than advertising nothing, since a consumer that read it decided on a fact that was not true. Pinned real MCP current peer passes in CI stays separate and unmet.
  • 2026-08-13 (RFC 0152 §B — A2A version negotiation witness, suite 1.95.1 → 1.96.0) added one: a2a-version-negotiation.test.ts (four capability-gated legs) plus header capture on A2AFakePeer. RFC 0152 was twice described here as needing a live upstream peer, and that was half true: interop needs a real peer, negotiation does not — negotiation is the host's behavior and the host is right here. What was actually missing is that A2AFakePeer recorded method, path, rpcMethod, and body but not headers, and §B lives entirely in the headers: the peer could see that a call happened but not which version was negotiated, which is the only part §B is about. The fix was to record what the fake peer already received. The load-bearing leg is negative — "a host MUST NOT silently downgrade an authenticated request" — and a silent downgrade is dangerous precisely because it SUCCEEDS: the caller believes it negotiated 1.0, the peer answered 0.3, and nothing in the response says otherwise. A failure would at least be visible. So a host that proceeds after a downgrade MUST report the version it actually negotiated, and the wire header MUST match. Real upstream A2A 1.0 peer passes in CI remains separate and unmet — that item tests interoperation, which no fake can stand in for, and the RFC's acceptance criteria still say so.
  • 2026-08-13 (RFC 0154 §A/§B — the workload-identity BEHAVIORAL witness, suite 1.94.0 → 1.95.0) added one: workload-identity-behavior.test.ts (six capability-gated legs) plus host-sample-test-seams.md §20. §A's requirements are invisible from the wire: verify, bind to request, resolve to a principal before authorization, fail closed — and a normal request either succeeds or 401s, both of which look identical whether the host verified anything or simply trusted a header. That invisibility is why the seam exists, and why a missing seam is reported as blocked rather than skipped: RFC 0148 §A resolves an unobservable requirement to blocked, so without it RFC 0154 cannot be certified at all. The negatives carry the weight — a host that echoes its input satisfies every positive assertion, so only the rejections distinguish a verifier from a passthrough: audience_mismatch (an identity minted for another host, accepted here, is how a credential valid elsewhere becomes valid here — RFC 0147 R12), delegation_expired (a delegation without a live expiry is a standing grant, which is not what delegation means), and sender_constraint_missing (without proof-of-possession a bearer credential is replayable by anyone who observed it). Failures MUST be non-retriable — an identity that does not resolve will not resolve on retry, and marking it retriable invites hammering a failing authorization path. The seam MUST NOT be a mock: it drives the host's real resolver, or the witness proves nothing about production.
  • 2026-08-13 (RFC 0151 §C–§G — the compensation BEHAVIORAL witness, suite 1.93.0 → 1.94.0) added one: compensation-behavior.test.ts (five capability-gated legs). This closes a gap in the SUITE, not in any host. RFC 0147 §A.5 requires a host to execute every normative behavioral path in strict mode — and RFC 0151 had no behavioral scenario at all, only a server-free schema check. A willing host had nothing to run. For several turns the blocker was described as "no host implements this" when part of it was that the evidence-collecting apparatus did not exist either. Each leg is gated on compensation.supported via behaviorGate, so it soft-skips against a non-advertising host and hard-fails under OPENWOP_REQUIRE_BEHAVIOR=true, recording an RFC 0148 §A ledger disposition either way. The legs pin the §C rules that are otherwise unverifiable: plan persisted before the first inverse action (requested strictly precedes started — a host that unwinds before persisting cannot resume, and the crash is exactly when that matters); descending forward-completion order (compensating forward can release a resource a later inverse action still needs); replay does not re-fire (§F — a replay that re-executes inverse effects turns a recovery into a second outage); and no provider bodies or credentials in events (§D/§G — these land in the durable log, the least revocable place a credential can reach). A missing sample seam fails with a message saying the requirement is unobservable and therefore blocked, not passed.
  • 2026-08-13 (RFC 0148 §C — certification bundle v2, suite 1.91.0 → 1.92.0) added one: certification-bundle-v2.test.ts (eight legs, server-free) plus schemas/certification-bundle-v2.schema.json. v1 recorded {passed, failed, skipped} as scenario-FILE lists, and those three words cannot express the distinction this program turns on: a file counted as passed whether its assertions ran or its runner returned early, and skipped flattened three different claims — "the operator excluded this", "the requirement does not apply", and "we could not check" — into one word, when only the first two are certifiable. v2 carries per-requirement dispositions from §A plus the counts §A.4 requires. Two properties are load-bearing: blocked is a REQUIRED total, because blocked: 0 asserted is a different claim from blocked unstated and an omitted total reads as zero; and assertionCount makes a vacuous pass visible — the schema deliberately still ADMITS executed-pass with assertionCount: 0, because forbidding it would only move the lie one field over, while putting the number in the artifact lets a reader see a pass that executed nothing without re-running the suite. Sabotage-verified: dropping blocked from required and removing minItems on the requirement list red two legs.
  • 2026-08-13 (RFC 0154 §A/§B — workload identity and delegation, SHAPE ONLY, suite 1.90.0 → 1.91.0) added one: workload-identity-profile.test.ts (eleven legs, server-free) plus schemas/workload-identity.schema.json and an auth.workloadIdentity capability. §A's real requirements are behavioral — cryptographically verify the presented identity, bind it to the request, resolve it to a principal before authorization, fail closed when unresolvable — and a schema demonstrates none of them. What a schema can do is make the dangerous shape unrepresentable, which is what this checks: raw certificates, tokens, proofs, and credentials MUST NOT enter these objects. subject is opaque; proofRef and thumbprintRef are pattern-constrained digest references, so a JWT or a raw key cannot be pasted in and still validate. That matters because these objects are projected into events, spans, and audit records — the three places credential material must never reach. audience is required on a delegation because an identity minted for somewhere else, accepted here, is the confused-deputy path (RFC 0147 R12), and identity is not authorization is the one rule a schema cannot enforce at all. Sabotage-verified: opening additionalProperties and dropping the digest pattern red two legs.
  • 2026-08-12 (RFC 0152 + RFC 0153 §A — versioned composition discovery, SHAPE ONLY, suite 1.89.0 → 1.90.0) added one: versioned-composition-profiles.test.ts (eleven legs, server-free) plus protocolVersions / preferredVersion / profiles on a2a and mcp, and a closed features list on mcp. Both RFCs address the same defect, which is why they land together: supported: true with no version is a claim a peer cannot negotiate against. Two hosts can both advertise it, share no revision, and discover that only when a call fails — at the peer, not at the handshake meant to prevent it. MCP versions are date-patterned rather than free strings because MCP revisions are dates: accepting latest or 2026-7-28 would let two hosts disagree about which revision they share while both validate. The MCP feature list is closed because an unrecognized name is indistinguishable from a typo. Shape only, and the file says so — nothing contacts a peer, and a final leg asserts both RFCs keep their upstream-peer acceptance items unticked, since interop is the one thing a schema cannot demonstrate. A first draft had the negatives passing for the wrong reason: the a2a family independently requires agentCardUrl, so fixtures omitting it failed on the missing card URL rather than the version defect under test — the vacuity pattern, inside a test written to prevent it.
  • 2026-08-12 (RFC 0151 §A/§B — compensation profile SHAPE ONLY, suite 1.88.0 → 1.89.0) added one: compensation-profile.test.ts (nine legs, server-free) plus the compensation capability family and the node-level compensation declaration. What it proves is stated in the file itself: the schemas admit the shapes §A/§B describe and reject the ones they forbid. That is shape-only evidence — not evidence that any host orders an unwind, persists a plan before the first inverse action, or resumes after a crash. RFC 0147 §A.5 forbids Accepted on shape-only evidence for a behavioral requirement, and a final leg asserts 0151's behavioral acceptance item stays unticked and annotated, so reading the suite alone cannot leave a different impression. Design constraints the schema encodes: compensation is a second effect, not an undo — it can fail, be partially applied, and be harmful (R9), hence requiresApproval and security-high; inputs come from recorded facts, since an inverse built from a re-inferred value is not the inverse of what was done; and nodeTypeId resolves at registration, so an unwind cannot fail on a typo first discovered during a failure. Sabotage-verified: opening additionalProperties on the declaration reds the closed-shape leg.
  • 2026-08-12 (RFC 0147 §A.10 — program self-audit, suite 1.87.0 → 1.88.0) added one: rfc-0147-self-audit.test.ts (four legs, server-free) plus docs/RFC-0147-SELF-AUDIT.md. §A states ten invariants that "apply to every workstream", and §A.10 forbids citing the RFC's partial implementation as evidence its gaps are closed — so a program that audits everything except itself has the same defect it was written to fix, one level up. The audit found two violations: §A.5 (0151–0154 reached Accepted with no evidence, not merely shape-only) and §A.6 (0148/0150/0152/0153/0154 had comment windows waived under MAINTAINERS.md bootstrap language — the exact mechanism §A.6 says must not shorten a high-risk window). The available technicality — 0147 was Draft at the instant of the flip — is recorded and not relied upon, because reading the program's partial state in whichever direction is convenient is the shape §A.10 forbids. The gate does not assert compliance; it asserts every invariant carries an explicit disposition, including the adverse ones. Making it fail on a violation would create pressure to delete the row rather than fix the program. Sabotage-verified both ways: replacing VIOLATED with satisfied reds, and deleting the §A.6 row reds.
  • 2026-08-12 (RFC 0155 §B/§C — core-standard manifest + extension registry, suite 1.86.0 → 1.87.0) added one: core-manifest-and-extension-registry.test.ts (eight legs, server-free), plus spec/v1/core-standard-manifest.json, spec/v1/extensions.json, and a generator wired into openwop:check. §B's value is the sentence after the inventory"prose and code profile definitions MUST be generated from or checked against this manifest". Three places described the core-standard floor independently and could only be assumed to agree; they did not, and PROFILE_FLOOR_SCENARIOS being an incomplete transcription is what let five profiles verify as floor-proven against nothing (RFC 0148 §C). The manifest is derived, never hand-listed, because a hand-listed manifest drifts the moment the corpus moves and then asserts the drift with a digest attached. §C's registry carries six records backfilled from each RFC's own status. Every one is draft, and none can currently be stable: §C gates stable on a Tier-3 implementation and none exists, so that ceiling is recorded rather than worked around — a fact about adoption, not about the work. Sabotage-verified on both halves: dropping a floor scenario from the manifest reds the parity leg, and marking any extension stable without an evidenceTier reds the overclaim leg.
  • 2026-08-12 (RFC 0150 §C — golden vectors, suite 1.85.0 → 1.86.0) added one: semantic-digest-vectors.test.ts (sixteen legs, server-free) plus conformance/vectors/semantic-request-digest-v2.json. §C's acceptance criterion is that TypeScript, Python, and Go compute the same digest, and prose cannot deliver that — three independent readings of "canonicalize via JCS and hash" is exactly how three implementations diverge, invisibly, until two hosts replay the same run and get different cache keys. The vectors are the contract; an SDK reproduces the file. Several are pairs whose relationship IS the requirement: tools sorted vs reversed MUST agree, message order reversed MUST NOT, and the two Unicode forms of é MUST NOT — because JCS does not apply NFC. That last pair catches a well-meaning "add NFC to be safe" change that every other vector passes. The first draft of this gate had the bug it was written to catch: the relationship legs compared two stored digests from the same file — two constants — so they validated the vector set and would have passed unchanged against a broken implementation. They now recompute from input. Sabotage before the fix reddened one leg; after, two.
  • 2026-08-12 (RFC 0150 §C — semantic request digest v2, safety-fix, suite 1.84.0 → 1.85.0) added one: semantic-digest-v2.test.ts (six legs, server-free). Three defects in replay.md. (1) The exclusion list forbade exactly what §C requires: it said max_tokens, stop, and seed MUST NOT influence the cache key — but all three change the completion, so keying them identically causes a wrong hit, not a miss, deterministically returning a response the caller never asked for. (2) It prescribed a normalization JCS does not perform: canonicalize "via RFC 8785 JCS", then, for hosts without JCS, "UTF-8 NFC for all strings" — so the two routes the same sentence offered produced different bytes for the same input, defeating the cross-host portability §D asserts as a normative invariant. (3) It quoted a formula that no longer exists: the attempt-bearing Layer-2 composition RFC 0150 §B retired, left behind when idempotency.md went to v1.4 — a cross-document staleness §B itself introduced and did not catch. v2 stamps the canonical object openwop-semantic-request-v2 so retired digests are distinguishable, and carries unknown provider options in a namespaced providerOptions rather than dropping them. The gate reads normative text only, excluding blockquotes, because the document deliberately quotes the rules it retired — with a guard that the filter retains >70% of the file, and sabotage-verified that reintroducing a retired rule in normative voice still reds.
  • 2026-08-12 (RFC 0148 §B — strict behavior, suite 1.83.0 → 1.84.0) added one: strict-behavior-gate.test.ts (seven legs, server-free). §B says "a host MUST NOT both advertise and opt out of the same profile" — and behaviorGate() detected exactly that contradiction, emitted a console.warn, and proceeded as if advertised. A MUST NOT enforced by a warning is not enforced: nothing consumes the warning, nothing fails on it, and the certification bundle produced from that run records a pass. It matters because the two claims are opposite in kind — advertising says the host implements the profile, opting out says the operator declares it does not — so a run where both hold has no defensible reading, and the resolution the gate chose (advertisement wins) extracted more certification claim from a more contradictory input. Now throws. The second half wires §B to §A: a gate decision records a ledger disposition, so an honest opt-out is skipped and an unadvertised profile is inapplicable — both certifiable, and both distinct from the blocked that §A assigns to silence. Before the ledger existed there was nothing to record into, which is why §B could not be implemented before §A.
  • 2026-08-12 (RFC 0148 §A — requirement execution ledger, suite 1.82.1 → 1.83.0) added one: requirement-ledger.test.ts (thirteen legs, server-free) plus src/lib/requirement-ledger.ts and src/lib/requirement-registry.ts. §A's operative sentence is negative — "a plain test return, caught exception converted to a return, or empty assertion body MUST NOT produce executed-pass" — and a ledger that merely offers five dispositions does not deliver it. Every vacuity found in this corpus reached pass by not running, not by running wrong: [].every(...) over an undefined floor, a gated subtest that 404'd and soft-skipped, a scenario whose assertions never executed while its file counted green. So the ledger inverts the default: a requirement with no recorded disposition resolves to blocked, never to a pass. Silence is evidence of nothing, and the data structure says so instead of relying on each author to remember it. blocked is not certifiable (skipped and inapplicable are); an empty requirement set does not certify, because that is the [].every(...) shape itself; requirementsFor() returns null rather than [] for an unwritten floor, forcing the caller to distinguish "empty by design" from "not transcribed yet"; a non-pass disposition without a reason throws; and contradictory dispositions for one requirement throw rather than last-write-wins, since silent overwrite would let a later soft-skip bury an earlier real failure. Scoped to the certification floor, not all 423 files — tagging every assertion in one pass would produce a registry nobody could review, and an unreviewed requirement ID is worth less than none because it looks like coverage. Sabotage-verified on the load-bearing property: defaulting absence to executed-pass reds the gate.
  • 2026-08-12 (RFC 0150 §B — cross-scope effect identity, additive, suite 1.81.0 → 1.82.0) added one: effect-identity-cross-scope.test.ts (four legs, server-free). runId is in the §B preimage, so an effect issued outside any run cannot produce a Layer-2 identity at all and can never collide with one. Layer 2 dedupes a node effect against its own retries; it does not dedupe it against the same logical effect issued via an operator route, admin action, or scheduled job. Since §"Why this exists" requires Layer 2 "for any node executor that performs an external side effect", a host reading that literally would use the ordinal form and stop — and for a cross-entry-point effect that reintroduces the duplicate-effect class §B exists to kill, on the highest-stakes path it touches. v1.4 requires such effects to be additionally keyed on a business identity, stable across entry points and containing no runId/nodeId/ordinal. Reported by a tier-1 host from a shipped node pack (refund-order plus three non-run entry points reaching one implementation), not proposed in the abstract. The fork note and this are one limitation seen twice, and a leg asserts the linkage so a reader cannot read the fork case as a special exception.
  • 2026-08-12 (RFC 0149 §D — lifecycle coherence, governance, suite 1.80.0 → 1.81.0) added one: rfc-lifecycle-coherence.test.ts (three legs, server-free). §D asks the generator to fail when an Accepted RFC "retains an unresolved acceptance blocker not explicitly carried". The obvious gate — every box ticked before Accepted — was measured and rejected: of 141 Accepted RFCs, 42% ticked all, 25% ticked none, 24% ticked some, so a blanket rule fails 69 RFCs on its first run, and a gate that fires 69 times gets disabled rather than fixed. The first triage hypothesis was also wrong, and correcting it produced the rule that shipped: the partially-ticked RFCs are not a blocker backlog — every trailing item in 0027/0040/0041 is deliberately unticked and annotated with why, which is §D's "explicitly carried", just in a parenthetical rather than a register row. So the signal is annotated vs bare, not ticked vs unticked: an unticked item with no explanation is indistinguishable from one nobody checked. Scoped to RFCs ≥ 0147 — the program's own cohort — so the rule binds the RFC that proposed it, with a leg asserting exactly that; earlier RFCs are the dated record of a period when the convention did not exist, and the boundary is asserted so a new RFC cannot inherit the exemption. All 67 bare items across 0147–0156 were annotated to land it, which is the real work: each now names its gate. Sabotage-verified — stripping one annotation reds the leg with its file:line.
  • 2026-08-12 (RFC 0149 §E — canonical-family shadowing, security, suite 1.79.0 → 1.80.0) added one: discovery-canonical-family-no-shadow.test.ts (three legs, server-free). RFC 0073 puts canonical families at the document root and host-extensions.md puts vendor surface under x-host-* / vendor.* / private.*; together that is what makes discovery negotiable. Nothing forbade a canonical family name appearing INSIDE the namespaced regionvendor.acme.auth, or an x-host-acme-* object carrying its own interrupts. A consumer that merges vendor surface over the root before negotiating then reads a vendor's auth block as the auth contract. host-extensions.md already tells clients to treat extension surface as opaque, but that binds the consumer — it does not stop a host publishing the collision, and the consumer that gets the merge order wrong is exactly the one the rule exists to protect. Second leg, unconditional: no discovery example may carry credential material, because discovery is served credential-free to anonymous callers, so a real-shaped secret in an example sits in the most-copied, least-guarded artifact in the corpus. The family list is read from capabilities.schema.json rather than hand-listed, so a new family is covered the day it lands. The corpus was already clean, making this a guardrail rather than a repair — so it was sabotage-verified rather than red-before-green: injecting "vendor.acme.suite": { "auth": … } reds the shadow leg with its file:line and the colliding key, and an sk--prefixed value reds the credential leg.
  • 2026-08-12 (RFC 0149 §C — protocolVersion grammar, safety-fix, suite 1.78.0 → 1.79.0) added one: protocol-version-grammar.test.ts (twenty legs, server-free). The field was specified three incompatible ways at once: capabilities.schema.json constrained it to minLength: 1, the suite's own core predicate tested startsWith('1.'), and prose called it semver while every example showed two components. So "v1.0", "1.0.0", and "banana" all validated — and "1.0.0" additionally derived openwop-core, because the predicate deciding whether a host is openwop-compatible at all was looser than the schema every host validates against. Comparison needs an integer major (the hard boundary) and an integer minor (the additive contract level); neither is extractable from a string nothing constrains, so two hosts advertising "1.0" and "1.0.0" gave a consumer no way to tell a patch convention from a typo from a different protocol. Now ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ in the schema, in profiles.ts, and in version-negotiation.md §"Protocol version grammar" — with a leg asserting the schema pattern and the predicate agree, so they cannot drift apart again. Closes gap V2, open since v1.0 with owner future.
  • 2026-08-12 (RFC 0150 §D — multi-region effect vocabulary, safety-fix, suite 1.77.0 → 1.78.0) added one: multi-region-effect-vocabulary.test.ts (eight legs, server-free). Two capability values promised things they did not deliver. crossRegion: "strict" was documented as "cross-region read-visibility is bounded by multiRegion.replicationLagBoundMs" — a latency claim occupying the top slot of a ladder implementers read as effect safety. A host replicating synchronously at 0 ms can still issue duplicate effects from two regions, because knowing what the other region wrote is not the same as being authorized to act. It was removed rather than renamed to fenced-effects: a rename would have promoted every existing strict advertisement into a claim no host substantiated, and its latency content is not lost because replicationLagBoundMs already carries it. partitionRecoveryStrategy offered last-writer-wins and first-writer-wins, both time-ordered — but under a partition there is no shared clock, so each region believes it wrote last, which cannot satisfy the annex MUST that "re-running the same conflict input MUST produce the same survivor", and both select a different survivor than the lex-min(runId) rule the same document requires. The schema was advertising strategies that violate two MUSTs already in force. RFC 0150 §D names only last-writer-wins; removing that alone would have left the identical defect under a different label, so both went and lexicographic-min-run-id was added to name the rule the annex actually requires. The gate also catches the unparseable ```json example RFC 0149 §B found and deliberately left. Corpus-structural tier: it reads the schema and the prose, not a host — whether an engine fences before issuing an effect needs a partition simulator plus an observable effect sink, which the existing seam does not provide.
  • 2026-08-12 (RFC 0150 §B — Layer-2 effect identity v2, safety-fix, suite 1.76.0 → 1.77.0) added one: effect-identity-composition.test.ts (six always-on server-free legs, skipped under the published layout). spec/v1/idempotency.md specified the Layer-2 key as sha256(runId ':' nodeId ':' attempt ':' providerKey) while the same document promised that a retried call's identity "is identical" — both cannot hold, and the formula was the wrong half. A retry counter in the preimage gives every retry a fresh key, so the invocation log never hits, and because the engine injects that value as the provider's Idempotency-Key, it also sends Stripe or OpenAI a different key each attempt — defeating the provider-side dedup the same paragraph invokes as its safety net. v1.2 specifies the domain-separated, tenant-bound composition over a logicalInvocationOrdinal that MUST NOT change across retries. The gate's tier is labelled corpus-structural on purpose: it reads the normative composition, not a host, because whether an engine reuses the identity across a retry is observed by the provider, not the caller, and a wire probe claiming otherwise would be the vacuous-witness pattern RFC 0148 exists to close (gap G9). Sabotage: weakening the ordinal rule to SHOULD NOT, or deleting it, reds the leg.
  • 2026-08-12 (RFC 0149 §B — discovery-example root layout, editorial, suite 1.75.0 → 1.76.0) added one: capability-example-root-layout.test.ts (three legs, server-free, skipped under the published layout because spec/v1/ and RFCS/ are not in the tarball). RFC 0073 made the document-root layout "the normative MUST since Phase 1" and, at Phase 4, made the suite grade a wrapper-only host non-conformant — the capabilities wrapper survives only as a deprecated shape runtime discovery tolerates until v2.0. Tolerating a shape at runtime and teaching it in a normative example are different things: eight spec/v1 examples, several under headings like "Capability advertisement (normative)" introduced by prose saying hosts "advertise it under /.well-known/openwop", showed the deprecated wrapper. An implementer copying one produced a document RFC 0073 grades as non-conformant, and no gate noticed, because a fenced example is prose to every validator in the corpus. All eight unwrapped. Authoring-time only — nothing here reads a host, and RFC 0149 §B keeps runtime discovery open to unknown server-emitted properties. The RFC leg asserts the historical carve-out rather than assuming it: every RFCS/ example still showing the wrapper is in an RFC numbered below 0073, so a new post-0073 RFC introducing one fails and the exemption cannot widen into a licence. Sabotage: restoring one wrapped example reds the lint with its file:line.
  • 2026-08-12 (RFC 0148 §C / gap G6 — floor enforcement, implementation of existing normative text, suite 1.74.0 → 1.75.0) added one: certification-floor-enforcement.test.ts (six always-on server-free legs). PROFILE_FLOOR_SCENARIOS defined a floor for openwop-core-standard alone, and verifyBundleProfile() computed floorProven from missingFloor.length === 0 && prefixOkboth vacuously true over an absent floor, so a claimed profile with nothing behind it verified as proven. The floors were transcribed, not invented: profiles.md §"Claiming vs passing" already requires "predicate AND passing the conformance scenarios labelled with the profile tag", and its per-profile sections already name them — so this enforces normative prose rather than adding to it, and needs no safety-fix window. An empty floor and an unwritten floor are now different values: openwop-core and openwop-fixtures are discovery-payload-only by prose and carry an explicit discoveryOnly marker; an absent key means unprovable, surfaced as floorUnspecified so "the corpus has no floor" stays distinct from "the host failed its floor". openwop-replay-fork is deliberately left unspecified — its floor is conditional on the advertised mode, which a flat required-list cannot express, and forcing it would either fail an honest single-mode host or restore the vacuity. Sabotage: restoring the old undefined-floor branch reds two legs.
  • 2026-08-11 (RFC 0149 §A — canonical URL resolution, editorial, suite 1.73.1 → 1.74.0) added one: openapi-resolved-paths.test.ts (three always-on server-free legs). It resolves each servers[].url against each path key and asserts exactly one /v1 segment for versioned operations. Why every existing gate was blind to this: redocly lint validates the server, validates the paths, and never composes the two — the defect existed only in the join, where all 44 versioned path keys resolved to /v1/v1/*. The reference SDKs were the control: OpenwopClient issues /v1/runs against a bare base URL, so the SDKs and the canonical document disagreed about where the version segment lives and the SDKs were correct. A third leg asserts /.well-known/* resolves unversioned — the red run showed the duplicated base also resolved discovery to /v1/.well-known/openwop, so a generated client could not bootstrap at all. A guard leg fails if the extraction finds no servers or no paths, because a scan that silently matched nothing would make the other legs vacuously true. Written red first, observed failing on both counts, then green after the one-line correction.
  • 2026-08-11 (RFC 0146 — contractProvenance, additive, suite 1.72.2 → 1.73.0) added one: contract-provenance.test.ts (four always-on corpus legs + one behavioral). Why validation alone cannot catch what this closes: v1.x changes are additive, so a discovery document written against an older contract still validates against the newer schema — which is exactly why a host validating against a vendored copy 7 properties behind was green. Only a claim on the wire surfaces it. The legs assert the SHAPE of that claim and never that a host is current, because a host on an older corpus revision is CONFORMANT; a leg failing a host for being behind would convert an optional disclosure into a de-facto upgrade mandate inside a version line where be